最近写代码,感觉对jdk中Collection和Map中的实现方式和原理有些遗忘,为了在写代码的时候让代码更加简介和让性能更加优秀,所以我先必须了解其中容器增长方式和原理,HashMap,如果每个Node桶存放的数据大小大于等于8,就会采用红黑树存储。
工具:starUML、IDEA
jdk版本:jdk1.8.0_102
(一) 先贴上一张类图:
(二) 其中Collection和Map部分实现中实现参数。
1、 全局实现的参数:
新容量大小/原容器大小 = N/O
x = 没有该值
Collection | |||||||||
List | Set | ||||||||
ArrayList | AttributeList | RoleList | LinkedList | Vector | Stack | HashSet | TreeSet | LinkedHashSet | |
实现方式 | Object[] 数组 |
Object[] 数组 |
Object[] 数组 |
Node<E> 双向链表 |
Object[] 数组 |
Object[] 数组 |
HashMap<E,Object> |
NavigableMap<E, Object> | HashMap<E,Object> |
默认容器大小 | 10 | 10 | 10 | x | 10 | 10 | 16 | x | 16 |
扩充容量方式 | System.arraycopy | System.arraycopy | System.arraycopy | x | System.arraycopy | System.arraycopy | (Node<K, V>[])new Node[newCap] | x | (Node<K, V>[])new Node[newCap] |
N/O | 1.5 | 1.5 | 1.5 | x | 2(默认,值在初始化的第二个参数可设置) | 2默认,值在初始化的第二个参数可设置) | 2 | x | 2 |
负载因子 | 1 | 1 | 1 | x | 1 | 1 | 0.75f | x | 0.75f |
默认门阀 | 记载因子*容量大小 | 记载因子*容量大小 | 记载因子*容量大小 | x | 记载因子*容量大小 | 记载因子*容量大小 | 如果使用无参构造方式,默认门阀为“默认加载因子”*"默认cap大小". 如果使用其他构造函数,门阀值初始为 将该初始因子-1的值写成机器码,从最高位(从左往右除符号位的第一个1)开始,右边所有位变1的值。 |
x | 如果使用无参构造方式,默认门阀为“默认加载因子”*"默认cap大小". 如果使用其他构造函数,门阀值初始为 将该初始因子-1的值写成机器码,从最高位(从左往右除符号位的第一个1)开始,右边所有位变1的值。 |
线程安全 | 否 | 否 | 否 | 否 | 是 | 是 | 否 | x | 否 |
Map | |||||||||
HashMap | LinkedHashMap | Hashtable | WeakHashMap | TreeMap | EnumMap | Attributes | ConcurrentHashMap | Properties | |
实现方式 | Node<K, V>[] (根据key的hash值和长度来决定选择那个桶index)有hashValue,next的链表结构(这里面定义了TreeNode extend LinkedHashMap.Entry) |
LinkedHashMap.Entry<K, V> 双向链表实体,继承HashMap.Node<K, V> | Entry<K,V>[] 是HashMap的安全实现 |
Entry<K,V>[] 是继承WeakReference实现弱引用,减少在内存中存活时间,节省内存。 |
Entry<K, V> 有right,left,parent的map结构 |
Object [] 数组 |
Map<Object, Object>,使用的HashMap实现 | Node<K,V>[] 数组 |
Entry<K,V>[] 是HashMap的安全实现,是继承于HashTable |
默认容器大小 | 16 | x | 11 | 16 | x | 动态 | 11 | 16 | 11 |
扩充容量方式 | (Node<K, V>[])new Node[newCap] | x | new Entry<?, ?>[newCapacity] | (Node<K, V>[])new Node<?, ?>[newCap] | x | x | (Node<K, V>[])new Node[newCap] | (Node<K, V>[])new Node<?, ?>[n << 1] | new Entry<?, ?>[n] |
N/O | 2 | x | 2 另外加1 | 2 | x | x | 2 | x | 2 另外加1 |
负载因子 | 0.75f | x | 0.75f | 0.75f | x | x | 0.75f | x | 0.75f |
默认门阀 | 如果使用无参构造方式,默认门阀为“默认加载因子”*"默认cap大小". 如果使用其他构造函数,门阀值初始为 将该初始因子-1的值写成机器码,从最高位(从左往右除符号位的第一个1)开始,右边所有位变1的值。 |
x | 记载因子*容量大小 | 记载因子*容量大小 | x | x | 如果使用其他构造函数,门阀值初始为 将该初始因子-1的值写成机器码,从最高位(从左往右除符号位的第一个1)开始,右边所有位变1的值。 |
x |
记载因子*容量大小 |
线程安全 | 否 | 否 | 是 | 否 | 否 | 否 | 否 | 是 | 是 |
2、线程安全构造函数和门阀与N/O的关系详细说明:
新容量大小/原容器大小 = N/O
新门阀/老门阀 = NT/OT
x = 不存在
i = 输入值
oldCap = 老容器大小
oldCap/2(取整) = oldCap >> 1
{2^n}= 满足2的n次方值大于i条件2的n次方的最小值
动态 = 表示它没有实现,需要传入一个实现的对象,按照对象来使用
1) 线程安全:
Vector | Stack(继承与Vector) | |||||||||
初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | |
默认容器大小 | 10 | x | x | 2 | x | 10 | x | x | 2 | x |
只有initialCapacity参数值 | i | x | x | 2 | x | x | x | x | x | x |
有initialCapacity,loadFactor参数的值 | i | x | x | 这儿没有loadFactor参数,有capacityIncrement参数,每次增加capacityIncrement | x | x | x | x | x | x |
Stack(继承与Vector) | Hashtable | ConcurrentHashMap | Properties | |||||||||||||
NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | |
默认容器大小 | x | 11 | 0.75f | 8 | 2 倍另外单独+1 | 2 | 16 | x | x | 1.5倍另外单独 + 1 | x | 11 | 0.75f | 8 | 2 倍另外单独+1 | 2 |
只有initialCapacity参数值 | x | i | 0.75f | i * 0.75f |
2 倍另外单独+1 | 2 | 3i/2 + 1 | x | 1.5倍另外单独 + 1 | x | i | 0.75f | i * 0.75f |
2 倍另外单独+1 | 2 | |
有initialCapacity,loadFactor参数的值 | x | i | i | i(容量) *i(负载因子) | 2 倍另外单独+1 | 2 | {2^i/loadFactor} | x | x | 1.5倍另外单独 + 1 | x | i | i | i(容量) *i(负载因子) | 2 倍另外单独+1 | 2 |
2)线程不安全:
ArrayList | AttributeList | RoleList | LinkedList | |||||||||||||||||
初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | |
无参构造函数容器大小 | 10 | x | oldCap | 1.5 | x | 10 | x | oldCap | 1.5 | x | 10 | x | oldCap | 1.5 | x | x | x | x | x | x |
只有initialCapacity参数值 | i | x | oldCap | 1.5 | x | i | x | oldCap | 1.5 | x | i | x | oldCap | 1.5 | x | x | x | x | x | x |
有initialCapacity,loadFactor参数的值 | i | x | oldCap | 1.5 | x | i | x | oldCap | 1.5 | x | i | x | oldCap | 1.5 | x | x | x | x | x | x |
HashSet(数据保存是通过HashMap实现的) | TreeSet | LinkedHashSet(继承HashSet) | HashMap | |||||||||||||||||
初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | |
无参构造函数容器大小 | 16 | 0.75f | 12 | 2 | 2 | x | x | x | x | x | 16 | 0.75f | 12 | 2 | 2 | 16 | 0.75f | 12 | 2 | 2 |
只有initialCapacity参数值 | {2^n} | 0.75f | {2^n} * 0.75f |
2 | 2 | x | x | x | x | x | {2^n} | 0.75f | {2^n} * 0.75f |
2 | 2 | {2^n} | 0.75f | {2^n} * 0.75f |
2 | 2 |
有initialCapacity,loadFactor参数的值 | {2^n} | i | {2^n} * 0.75f |
2 | 2 | x | x | x | x | x | {2^n} | i | {2^n} * 0.75f |
2 | 2 | {2^n} | i | {2^n} * 0.75f |
2 | 2 |
LinkedHashMap | WeakHashMap | TreeMap | EnumMap | Attributes(数据用HashMap来保存) | ||||||||||||||||||||
初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT | 初始容量 | 负载因子 | 门阀 | N/O | NT/OT |
16 | 0.75f | 12 | 2 | 2 | 16 | 0.75f | 12 | 2 | 2 | x | x | x | x | x | 动态 | 动态 | 动态 | 动态 | 动态 | 16 | 0.75f | {2^n} * 0.75f |
2 | 2 |
i | 0.75f | {2^n} * 0.75f |
2 | 2 | {2^n} | 0.75f | {2^n} * 0.75f |
2 | 2 | x | x | x | x | x | 动态 | 动态 | 动态 | 动态 | 动态 | {2^n} | 0.75f | {2^n} * 0.75f |
2 | 2 |
i | i | {2^n} * 0.75f |
2 | 2 | {2^n} | i | {2^n} * 0.75f |
2 | 2 | x | x | x | x | x | 动态 | 动态 | 动态 | 动态 | 动态 | x | x | x | x |
补充描述:这里面有些很复杂,详细还是得去研究源码,而且,每个jdk版本的实现的方式不一样,建议有时间详细看源码,了解其中原理。最重要的是HashMap,TreeMap,Hashtable,ConcurrentHashMap这些详细查看很有用, 这个其中还常使用到CAS。
(三) 知识点总结:
HashMap,虽然存储数据是通过key-value的形式存储的,但是,它内部为了提高查询效率,使用的key的hash值,构建数组对象链。先是数组,称作“桶”,通过hash值来判断放那个桶,桶的数量就是上面的cap。每个桶存放的数据小于8个的时候,是采用简单的对象表,如果每个Node桶存放的数据大小大于等于8,针对该桶的数据就会采用红黑树存储。
ConcurrentHashMap在这个版本实现和HashMap内容差不多,只是增加了线程安全的操作,其中加载因子只是在容器初始化的时候和cap通过cap/loadFactor + 1向上取2^n的整数,还有这个版本Segment只有在JAVA对象流进行序列化和反序列化中使用。