集合框架总结
我们常用的集合框架有Map集合(HashMap、HashTable、TreeMap)、List集合(ArrayList、LinkedList)、Set集合(HashSet)
常用的并发集合框架有ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteLinkedList等
一一解析:
Map集合存放key-value结构数据,常用的有HashMap、HashTable、TreeMap
HashMap
数据结构:数组+链表+红黑树
hashmap底层是一个Entry[]数组,数组中存放的是一个Node链表;当put(key,value),一对key,value到hashMap中时,先求key的hash值,找到对应的buket,若该key已存在则替换原来的值,若发生hash碰撞,则将对应的value存到链表中,当链表长度达到8且数组长度达到64时链表转化为红黑树,否则容器扩容。
何时扩容,如何扩容:
HashMap的负载因子是0.75,既数组中的值占全部数组容量的75%时就会扩容,或者容器小于64但其中有链表的长度达到8时也会扩容。扩容为原来容器的两倍。
hashmap的扩容会对链表上的元素重新进行hash计算,标记元素应该是高位还是低位,低位元素依次放在原位置的buket上,高位元素依次放在放在原位置+扩容量的buket上。
特性问题
当增加元素时,链表查长度达到8时转换为红黑树,当删除元素时,元素红黑树上元素的数量减少到6是重新转换为链表。链表的查询时间复杂度是O(n),红黑树的时间复杂度是O(lgn)。绘时间复杂度的图便很容易看到,长度为6时,数组效率高于红黑树,长度为8时红黑树效率高于数组。在考虑到转换耗时,所以定在这两个数字做转换。
size的计算:
hashmap中有一个属性值size记录元素个数。新增元素时++size
HashTable既是在HashMap的部分方法上加上synchronized关键是,使其转变为线程安全的HashMap。
TreeMap的底层数据结构是红黑树,所以存入其中的集合遍历时是有序的。
List集合常用的有ArrayList、LinkedList
ArrayList集合底层是一个数组,所以查询效率高,增删效率低,
当数组满的时候扩容,每次扩展为原来的1.5倍。{
newCapacity = oldCapacity + (oldCapacity >> 1);
}。ArrayList中有一个属性值size记录元素个数。插入时先判断是否有足够的空间保存元素,不够就扩容。可以承载的最大元素个数为Integer.MAX_VALUE - 8
LinkedList底层是一个双向链表结构,所以其增删效率高查询效率低。
Set集合常用的有HashSet、TreeSet
HashSet集合底层其实使用HashMap实现的,它利用HashMap的key不能重复的特性,用key存储数据,并将所有的value都是设为null
常用的并发集合框架解析
ConcurrentHashMap是java1.5新增的并发集合框架,它采用了分段锁的设计,初始化时,生成16个segment,每个segment内都有一个类似hashmap的hashEntry。只有在同一个分段内的数据才存在竞态关系,不同的分段锁之间没有锁竞争。所以理论上,ConcurrentHashMap最多可以允许16个线程并发put数据到不同的segment中。
当put一个key-value到ConcurrentHashMap中时,先对key进行hash计算找到其对应的segment,再将key-value存入对应的hashentry中。
相比于对整个Map加锁的设计,分段锁大大的提高了高并发环境下的处理能力。但同时,由于不是对整个Map加锁,导致一些需要扫描整个Map的方法(如size(), containsValue())需要使用特殊的实现,另外一些方法(如clear())甚至放弃了对一致性的要求(ConcurrentHashMap是弱一致性的。)
当统计ConcurrentHashMap包含多少个值时,先后对ConcurrentHashMap统计两次,若两次统计的结果一致,既两次统计的过程中mod值没有改变则返回统计结果;如果两次统计的值不一致,则锁定整个ConcurrentHashMap,for循环统计其size。
CopyOnWriteArrayList和CopyOnWriteLinkedList
它们分别是基于Copy-On-Write容器的ArrayList和LinkedList。CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
它适用于读多写少的情况。具体详情参考:Java中的Copy-On-Write容器