【Java】jdk1.8集合类特性综述及横向比较

  • 前置知识: 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

  1. Java 8系列之重新认识HashMap
  2. 深入理解哈希表
  3. Java 核心技术点之集合框架
  4. LinkedHashMap
  5. List 总结
  6. 【集合框架】JDK1.8源码分析HashSet && LinkedHashSet(八)
  7. JDK中优先级队列PriorityQueue实现分析

  1. 通过NavigableMap(扩展自SortedMap)接口,程序可以通过Entry之间的大小顺序,访问某个Entry相邻的Entry
  2. 一共两次哈希:第一次:Object.hashCode()返回32位整数;地二次:对第一次哈希值的低位和高位做乘方运算,保证所有数字都参与计算,防止Hash聚集现象 ↩
  3. 定位元素位于哪个bucket中一般使用求模运算index = hash % length,HashMap中使用较之更快的等效位运算index = hash & (length - 1),条件是数组length必须满足2^n .受影响参数包括初始容量/最大容量/扩容策略.使用位运算的代价是:如果length为素数,HashMap不容易产生Hash冲突,但这是一个trade-off ↩
  4. since jdk1.8,当元素过于集中在一个bucket时,由链表转成红黑树;反之由红黑树转成链表 ↩
  5. loadFactor>0即可;负载因子越大,同样内存HashMap能容纳元素越多,Hash冲突可能性越大,各个操作的复杂度越大 ↩
  6. 插入后检测扩容比插入前要差,无法避免无谓的扩容(即之后不在put元素的场景) ↩
  7. 由于resize后各个元素的hash值可能发生变化,所以无法保证迭代器遍历的顺序,主要体现在在原数组中靠前的元素在新数组中靠后,而且如果假设hash函数是平均分布的话,该种情况出现的概率为50%(eg.元素hash=31,old_array.length=16,new_array.length=32,元素位置从15变成31).有趣的是,jdk1.8的HashMap利用了这种现象来降低resize时重新计算元素位置的开销 ↩
  8. head/tail为双向链表的头结点和尾节点,由于使用其父类的序列化方法,因此反序列之后需要重新生成双向链表,这是在新建节点的newNode()中实现的;访问/插入/删除方法中对双向链表的操作会通过Override父类的Hook方法实现 ↩
  9. transient修饰的变量不会被Java默认的序列化器处理,需要程序自己OverloadwriteObject()readObject()方法 ↩
  10. modCount记录结构变化的次数(eg.插入新元素/删除老元素) ↩
  11. 红黑树的根节点 ↩
  12. 讲道理是可以用单向链表的,但是由于该类被官方推荐来模拟Stack/Queue/Deque,因此使用了双向链表,该类能够毫不费力的兼容这些数据结构 ↩
  13. 执行拷贝数组的方法是Arrays.copy(),底层native方法是arraycopy(),对数组元素的拷贝都是浅拷贝.可以用简单的实验表明:当原数组的元素数量超过10^6 时,耗时超过1ms;当原数组元素数量超过10^7 时,耗时超过5ms ↩
  14. 由于历史原因Vector(since jdk1.0)没有对序列化做优化,数组尾部的空白片段依然会被保留,官方也推荐使用更新的集合类(since jdk1.2)来代替它 ↩
  15. 默认是二叉最小堆(默认的comparator来自于SortedSet) ↩
  16. null元素是被删除的元素留下的空位置/还没有添加元素的空位置,是个重要的remove()判断条件,所以不能插入null元素 ↩
  17. 原因类似PriorityQueue,循环数组中中间有一段是null的,null是数组中的空位置 ↩
  18. 该最小容量是由序列化writeObject()方法保证,保证树二叉堆至少有两层 ↩

你可能感兴趣的:(java)