首先HashMap的初始容量设置为16,其实必须是2的幂。
问一个问题:
Hashmap中的链表大小超过八个时会自动转化为红黑树,
当删除小于六时重新变为链表,为啥呢?
在源码中,有一个叫做_Factor,默认是0.75,根据泊松分布,也即是说单个hash桶内元素个数为8的概率小于百万分之一。所以以7为转折点,大于等于8的时候才进行转换为树,小于等于6的时候就化为链表。
在多线程情况下,为保证线程安全,怎样使用HashMap?
1 HashTable与HashMap区别:
//HashMap 看出当键值为空,则返回第一个桶进行插值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
HashTable
采用安全失败机制:即当前获取的数据可能不是最新的数据,如果为空值,则无法判断是为空还是不存在。
//看下面抛出异常信息。
public V put(K key, V value) {
Segment s;
if (value == null)
throw new NullPointerException();//这就是为啥他不可以put null值的原因
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
if ((s = (Segment)UNSAFE.getObject
(segments, (j << SSHIFT) + SBASE)) == null)
s = ensureSegment(j);
return s.put(key, hash, value, false);
}
HashMap的底层参考我的另一篇文章,桶原则:https://blog.csdn.net/huifeidezhu521/article/details/105451427
在ConcrrentHashMap中的数组采用Segment 数组,理论上讲采用分段锁机制,继承ReentrantLock,就是访问当前Segment,并不会影响其他的。
put操作,做到线程安全
1.7版本
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
// 将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry
//这一步是计算方式
HashEntry node = tryLock() ? null :
scanAndLockForPut(key, hash, value);
V oldValue;
try {
HashEntry[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry first = entryAt(tab, index);
for (HashEntry e = first;;) {
if (e != null) {
K k;
// 遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
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 {
// 不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
if (node != null)
node.setNext(first);
else
node = new HashEntry(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
//释放锁
unlock();
}
return oldValue;
}
用语言描述就是:
根据 key 计算出 hashcode 。
判断是否需要进行初始化。
即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
如果都不满足,则利用 synchronized 锁写入数据。
如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。
get操作
需要通过key值Hsah之后定位到Segement,再一次Hash定位到具体元素。每次获取都是最新值,并不需要加锁。
只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。
但是在1.8版本中采用CAS + synchronized 来保证并发安全性。同时引入红黑树。
ConcurrentHashMap的get操作
根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
如果是红黑树那就按照树的方式获取值。
就不满足那就按照链表的方式遍历获取值。
小结:1.8 在 1.7 的数据结构上做了大的改动,采用红黑树之后可以保证查询效率,甚至取消了 ReentrantLock 改为了 synchronized。
///站在巨人的肩膀上,也是第一次写这么深奥的东西,谅解。