HashMap源码分析
分析1.7、1.8的HashMap、ConcurrentHashMap的区别
越到后面越烦躁,写不下去了,到时回来填坑把
1. 补充位运算
位运算是对2进制而言的
符号 | 描述 | 运算规则 |
---|---|---|
& | 与 | 两个都为1,结果才为1 |
| | 或 | 两个都为0,结果才是0 |
^ | 异或 | 同0异1 |
~ | 取反 | 0变1,1变0 |
<< | 左移 | 各二进位全部左移若干位,高位丢弃,低位补0。表示2次幂 |
>> | 右移 | 各二进位全部右移若干位,对无符号数,高位补0。表示除2 |
有符号数,各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移) | ||
>>> | 逻辑右移 | 不管符号位,直接右移,补零 |
1.1 位运算实现取模
// 其实很简单,主要看length
// &运算,就算h全部为1,&之后都是看length有1的部分
// 那么最大只能是length,所以范围限定在了length里,比 % 运算快多了
// -1为了符合数组0开始
// 这也是扩容为2次幂的原因,配套取模运算
h & (length-1)
1.2 二次幂
二次幂的二进制只有一个1,其他位为0
如果减1,那么二进制中的1变成0,后面的0全部变成1,符合上面的length,配合实现取模运算
1---0001
2---0010
4---0100
8---1000
2. JDK 1.7
使用头插法,链表放头部是最快的,尾部需要遍历的
真要put的才初始化,体现懒加载
2.1 构造函数
public HashMap(int initialCapacity, float loadFactor) {
// 参数的各种判断
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
// 设置加载因子,体现懒加载
this.loadFactor = loadFactor;
// 这里注意讲初始化容量大小,赋值给阀值,后面扩容表用到
// 其实也就是扩容大小,不过为什么用阀值赋值??
threshold = initialCapacity;
// 真正的初始化
init();
}
2.2 二次幂
// 总之返回一个大于但最接近number的二次幂
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
// -1保证本身不是2次幂,左移保证大于大于当前2次幂,而小于下一个2次幂
? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
// 返回小于等于的2次幂
public static int highestOneBit(int i) {
// 多次右移再异或保证最高位的1后面全是1
i |= (i >> 1);
i |= (i >> 2);
i |= (i >> 4);
i |= (i >> 8);
i |= (i >> 16);
// 这里保证除了最高位1以为,其余全部变成0了
return i - (i >>> 1);
}
2.3 扩容表
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);
// 和最大容量比咯,选择小的。。。
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
// 创建哈希表的桶子
table = new Entry[capacity];
// 是否再哈希
initHashSeedAsNeeded(capacity);
}
2.4 再哈希
// 正常来说都不用
final boolean initHashSeedAsNeeded(int capacity) {
// 哈希种子,默认0,返回false
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
// 最关键在这里,这里要容量允许的最大值时才为true
// Holder里面从JVM环境配置拿看有没有设置自定义的最大容量
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
// false false
boolean switching = currentAltHashing ^ useAltHashing;
// false,意思不用再hash
if (switching) {
hashSeed = useAltHashing ? sun.misc.Hashing.randomHashSeed(this) : 0;
}
return switching;
}
2.5 哈希值
// 注意:根据Key来获取hash值的
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
// 根据Key的哈希值再和哈希散列
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
2.6 获取下标
static int indexFor(int h, int length) {
// 取模,必须确保2次幂
return h & (length-1);
}
2.7 get方法
public V get(Object key) {
// 当然首先判断是不是空,空就调用专门对NULL的方法,1.7有专门分开
if (key == null) return getForNullKey();
// 否则调用正常流程
Entry<K,V> entry = getEntry(key);
// Entry是从Tree里面继承的,K/V结构
return null == entry ? null : entry.getValue();
}
// 看看NULL的实现
private V getForNullKey() {
// 如果空表,返回null把
if (size == 0) {
return null;
}
// null 是固定放在0号下标的
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
// 正常流程
final Entry<K,V> getEntry(Object key) {
// 当然也有非空判断
if (size == 0) {
return null;
}
// 这里的null可能是直接调用,不用通过get的把
// 比如containsKey:return getEntry(key) != null;
int hash = (key == null) ? 0 : hash(key);
// 流程才是重点
// 1.根据Key的哈希值获取桶子下标,然后遍历该桶子上的元素进行判断
// 2.判断流程:
// 2.1 判断元素的哈希值
// 2.2 再判断Key是否地址相同(比如String有常量池),或者值相同
for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
2.8 put方法
// 也是注意流程
// 1.根据Key获取桶下标进行遍历,有相同的就替换
// 2.没有相同则插入
// 2.2
public V put(K key, V value) {
// 体现懒加载,添加元素才去看看初始化没
if (table == EMPTY_TABLE) {
// 前面函数有说明,扩容表
// 看这里传进入的是阀值,也就是第一次赋值时传进去的容量大小
inflateTable(threshold);
}
// 看来为空都有特殊对待
if (key == null) return putForNullKey(value);
// 获取hash值,以及映射对应的获取桶子下标
int hash = hash(key);
int i = indexFor(hash, table.length);
// 链表遍历咯,看是否有相同的,相同则替换
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 步骤都差不多
// 第一步比hash,第二步比key地址,第三步比key值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
// 如果已经存在值了,那么替换,返回旧值
V oldValue = e.value;
e.value = value;
// 这里是空,主要给LinkedHashMap记录访问顺序的
e.recordAccess(this);
// 返回旧值
return oldValue;
}
}
// 快速失败机制
modCount++;
// 没有重复的值,那么就插入
addEntry(hash, key, value, i);
return null;
}
2.8.1 插入空值流程
// 所以这里存在的就是为了减少运算??
// 直接从0下标遍历,麻烦
private V putForNullKey(V value) {
// 区别在于直接从0下标遍历,因为NULL只存0下标
// 也是先遍历,看有没有存在替换,没有就插入
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 少不了的套路,快速失败
modCount++;
// 这里才是真正的插入方法
addEntry(0, null, value, 0);
return null;
}
2.8.2 插入非NULL值流程
// 这里还是添加Entry的判断(判断条件也挺重要的)
void addEntry(int hash, K key, V value, int bucketIndex) {
// 判断阀值,且该桶子上不为空
// 为空就不扩容,为了节省了一次扩容消耗???
// 这里是1.7的特点,目标下标为空可以直接插入,不进行扩容
if ((size >= threshold) && (null != table[bucketIndex])) {
// 扩容后面讲,知道这里2倍就是了
// 扩容完,当然还要记得插入元素
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
// 正真的头插入过程
createEntry(hash, key, value, bucketIndex);
}
2.8.3 真正的插入元素
// 1.7 使用头插法,并发会出现死循环,1.8 使用尾插法
// 下面是头插法的流程,慢慢看,可能有点难懂
void createEntry(int hash, K key, V value, int bucketIndex) {
// 先获取桶子指定下标桶的元素
Entry<K,V> e = table[bucketIndex];
// 然后将桶子上的元素换成将要插入的元素
// 被顶掉的元素放入,插入元素的next属性上就OK了
table[bucketIndex] = new Entry<>(hash, key, value, e);
// 最后别忘实际存放的元素数量+1
size++;
}
2.9 再散列(扩容)
// 扩容理由:
// 桶子不够,哈希冲突过多
// 链表过长,影响get遍历效率
// 注意流程:
// 1.建新表,两倍大小
// 2.数据转移
// 2.1 遍历桶子的同时,遍历桶子上的链表
// 2.2 然后逐个拆分链表元素,再移动新旧表上。(1.8是拼接新旧拼接好后才移动的)
// 3.新表替旧表
void resize(int newCapacity) {
// 旧桶
Entry[] oldTable = table;
// 旧容量
int oldCapacity = oldTable.length;
// 如果已经最大了,那么设置阀值,然后什么都不做直接返回
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
// 创建一个新桶,2倍大小的
Entry[] newTable = new Entry[newCapacity];
// 数据转移,第二个参数新容量决定是否再哈希
transfer(newTable, initHashSeedAsNeeded(newCapacity));
// 新表替旧表
table = newTable;
// 设置新阀值,其实可以直接*2的
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
2.9.1 数据转移
void transfer(Entry[] newTable, boolean rehash) {
// 取出新桶容量
int newCapacity = newTable.length;
// 遍历旧桶
for (Entry<K,V> e : table) {
// 遍历每个桶子上的链表
while(null != e) {
// 这里获取了当前遍历的e(Entry),以及e后一个Entry,就是遍历链表
Entry<K,V> next = e.next;
// 判断需不需要再哈希
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
// 在同一个桶子上,证明hash取模容量之后相同,只是下标相同,不一定hash相同
// 如果容量变化了,那么取模之后这些同链表的元素就会拆分
// 这里扩容完:
// 问题一:链表因为头插法,倒序了
// 问题二:多线程添加元素扩容,死锁。线程1元素丢失,线程2死循环
// 下面是头插法
// 获取新下标,因为桶长变了
int i = indexFor(e.hash, newCapacity);
// 这步和下一步共同组成移到桶子上方,然后再下移
e.next = newTable[i];
// 然后再往下移动
newTable[i] = e;
// 这个操作和上面合起来相当于:e = e.next,
e = next;
}
}
}
2.9.2 多线程扩容
// 旧哈希值两个线程共有
// 对于新表,两个线程各自会创建一个新表
// 转移到新表的时候,头插法混乱,线程1还没移动完成,线程2就开始移动,二者的链表会形成环,从而死循环
2.10 ConcurrentHashMap
并发的HashMap,使用了ReentrantLock,下面看原理
3.10.1 原理
// 有内部类Segment,继承了ReentranLock,且是个小的HashMap,那么每个分段扩容不同大小也就不同了
// 内有两数组,Segment[]、Entry[]
// Segment[]:实现了分段锁机制,往数组插入防止并发要用到CAS
// Entry[]:是我们真正存放元素的
// 意思就是Segment数组里面有个Entry数组,要往Entry放元素,得先获取Segment的锁
2.10.2 属性
// 并发级别,默认16
// 就是总共有多少个锁,分多少段(Segment),继承ReentrantLock
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
// 最多分段
static final int MAX_SEGMENTS = 1 << 16; // slightly conservative
2.10.3 构造方法
public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) {
// 参数校验
if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0)
throw new IllegalArgumentException();
if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS;
// Find power-of-two sizes best matching arguments
int sshift = 0;
int ssize = 1;
// 找大于等于并发级别的2次幂最小值
while (ssize < concurrencyLevel) {
// 记录并发级别的二进制位数
++sshift;
ssize <<= 1;
}
// 这里保留并发级别相关的位数,后面左移用到了
this.segmentShift = 32 - sshift;
// 这个用来取模找Segment数组下标
this.segmentMask = ssize - 1;
if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY;
// 这里看每个Segment存多少个
int c = initialCapacity / ssize;
// 除法的向上取整
if (c * ssize < initialCapacity) ++c;
// cap最小默认为2,这里是Segment里的HashMap用的容量大小,当然需要2次幂了
int cap = MIN_SEGMENT_TABLE_CAPACITY;
while (cap < c) cap <<= 1;
// create segments and segments[0]
// 创建一个S0的分段,不用每次都计算分段内HashMap的大小了
Segment<K,V> s0 =
// 加载因子,阀值,table建好放进去给你了
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
// 意思是结构复制
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
}
2.10.4 put方法
public V put(K key, V value) {
Segment<K,V> s;
if (value == null) throw new NullPointerException();
int hash = hash(key);
// 根据hash值,获取Segment数组的下标
int j = (hash >>> segmentShift) & segmentMask;
// 获取对应下标的数组元素,判断是否为空
if ((s = (Segment<K,V>)UNSAFE.getObject(segments, (j << SSHIFT) + SBASE)) == null)
// 为空创建小HashMap咯
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
2.10.5 生成Segment对象
private Segment<K,V> ensureSegment(int k) {
// 又来一次判断??
final Segment<K,V>[] ss = this.segments;
long u = (k << SSHIFT) + SBASE; // raw offset
Segment<K,V> seg;
// 看看有没有其他线程已经生成新的了,UNSAF安全类
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
// 从原型获取
Segment<K,V> proto = ss[0]; // use segment 0 as prototype
int cap = proto.table.length;
float lf = proto.loadFactor;
int threshold = (int)(cap * lf);
HashEntry<K,V>[] tab = (HashEntry<K,V>[])new HashEntry[cap];
// 又判断是不是空,再次检查
if ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
// new了一个Segment对象
Segment<K,V> s = new Segment<K,V>(lf, threshold, tab);
// 判空才自旋CAS添加第一层数组元素
while ((seg = (Segment<K,V>)UNSAFE.getObjectVolatile(ss, u)) == null) {
// 根据CAS操作交换的
if (UNSAFE.compareAndSwapObject(ss, u, null, seg = s))
break;
}
}
}
// 这里不管是否自己创建的,都会返回一个Segment对象,前面有多次获取最新。。。
return seg;
}
2.10.6 Segment的put方法
// Segment插入当然使用继承的ReentranLock
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// Seg对象的put方法会有锁,tryLock不阻塞的,和Lock比
// tryLock失败,可以用后面的获取锁方法
HashEntry<K,V> node = tryLock() ? null : scanAndLockForPut(key, hash, value);
V oldValue;
try {
// 获取该Seg对应的table
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
// 获取对应的table下标,返回第一个Entry,后面剧情应该是遍历是否重复,然后头插法
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key || (e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
node.setNext(first);
// 头插法
else
node = new HashEntry<K,V>(hash, key, value, first);
// 小HashMap的实际大小
int c = count + 1;
// 内部扩容机制
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
// 这个set内部用Unsafe方法,否则改的是线程的,不是内存的,可能还是有冲突
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
2.10.7 获取锁函数
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
// 获取table上对应的Entry
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
// 重试次数
int retries = -1;
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
// 先遍历判断要不要new一个Entry
if (retries < 0) {
if (e == null) {
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
else if (key.equals(e.key))
retries = 0;
else
e = e.next;
}
// 超过重试次数,直接lock()即可能被阻塞
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
// 判断获取的头部,和最新的头部是否相同。这里就是判断别人获取锁时,有否改变结构。
else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}
2.10.8 扩容
private void rehash(HashEntry<K,V> node) {
HashEntry<K,V>[] oldTable = table;
int oldCapacity = oldTable.length;
int newCapacity = oldCapacity << 1;
threshold = (int)(newCapacity * loadFactor);
HashEntry<K,V>[] newTable = (HashEntry<K,V>[]) new HashEntry[newCapacity];
int sizeMask = newCapacity - 1;
// 遍历转移咯
for (int i = 0; i < oldCapacity ; i++) {
HashEntry<K,V> e = oldTable[i];
if (e != null) {
HashEntry<K,V> next = e.next;
// 直接获取了新下标,不用判断是否重哈希
int idx = e.hash & sizeMask;
// 只有一个元素直接放进桶子
if (next == null)
newTable[idx] = e;
//
else { // Reuse consecutive sequence at same slot
HashEntry<K,V> lastRun = e;
int lastIdx = idx;
// 这个循环为了:记录最后下标相同的元素,转移就一起转移过去了
// 只是最后的,前面的和后面的就被覆盖了,即没有记录到
for (HashEntry<K,V> last = next;last != null;last = last.next) {
int k = last.hash & sizeMask;
if (k != lastIdx) {
lastIdx = k;
lastRun = last;
}
}
newTable[lastIdx] = lastRun;
// Clone remaining nodes
for (HashEntry<K,V> p = e; p != lastRun; p = p.next) {
V v = p.value;
int h = p.hash;
int k = h & sizeMask;
HashEntry<K,V> n = newTable[k];
newTable[k] = new HashEntry<K,V>(h, p.key, v, n);
}
}
}
}
// 秉持1.7先扩容再插入元素
int nodeIndex = node.hash & sizeMask; // add the new node
node.setNext(newTable[nodeIndex]);
newTable[nodeIndex] = node;
table = newTable;
}
3. JDK 1.8
- 判断是否空
- get
- 判断第一个元素
- 然后下一个再判断是否红黑树
- 不是红黑树就用链表的方法
- put
- 判断表是否为空,为空初始化
- 判断对应的桶子上是否为空,为空直接插入
- 判断是否匹配第一个元素,是就直接替换
- 否则判断是否红黑树,否则进入链表处理阶段(循环遍历看是否有重复元素)
- 有重复元素退出循环,进入值替换
- put之后,再判断是否需要resize ()
- 判断节点是否相等。先比Hash值,再比地址(适用null),最后再比key值
3.1 变量
// 初始化容量大小16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量2^30
static final int MAXIMUM_CAPACITY = 1 << 30;
// 默认加载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
// 转化红黑树界限8
static final int TREEIFY_THRESHOLD = 8;
// 转化成链表界限6,二者不同是为了不要在界限内一直转换
static final int UNTREEIFY_THRESHOLD = 6;
// 当链表需要转换成红黑树时,先先判断数组长度是否<64,是的话不转换红黑树,先扩容
static final int MIN_TREEIFY_CAPACITY = 64;
3.2 节点
// Node实现了Entry,之前用的是Entry
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
public final K getKey() { return key; }
public final V getValue() { return value; }
public final String toString() { return key + "=" + value; }
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
3.3 哈希值
// 根据key生成, >>>无符号舍弃右边16位,即取高16位
// 因为一般桶长度都在2字节下,
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// n = tab.length
// 一般桶长度都小于2^16即小于65536,即低16位才有效
// 这样与哈希异或的话,只能用到了低16位的
// 那么上面hash自身与自身的高16位异或,就利用到了高16位,更随机
tab[(n - 1) & hash]
// 返回桶下标
3.4 二次幂
// 1.8大于输入参数且最近的2的整数次幂的数,而1.7是小于等于的
// 或,右移运算
// 这样做为了让二进制中,最高位的1后面全置为1,后面加1转换为从1开始计数,不是从0开始了
static final int tableSizeFor(int cap) {
int n = cap - 1; // 防止已经是2次幂的情况
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
3.5 字段
transient Node<K,V>[] table;
transient Set<Map.Entry<K,V>> entrySet;
// 实际存储元素多少
transient int size;
// 记录 hashMap 发生结构性变化的次数
// 调用迭代器的时候,将modCount赋值给迭代器内部
// 如果修改了结构,modCount就会+1
// 那么迭代器迭代一次,就会判断内部的expectedModCount 和 HashMap的是否相同,不同则抛出异常
// 调用Iterator的remove方法即可,内部就是重新赋值迭代器内部的modCount而已
transient int modCount;
// 值等于table.length * loadFactor, size 超过这个值时进行 resize()扩容
int threshold;
// 加载因子
final float loadFactor;
3.6 构造函数
public HashMap(int initialCapacity, float loadFactor) {
// 小于0当然抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity);
// 不能最大2^30
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
// 加载因子异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// 构造函数只是赋值,没有初始化表,懒加载
this.loadFactor = loadFactor;
// 前面的2次幂用处,这里就设置了阀值,用于后面的扩容,其实就是赋值容量给阀值
this.threshold = tableSizeFor(initialCapacity);
// 没有了那个LinkedHashMap的访问顺序初始化函数
}
// 给了初始化大小,加载因子
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 就给了加载因子
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
3.7 简单函数
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
3.8 get方法
public V get(Object key) {
Node<K,V> e;
// 这里返回节点的.value
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
// 这里返回节点
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
// 桶不为空,桶大于0,获取桶下标
if((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null){
// 总是先对比桶上第一个元素,地址相同,或者内容相同(为了省去equal而先对比地址?)
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k))))
return first;
// 循环遍历链表,第一个元素都是被放弃遍历的吗。。。。
if ((e = first.next) != null) {
// 查看是否红黑树(难点,重点,考点)
if (first instanceof TreeNode)
// 强转,然后.getTreeNode方法
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
// 这里非红黑树就是链表了,遍历咯,找到就返回,没找到null
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
3.9 put方法
public V put(K key, V value) {
// 第三个参数:onlyIfAbsent,第四个:访问顺序用的
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
// tab存放表,p存放节点,n表示桶长,i表示哈希值
Node<K,V>[] tab; Node<K,V> p; int n, i;
// 空表时resize
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
// 对应桶上为空,直接放桶上
// tab[i = (n - 1) & hash],相当于 hash % n
// 但一般是取最大的素数来取模的
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
// 这里就是发生哈希冲突
else {
Node<K,V> e; K k;
// 首先判断是否第一个元素,是的话赋值给e,直接进入替换值的阶段,跳过下面两个步骤
if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
e = p;
// 进入红黑树处理阶段
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
// 链表处理阶段
else {
// 链表遍历
for (int binCount = 0; ; ++binCount) {
// 遍历到一个空的,那就尾部插入,这里和1.7的头插法不同
// 一旦插入成功,那么退出这个循环
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
// 是先插入元素再转换红黑树的
// 链长超过8,(那么实际上已经有9个了)转换红黑树,转换过程跳过,后面单独抽出来讲
// 特别注意,如果数组长度小于64,不会树化,是直接扩容
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
// 这里发现有个重复的元素
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
break;
// 节点移动,这里差点看不懂了
p = e;
}
}
// existing mapping for key
if (e != null) {
// 旧值
V oldValue = e.value;
// 新值替换旧值,返回旧值
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
// put新元素,除了替换属于结构改动
// 需要快速失败
++modCount;
// 桶长超过了 实际元素大小 * 加载因子
// 再散列
if (++size > threshold)
resize();
// LinkedHashMap使用的,这里为空函数
afterNodeInsertion(evict);
// 原来没有覆盖旧值是返回null
return null;
}
// 进行树化
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 表空,数组长度小于64进行扩容,不树化
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize();
// 当前选择的桶子不为空
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
// 遍历链表,转化成树节点
do {
// 双向链表prev,next,辅助性属性
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);
// 树化,节点内部有树化功能
// 这个树化是从头节点开始,即第一个节点当成根节点,然后根节点还没树化的链表遍历,一个个插入树中
// 这个过程穿插着平衡
if ((tab[index] = hd) != null)
hd.treeify(tab);
}
}
TreeNode节点继承了LinkedHashMap.Entry所以内部属性好多
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // red-black tree links
TreeNode<K,V> left;
TreeNode<K,V> right;
TreeNode<K,V> prev; // needed to unlink next upon deletion
boolean red;
// next是Node的属性
3.10 扩容
- 判断是否初始化过了
- 是:判断是否达到最大容量
- 设置变量的初值,但还没进行扩容
- 否:判断是否设置了加载因子,设置加载因子
- 否:调用默认构造,赋值都是默认的
- 是:判断是否达到最大容量
final Node<K,V>[] resize() {
// 获取旧表
Node<K,V>[] oldTab = table;
// 获取旧表容量
int oldCap = (oldTab == null) ? 0 : oldTab.length;
// 旧,新阀值
int oldThr = threshold;
int newCap, newThr = 0;
// 旧容量大于0,表示初始化过了
if (oldCap > 0) {
//如果已经最大容量了,设置阀值整型最大,不必要进行再散列了
// 返回旧表?
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 扩容是2倍,和ArrayList的1.5别搞错了
// 阀值当然也变成2倍了
else if((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >=DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
// 这里和上面对比,说明空表,但旧阀值大于0,说明调用了非空构造函数
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
// 这里是容量和阀值都为0,即调用默认构造函数的结果,即要初始化
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 建立新表
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
if (oldTab != null) {
// 遍历旧数组每一个数组,桶子不为空才转移
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
// 只有一个元素,直接插入
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
// 如果是树
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
// 遍历链表
do {
next = e.next;
// 哈希值 & 旧容量,原位置
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
// 相对二倍的位置
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 这个转移效率高了,容易看懂
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
// 红黑树转移
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
TreeNode<K,V> b = this;
// Relink into lo and hi lists, preserving order
TreeNode<K,V> loHead = null, loTail = null;
TreeNode<K,V> hiHead = null, hiTail = null;
int lc = 0, hc = 0;
// 和链表差不多,都是遍历,然后记录拆分位置
for (TreeNode<K,V> e = b, next; e != null; e = next) {
next = (TreeNode<K,V>)e.next;
e.next = null;
// 分别组装新旧位置的部分
if ((e.hash & bit) == 0) {
if ((e.prev = loTail) == null)
loHead = e;
else
loTail.next = e;
loTail = e;
++lc;
}
else {
if ((e.prev = hiTail) == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
++hc;
}
}
// 然后将这两部分转移过去,如果数量不超过‘6’,那么反树化
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
// 对这个组装好的部分,进行反树化
tab[index] = loHead.untreeify(map);
else {
// 整个树都移动过去
tab[index] = loHead;
// 如果另一个位置不为空,即有转移到另一个位置的元素
// 这样的话,低位就要树化;如果高位为空,整个树都转移过去了,不用再树化,本来就是一个树
if (hiHead != null)
loHead.treeify(tab);
}
}
if (hiHead != null) {
if (hc <= UNTREEIFY_THRESHOLD)
tab[index + bit] = hiHead.untreeify(map);
else {
tab[index + bit] = hiHead;
if (loHead != null)
hiHead.treeify(tab);
}
}
}
3.11 ConcurrentHashMap
换成普通的HashMap了,但增加了头部锁。桶子上的节点用CAS操作完成,而链表或红黑树里面的用Synchronized
3.11.1 原理
// 是普通的1.8哈希表
// 不同在于,每次修改结构会锁住 链表的头,红黑树的根
// Synchronized(Node / Root)
3.11.2 put方法
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
// 初始化表
if (tab == null || (n = tab.length) == 0)
tab = initTable();
// 第一个是否为空,Unsafe类直接获取内存值
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
// no lock when adding to empty bin
// CAS操作
if (casTabAt(tab, i, null,new Node<K,V>(hash, key, value, null)))
break;
}
// MOVDE = -1,表示整个数组在扩容
else if ((fh = f.hash) == MOVED)
// 表示当前线程也去帮忙扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
// 居然用了synchronized,锁住了头节点,意外
synchronized (f) {
// 若加锁时,另外线程删除了这个节点,锁没了
// 那么会再次循环,然后获取最新的头节点
if (tabAt(tab, i) == f) {
// 遍历链表
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
// 树化内部有锁
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
// 这里才是真正的扩容,上面的helpTransfer表示帮助扩容
addCount(1L, binCount);
return null;
}
3.11.3 init初始化
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
// 有可能一直竞争到时间片??? 不太可能把
Thread.yield(); // lost initialization race; just spin
// CAS改变了sc转台
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
3.11.4 扩容
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// 逻辑 ||,两个条件有条件执不执行
if ((as = counterCells) != null || !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
3.11.5 普通方法
public int size() {
long n = sumCount();
return ((n < 0L) ? 0 :
(n > (long)Integer.MAX_VALUE) ? Integer.MAX_VALUE :
(int)n);
}
final long sumCount() {
CounterCell[] as = counterCells; CounterCell a;
long sum = baseCount;
if (as != null) {
for (int i = 0; i < as.length; ++i) {
if ((a = as[i]) != null)
sum += a.value;
}
}
return sum;
}
4. 总结
4.1 两个版本的区别
- 1.8 添加了红黑树(logN)
- 1.7先扩容再添加元素,但空桶不扩容、1.8先添加元素再扩容
- 1.7和1.8的2次幂方法不同
- 1.7扩容是一个一个转、1.8是分成两个部分组装好了,最后才转移过去
- 1.7有单独的null方法,而1.8没有使用==判断了
- 1.8扩容,当数组长度<64时,优先扩容,不先转化红黑树
4.2 并发区别
- 1.7 使用分段锁,ReentranLock、CAS的使用,使用双重数组
- 1.8 使用头部锁,Synchronized、CAS的使用,使用普通哈希表