目录
- 一、HashSet
- 二、HashMap
- 三、LinkedHashMap
- 四、HashTable
- 五、ConcurrentHashMap
一、HashSet
首先,让我们先从最简单的开始,总的来说,hashSet可以说是建立在hashMap上的变种应用。
通过阅读hashSet的源码我们可以得出以下结论:
1. hashSet的add方法,是巧妙运用了hashMap的key值不可重复的特性;
2. 由于hashMap的可以是可以为null的,所以,hashSet的值支持为null;
3. hashSet不是线程安全的;
4. hashSet的clone方法,是浅复制,即当原set内的元素值改变时,copy的set里的值也改变;
5. 当不指定容量时,hashSet的默认容量时16,负载因子是0.75;
6. 由于hashSet的iterator(迭代器)是fail-fast模式,因此在interator操作时,hashSet发生变化,会导致异常抛出: ConcurrentModificationException;
二、HashMap
由于hashMap的源码比较长,我们就不在这里贴出了,通过阅读hashSet的源码我们可以得出以下结论:
**1. 默认容量是16,最大容量是2^30,默认负载因子0.75,自定义容量时必须是2的幂级;
- hashMap也不是线程安全的,推荐单线程内使用;
- 触发结构“树化”的最小容量是64,当一个支链上的值大于8个时,触发树化,小于6个时退缩成链表;**
4. 关于时间复杂度,如果桶里面没有元素,那么直接将元素插入/或者直接返回未查找到,时间复杂度就是O(1),如果里面有元素,那么就沿着链表进行遍历,时间复杂度就是O(n),链表越短时间复杂度越低,如果是红黑树的话那就是O(logn)。所以平均复杂度很难说,只能说在最优的情况下是O(1)
5. 单向链表形式存储,(node.next指向下一个节点值);
三、LinkedHashMap
linkedHashMap是jdk1.4出现的,是java 集合框架的一员;是hashMap的子类。通过源码阅读,可以获取一下信息:
1. 其结构是双向链表,对hashMap.node做了拓展,通过before和after记录相邻数据;
3. 用head和tail记录最早的和最新的数据;
4. 其默认容量,负载因子等,同hashMap;
5. 多了个accessOrder变量,用于遍历时的输出顺序,默认false,即以插入顺序遍历;
6. 由于linkedHashMap是hashMap的子类,它的树化逻辑同hashMap;
7. 它不是线程安全的;
四、HashTable
阅读hashTable的源码,可知:
- 它是线程安全的,是由于其对外方法加了synchronized修饰词;
- 它的默认容量是11,加载因子是0.75;当初始容量小于0时,抛
IllegalArgumentException; - 它是Dictionary的子类,这一点与hashMap不同,hashMap继承AbstractMap;
- 最大容量2^31-1-8;
- key和value都不允许为null,否则抛NullPointerException;
- 当table中key已经存在,会第二次put的value值放入,并返回第一次的value值;
- clone操作是浅copy,不对keys和values进行复制,且是个十分耗时的操作;
- hashTable里存储的是entry<K,V>数组,且是单向链表形式;
五、ConcurrentHashMap
ConcurrentHashMap是针对hashMap的安全性做的设计和拓展,
因此它具有hashMap的很多特性,比如容量、负载因子、树化等,除此之外,为了保证其线程安全性和拓展性能,ConcurrentHashMap在存储结构上也做了一些改造,通过阅读源码,可以得到以下几点:
- 我们常说,ConcurrentHashMap性能优于hashTable,是因为ConcurrentHashMap的锁是建立在分段锁上的,它以每个链上的第一个值为锁对象进行加锁;
- key和value也是以内部类node节点存储;
- 存储结构做了个比较大的优化,数组+链表+红黑树;
- key和value都不允许为空;
- 在调用putVal时,空链不会加锁,利用cas尝试写入,利用自旋保证写入;
/* ---------------- Fields -------------- */
/**
* The array of bins. Lazily initialized upon first insertion.
* Size is always a power of two. Accessed directly by iterators.
*/
transient volatile Node<K,V>[] table;
/**
* The next table to use; non-null only while resizing.
*/
private transient volatile Node<K,V>[] nextTable;
/**
* Base counter value, used mainly when there is no contention,
* but also as a fallback during table initialization
* races. Updated via CAS.
*/
private transient volatile long baseCount;
/**
* Table initialization and resizing control. When negative, the
* table is being initialized or resized: -1 for initialization,
* else -(1 + the number of active resizing threads). Otherwise,
* when table is null, holds the initial table size to use upon
* creation, or 0 for default. After initialization, holds the
* next element count value upon which to resize the table.
*/
private transient volatile int sizeCtl;
/**
* The next table index (plus one) to split while resizing.
*/
private transient volatile int transferIndex;
/**
* Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
*/
private transient volatile int cellsBusy;
/**
* Table of counter cells. When non-null, size is a power of 2.
*/
private transient volatile CounterCell[] counterCells;
// views
private transient KeySetView<K,V> keySet;
private transient ValuesView<K,V> values;
private transient EntrySetView<K,V> entrySet;