ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e)方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是 O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element)),时间复杂度就为 O(n)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的 (n-i) 个元素都要执行向后位/向前移一位的操作。
LinkedList 采用链表存储,所以在头尾插入或者删除元素不受元素位置的影响(add(E e)、addFirst(E e)、addLast(E e)、removeFirst()、 removeLast()),时间复杂度为 O(1),如果是要在指定位置 i 插入和删除元素的话(add(int index, E element),remove(Object o),remove(int index)), 时间复杂度为 O(n) ,因为需要先移动到指定位置再插入和删除。
publicbooleancontains(Object o) { returnindexOf(o) >= 0; } publicintindexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return-1; }
List<String> l1 = Lists.newArrayList(anotherListOrCollection); // from collection List<String> l2 = Lists.newArrayList(aStringArray); // from array List<String> l3 = Lists.newArrayList("or", "string", "elements"); // from varargs
5、使用 Apache Commons Collections
1 2
List<String> list = new ArrayList<String>(); CollectionUtils.addAll(list, str);
6、 使用 Java9 的 List.of()方法
1 2
Integer[] array = {1, 2, 3}; List<Integer> list = List.of(array);
使用 List.subList 进行切片操作居然会导致 OOM
List.subList 返回的子 List 不是一个普通的 ArrayList。这个子 List 可以认为是原始 List 的视图,会和原始 List 相互影响。如果不注意,很可能会因此产生 OOM 问题。
如下代码所示,定义一个名为 data 的静态 List 来存放 Integer 的 List,[也就是说 data 的成员本身是包含了多个数字的 List。循环 1000 次,每次都从一个具有 10 万个 Integer 的 List 中,使用 subList 方法获得一个只包含一个数字的子 List,并把这个子 List 加入 data 变量:
1 2 3 4 5 6 7 8
privatestatic List<List<Integer>> data = newArrayList<>();
privatestaticvoidoom() { for (inti=0; i < 1000; i++) { List<Integer> rawList = IntStream.rangeClosed(1, 100000).boxed().collect(Collectors.toList()); data.add(rawList.subList(0, 1)); } }
出现 OOM 的原因是,循环中的 1000 个具有 10 万个元素的 List 始终得不到回收,因为它始终被 subList 方法返回的 List 强引用。
/** * Returns a power of two size for the given target capacity. */ static final int tableSizeFor(int cap) { intn = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1: (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY :n + 1; }
// Returns: true if this set did not already contain the specified element // 返回值:当 set 中没有包含 add 的元素时返回真 publicbooleanadd(E e) { returnmap.put(e, PRESENT)==null; }
而在HashMap的putVal()方法中也能看到如下说明:
1 2 3 4 5 6
// Returns : previous value, or null if none // 返回值:如果插入位置没有元素返回 null,否则返回上一个元素 final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict){ ... }
staticinthash(int h){ // This function ensures that hashCodes that differ only by // constant multiples at each bit position have a bounded // number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }
The main reason that nulls aren’t allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can’t be accommodated. The main one is that if map.get(key) returns null, you can’t detect whether the key explicitly maps to null vs the key isn’t mapped. In a non-concurrent map, you can check this via map.contains(key), but in a concurrent one, the map might have changed between calls.
Java SE(Java Platform, Standard Edition) - Java 平台标准版。Java SE 的 API 提供了 Java 编程语言的核心功能。它定义了从 Java 编程语言的基本类型和对象到用于网络、安全、数据库访问、图形用户界面 (GUI) 开发和 XML 解析的高级类的所有内容。除了核心 API 之外,Java SE 平台还包括虚拟机、开发工具、部署技术以及 Java 技术应用程序中常用的其他类库和工具包。
Java EE(Java Platform, Enterprise Edition) - Java 平台企业版。Java EE 构建在 Java SE 基础之上。 Java EE 定义了企业级应用程序开发和部署的标准和规范,如:Servlet、JSP、EJB、JDBC、JPA、JTA、JavaMail、JMS。
publicstatic Integer valueOf(int i){ if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; returnnewInteger(i); } privatestaticclassIntegerCache { staticfinalint low = -128; staticfinalint high; static { // high value may be configured by property int h = 127; } }
Character 缓存源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
publicstatic Character valueOf(char c){ if (c <= 127) { // must cache return CharacterCache.cache[(int)c]; } returnnewCharacter(c); }
privatestaticclassCharacterCache { privateCharacterCache(){} staticfinal Character cache[] = new Character[127 + 1]; static { for (int i = 0; i < cache.length; i++) cache[i] = newCharacter((char)i); }
public classHero{ public String name() { return"超级英雄"; } } public classSuperManextendsHero{ @Override public String name() { return"超人"; } public Hero hero() { returnnewHero(); } }
public classSuperSuperManextendsSuperMan{ public String name() { return"超级超级英雄"; }
@Override public SuperMan hero() { returnnewSuperMan(); } }
XML 文件 - 如果只是企业内部之间的服务调用,并且都是 Java 语言的话,选择 XML 配置方式是最简单的。
IDL 文件 - 如果企业内部存在多个跨语言服务,建议使用 IDL 文件方式进行描述服务。
REST API - 如果存在对外开放服务调用的情形的话,使用 REST API 方式则更加通用。
XML 文件
XML 配置方式通过在服务提供者和服务消费者之间维持一份对等的 XML 配置文件,来保证服务消费者按照服务提供者的约定来进行服务调用。在这种方式下,如果服务提供者变更了接口定义,不仅需要更新服务提供者加载的接口描述文件 server.xml,还需要同时更新服务消费者加载的接口描述文件 client.xml。但这种方式对业务代码侵入性比较高,XML 配置有变更的时候,服务消费者和服务提供者都要更新,所以适合公司内部联系比较紧密的业务之间采用。支持 XML 文件的主流 RPC 有:阿里的 Dubbo(XML 配置示例:基于 Spring XML 开发微服务应用)、微博的 Motan。
XML 文件这种方式的服务发布和引用主要分三个步骤:
(1)服务提供者定义接口,并实现接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// The demo service definition. service DemoService { rpc sayHello(HelloRequest) returns (HelloReply) {} }
// The request message containing the user's name. message HelloRequest { stringname=1; }
// The response message containing the greetings message HelloReply { stringmessage=1; }
注册中心作为服务提供者和服务消费者之间沟通的桥梁,它的重要性不言而喻。所以注册中心一般都是采用集群部署来保证高可用性,并通过分布式一致性协议来确保集群中不同节点之间的数据保持一致。根据 CAP 理论,三种特性无法同时达成,必须在可用性和一致性之间做取舍。于是,根据不同侧重点,注册中心可以分为 CP 和 AP 两个阵营:
在实际的微服务测试和部署时,通常包含多套环境,比如生产环境一套、测试环境一套。开发在进行业务自测、测试在进行回归测试时,一般都是用测试环境,部署的 RPC Server 节点注册到测试的注册中心集群。但经常会出现开发或者测试在部署时,错误的把测试环境下的服务节点注册到了线上注册中心集群,这样的话线上流量就会调用到测试环境下的 RPC Server 节点,可能会造成意想不到的后果。
为了防止这种情况发生,注册中心需要提供一个保护机制,你可以把注册中心想象成一个带有门禁的房间,只有拥有门禁卡的 RPC Server 才能进入。在实际应用中,注册中心可以提供一个白名单机制,只有添加到注册中心白名单内的 RPC Server,才能够调用注册中心的注册接口,这样的话可以避免测试环境中的节点意外跑到线上环境中去。
在实现无主复制时,有两个关键问题:数据读写和数据修复。数据读写是通过仲裁条件 w + r > n 来保证的,如果满足 w + r > n ,那么读副本和写副本之间就一定有交集,即一定能读取到最新的写入。而数据修复是通过读修复和反熵过程实现的,这两个方法在数据的持久性和一致性方面存在一定的问题,如果对数据有强一致性的要求,就要谨慎采用无主复制。
然后,我们了解了 Sloppy Quorum ,它相比于传统的 Quorum ,为了系统的可用性而牺牲了数据的一致性,这里我们可以进一步得出,无主复制是一个可用性优先的复制模型。
事务(一):一致性,事务的集大成者
事务是一个或多个操作的组合操作,它需要保证这组操作要么都执行,要么都不执行。
事务(二):原子性,对应用层提供的完美抽象
简单介绍了 2PC
事务(三):隔离性,正确与性能之间权衡的艺术
简单介绍了事务隔离级别
事务(四):持久性,吃一碗粉就付一碗粉的钱
简单介绍了 Redo Log + WAL
一致性与共识(一):数据一致性都有哪些级别?
按照一致性强度由高到低,有以下模型:
线性一致性——现在可以实现的一致性级别最强的是线性一致性,它是指所有进程看到的事件历史一致有序,并符合时间先后顺序, 单个进程遵守 program order,并且有 total order。
顺序一致性——它是指所有进程看到的事件历史一致有序,但不需要符合时间先后顺序, 单个进程遵守 program order,也有 total order。
因果一致性——它是指所有进程看到的因果事件历史一致有序,单个进程遵守 program order,不对没有因果关系的并发排序。