- 前置知识: Java基础 集合类基础(jdk1.8)
Map(字典)
该接口不基于Collection
HashMap/LinkedHashMap/TreeMap比较
HashMap | LinkedHashMap | TreeMap | ||
---|---|---|---|---|
继承 | 父接口 | Map | Map | NavigableMap1 |
父类 | AbstractMap | HashMap | AbstractMap | |
数据存储 | 底层结构 | 数组+(链表/红黑树) | 同HashMap+双向链表 | 红黑树 |
复杂度 | 插入 | O(1) | 同HashMap | O(lgN) |
删除 | O(1) | 同HashMap | O(lgN) | |
查找 | O(1) | 同HashMap | O(lgN) | |
有序性 | 迭代顺序 | / | 插入顺序/访问顺序 | 自然序/自定义 |
支持Navigate | 否 | 同HashMap | 是 | |
哈希 | 哈希函数 | 基于HashCode()高/低位2 | 同HashMap | / |
桶定位法 | 位运算3 | 同HashMap | / | |
冲突处理 | 转换成链表/红黑树4 | 同HashMap | / | |
扩容机制 | 初始容量 | 163 | 同HashMap | / |
最大容量 | 1 << 30(2^31 ) | 同HashMap | / | |
负载因子 | 0.755 | 同HashMap | / | |
扩容策略 | 2倍3 | 同HashMap | / | |
扩容时机 | 插入后6 | 同HashMap | / | |
具体操作 | 在新数组中重排所有元素 | 同HashMap | / | |
保持迭代顺序 | 否7 | 同HashMap | / | |
序列化 | 典型优化 | 跳过数组null位置 | 同HashMap8 | / |
transient9 | size/modCount10; table/entrySet; | 同HashMap; head/tail8; | size/modCount; root11; | |
同步 | 线程安全 | 否 | 同HashMap | 否 |
final | Entry.hash/key | 同HashMap | 无 |
Set(集合)
该接口基于Collection
HashSet/LinkedHashSet/TreeSet比较
Set实现内部使用了Map实现,所以其特性和Map对应项类似
List(列表)
该接口基于Collection
ArrayList/LinkedList/Vector比较
ArrayList | LinkedList | Vector | ||
---|---|---|---|---|
继承 | 父接口 | List/RandonAccess | List/Deque | List/RandonAccess |
父类 | AbstractList | AbstractSequentialList | AbstractList | |
数据存储 | 底层结构 | 数组 | 双向链表12 | 数组 |
复杂度 | 插入 | O(N) | O(1) | O(N) |
删除 | O(N) | O(1) | O(N) | |
查询 | O(1) | O(N) | O(1) | |
容量 | 初始容量 | 10 | 0 | 10 |
最大容量 | Integer.Max | Integer.Max | Integer.Max | |
扩容 | 时间点 | 插入前 | / | 插入前 |
策略 | 1.5倍 | / | 默认为2倍 | |
主要耗时点 | 拷贝数组13 | / | 拷贝数组 | |
同步 | 线程安全 | 无 | 无 | 是(synchronized ) |
序列化 | 优化策略 | 跳过数组空白的尾部 | / | 无 |
transient | elementData; | size; first/last; | 无14 |
Queue/Deque(队列/双端队列)
该接口基于Collection
LinkedList/PriorityQueue/ArrayDeque比较
LinkedList | PriorityQueue | ArrayDeque | ||
---|---|---|---|---|
继承 | 父接口 | List/Deque | Queue | Deque |
父类 | AbstractSequentialList | AbstractQueue | AbstractCollection | |
数据存储 | 数据结构 | 列表/双端队列 | 优先队列 | 双端队列 |
底层结构 | 双向链表 | 最小堆/最大堆15(数组) | 循环数组 | |
Usage | 插入null元素 | 支持 | 不支持16 | 不支持17 |
有序性 | 出队有序性 | / | 自然序/自定义 | / |
迭代有序性 | / | 无 | / | |
复杂度 | 插入 | O(1) | O(lgN) | O(N) |
删除 | O(1) | O(lgN) | O(N) | |
查询 | O(N) | O(lgN) | O(1) | |
入队 | O(1) | O(lgN) | O(1) | |
出队 | O(1) | O(lgN) | O(1) | |
扩容 | 初始容量 | / | 11(1+2+4+4) | 8 |
最小容量 | / | 218 | 8 | |
时间点 | / | 插入前 | 插入后 | |
判断条件 | / | index >= queue.length | head==tail | |
策略 | / | 新容量<=64为2倍,否则为1.5倍 | 2倍 | |
序列化 | 优化策略 | / | 删除空白位置,但size不小于2 | 删除空白位置 |
transient | size; first/last; | queue; modCount; | elements; head/tail; | |
同步 | 线程安全 | 否 | 否 | 否 |
Reference
- Java 8系列之重新认识HashMap
- 深入理解哈希表
- Java 核心技术点之集合框架
- LinkedHashMap
- List 总结
- 【集合框架】JDK1.8源码分析HashSet && LinkedHashSet(八)
- JDK中优先级队列PriorityQueue实现分析
- 通过
NavigableMap
(扩展自SortedMap
)接口,程序可以通过Entry
之间的大小顺序,访问某个Entry
相邻的Entry
↩ - 一共两次哈希:第一次:Object.hashCode()返回32位整数;地二次:对第一次哈希值的低位和高位做乘方运算,保证所有数字都参与计算,防止Hash聚集现象 ↩
- 定位元素位于哪个bucket中一般使用求模运算
index = hash % length
,HashMap中使用较之更快的等效位运算index = hash & (length - 1)
,条件是数组length必须满足2^n .受影响参数包括初始容量/最大容量/扩容策略.使用位运算的代价是:如果length为素数,HashMap不容易产生Hash冲突,但这是一个trade-off ↩ - since jdk1.8,当元素过于集中在一个bucket时,由链表转成红黑树;反之由红黑树转成链表 ↩
-
loadFactor>0
即可;负载因子越大,同样内存HashMap能容纳元素越多,Hash冲突可能性越大,各个操作的复杂度越大 ↩ - 插入后检测扩容比插入前要差,无法避免无谓的扩容(即之后不在put元素的场景) ↩
- 由于resize后各个元素的hash值可能发生变化,所以无法保证迭代器遍历的顺序,主要体现在在原数组中靠前的元素在新数组中靠后,而且如果假设hash函数是平均分布的话,该种情况出现的概率为50%(eg.元素hash=31,old_array.length=16,new_array.length=32,元素位置从15变成31).有趣的是,jdk1.8的HashMap利用了这种现象来降低resize时重新计算元素位置的开销 ↩
-
head
/tail
为双向链表的头结点和尾节点,由于使用其父类的序列化方法,因此反序列之后需要重新生成双向链表,这是在新建节点的newNode()
中实现的;访问/插入/删除方法中对双向链表的操作会通过Override父类的Hook方法实现 ↩ -
transient
修饰的变量不会被Java默认的序列化器处理,需要程序自己OverloadwriteObject()
和readObject()
方法 ↩ -
modCount
记录结构变化的次数(eg.插入新元素/删除老元素) ↩ - 红黑树的根节点 ↩
- 讲道理是可以用单向链表的,但是由于该类被官方推荐来模拟Stack/Queue/Deque,因此使用了双向链表,该类能够毫不费力的兼容这些数据结构 ↩
- 执行拷贝数组的方法是
Arrays.copy()
,底层native方法是arraycopy()
,对数组元素的拷贝都是浅拷贝.可以用简单的实验表明:当原数组的元素数量超过10^6 时,耗时超过1ms;当原数组元素数量超过10^7 时,耗时超过5ms ↩ - 由于历史原因
Vector
(since jdk1.0)没有对序列化做优化,数组尾部的空白片段依然会被保留,官方也推荐使用更新的集合类(since jdk1.2)来代替它 ↩ - 默认是二叉最小堆(默认的
comparator
来自于SortedSet
) ↩ - null元素是被删除的元素留下的空位置/还没有添加元素的空位置,是个重要的
remove()
判断条件,所以不能插入null元素 ↩ - 原因类似
PriorityQueue
,循环数组中中间有一段是null的,null是数组中的空位置 ↩ - 该最小容量是由序列化
writeObject()
方法保证,保证树二叉堆至少有两层 ↩