Java集合框架包括很多集合类:
List: ArrayList, LinkedList, Vector, Stack
Set: HashSet, LinkedHashSet, TreeSet
Queue: PriorityQueue, ArrayDeque
Map: HashMap, LinkedHashMap, TreeMap, HashTable, WeakHashMap
并发
List: CopyOnWriteArrayList
Set: CopyOnWriteArraySet, ConCurrentSkipListSet
Queue: ArrayBlockingQueue, LinkedBlockingQueue, DelayQueue, PriorityBlockingQueue, SynchronousQueue, ConcurrentLinkedQueue
Map: ConcurrentHashMap, ConcurrentSkipListMap
Collections: synchronizedList, synchronizedMap, synchronizedSet, synchronizedSortedMap, synchronizedSortedSet, synchronizedNavigableMap, synchronizedNavigableSet
下面介绍各个类的具体内容。
①ArrayList
底层实现:数组
初始容量:10
扩容:1.5倍。因为如果一次扩容太大,可能会浪费更多的内存,而如果一次性扩容太小,需要多次对数组重新分配内存,对性能消耗比较严重。
modCount作用:在多线程操作过程中发现modCount不一致并抛出错误,即fail-fast机制。
注意:数组存放的是元素的引用,而不是元素本身。
②LinkedList
底层实现:双向链表
③Vector
底层实现:数组
初始容量:10
扩容:2倍。可指定扩容增量。
线程安全
对于 Vector 整个的扩容过程,就是根据 capacityIncrement 确认扩容大小的,若 capacityIncrement <= 0 则扩大一倍,否则扩大至 capacityIncrement 。当然这个容量的最大范围为 Integer.MAX_VALUE即,2^32 – 1,所以 Vector 并不是可以无限扩充的。
④stack
底层实现:继承Vector
线程安全
⑤HashMap
底层实现:数组+列表
初始容量:16
默认加载因子:0.75
扩容:2倍
线程不安全
容量表示哈希表中桶的数量,初始容量是创建哈希表时的容量,加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。
查找一个元素的平均时间是 O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。
HashMap 保存数据的过程为:当我们向一个 HashMap 中添加一对 key-value 时,系统首先会计算 key 的 hash 值,然后根据 hash 值确认在 table 中存储的位置。若该位置没有元素,则直接插入。否则迭代该处元素链表并依此比较其 key 的 hash 值。如果两个 hash 值相等且 key 值相等 (e.hash == hash && ((k = e.key) == key || key.equals(k))),则用新的 Entry 的 value 覆盖原来节点的 value。如果两个 hash 值相等但 key 值不等 ,则将该节点插入该链表的链头。
HashMap的初始容量总设为2的n次方, 是因为当 length = 2^n 时,不同的 hash 值发生碰撞的概率比较小,这样就会使得数据在 table 数组中分布较均匀,查询速度也较快。
HashMap的数组table用了transient修饰,也就是说table里面的内容全都不会被序列化,因为HashMap是基于HashCode的,HashCode作为Object的方法,是native的,这意味着的是,HashCode和底层实现相关,不同的虚拟机可能有不同的HashCode算法,Java采取了重写自己序列化table的方法,在writeObject选择将key和value追加到序列化的文件最后面,而在readObject的时候重构HashMap数据结构。
⑥HashSet
底层实现:基于HaspMap
HashSet 中的所有元素都是保存在 HashMap 的 key 中,value 则是使用的 PRESENT 对象,该对象为 static final。
⑦HashTable
底层实现:
初始容量:11
加载因子:0.75
扩容:2倍+1
线程安全
put 方法的整个处理流程是:计算 key 的 hash 值,根据 hash 值获得 key 在 table 数组中的索引位置,然后迭代该 key 处的 Entry 链表(我们暂且理解为链表),若该链表中存在一个这个的 key 对象,那么就直接替换其 value 值即可,否则在将改 key-value 节点插入该 index 索引位置处。
HashMap和HashTable的区别:
(1)HashMap是非线程安全的,Hashtable所有对外提供的方法都使用了synchronized,也就是同步,HashTable是线程安全的。
(2)HashMap允许key和value可以为null,而HashTable不允许。
(3)由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。
(4)HashMap不能保证随着时间的推移Map中的元素次序是不变的。
(5)HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。
(6)容量和扩容大小都不一致。
⑧LinkedHashMap
底层实现:散列表+双向链表
非线程安全
LinkedHashMap 是 HashMap 的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用 LinkedHashMap。
⑨CopyOnWriteArrayList
顾名思义,Write的时候总是要Copy,也就是说对于CopyOnWriteArrayList,任何可变的操作(add、set、remove等等)都是伴随复制这个动作的。
·底层实现:数组
·是否允许为空:允许
·是否允许重复数据:允许
·是否有序:有序
·是否线程安全:线程安全
一次add大致经历了几个步骤:
1、加锁
2、拿到原数组,得到新数组的大小(原数组大小+1),实例化出一个新的数组来
3、把原数组的元素复制到新数组中去
4、新数组最后一个位置设置为待添加的元素(因为新数组的大小是按照原数组大小+1来的)
5、把Object array引用指向新数组
6、解锁
插入、删除、修改操作都是一样,每一次的操作都是以对Object[] array进行一次复制为基础的。CopyOnWriteArrayList的缺点,就是修改代价十分昂贵,每次修改都伴随着一次的数组复制。CopyOnWriteArrayList适用于读操作远多于修改操作的并发场景中。
⑩TeeMap
增删改查log(n)
统计性能long(n)
非线程安全
key不允许为空,value允许为空
key重复会覆盖,value允许重复