hashMap中 table是一个含有 Entry 的数组;初始化大小为initialCapacity,其中Entry是一个含有hash值。key,value,next的entry的 自定义结构。其中桶的概念就是一个table数组中的一个元素,而形成的一个链表;
- 1.7 hashMap put(K key, V value)方法
public V put(K key, V value) {
//初始化一个比阈值大,且最接近的threshold大小两倍的空的数组
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
//根据key的hash值,和table长度,返回一个table数组的下标
int i = indexFor(hash, table.length);
//得到这个table[i](也就是所谓的一个桶)的第一个元素e,然后遍历这个桶,
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
//为了返回相同key的原来的value值;并把新value放进去,返回原来的value
//如果存在相同相同的hash值 和key;则返回原来存储的key值对应的value值
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//如果第一个的元素e 为空,
addEntry(hash, key, value, i);
return null;
}
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);
}
void addEntry(int hash, K key, V value, int bucketIndex) {
//当前数组大小大于阈值;并且这个table数组中的一个元素table[bucketIndex](桶)不空null,
进行数组2倍大小扩容;重新计算要添加元素的的数组下标(桶)
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//桶这个链表的插入顺序为从;从头开始插入
//添加一个新的的entry 分两种情况;1.桶的第一个entry为空;创建entry,放入桶的第一个位置
//2.桶的第一个元素不为空;拿到桶的第一个元素;新建一个元素(entry),放入桶的第一个位置;并把桶原来的第一个元素e利用链表的形式,链接到新放入元素的后面
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
- 1.7 hashMap get(Object key) 方法
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
//根据hash 定位到桶;遍历桶;相同返回
for (Entry 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;
}
- 1.8 hashMap put(K key, V value)
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
//hashMap中,将原来的链表修改为了红黑树
//TREEIFY_THRESHOLD 当这个阈值超过8之后,就变为了红黑树
//HashEntry 修改为 Node
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
//当table 为空时,初始化table
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//当地一个桶的节点为空时,初始化第一个桶的节点
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
//此处为桶的第一个节点,hash key 和新插入节点的 hash key 相等的时,将原来的节点值 赋值给 e;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//当节点是一个TreeNode的时候,使用红黑树的方式,存储该节点
//如果key值相同,返回原来的节点
else if (p instanceof TreeNode)
e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
else {
//否则,以链表的形式存放该节点;
//进行桶的链表遍历
for (int binCount = 0; ; ++binCount) {
//当前节点为尾节点,在链表的末尾插入新的节点
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//大于阈值是变换为红黑树
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;
}
}
//添加新的节点时,e 为null
//如果原节点不为null,返回原节点的value,并将新value赋值给原节点的value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//记录节点修改次数
++modCount;
//数组扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
- 1.8 hashMap get(Object key)方法
public V get(Object key) {
Node e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node getNode(int hash, Object key) {
Node[] tab; Node first, e; int n; K k;
//为null 直接返回 null
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//第一个节点的判断;第二个节点存在红黑树的情况,区别对单
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//第二个节点
if ((e = first.next) != null) {
//为红黑树
if (first instanceof TreeNode)
return ((TreeNode)first).getTreeNode(hash, key);
do {
//链表 循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}
- 1.7 concurrentHashMap put(K key, V value)方法
//ConcurrentHashMap初始化时,计算出Segment数组的大小ssize和每个Segment中HashEntry数组的大小cap
public V put(K key, V value) {
Segment s;
if (value == null)
throw new NullPointerException();
int hash = hash(key);
int j = (hash >>> segmentShift) & segmentMask;
//根据key值定位到segment
if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck
(segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
s = ensureSegment(j);
//调用segment的put()方法
return s.put(key, hash, value, false);
}
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
//尝试获取锁
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;
//只是针对存在相同key的情况
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
//是否用新值,替换相同key的原来的value
if (!onlyIfAbsent) {
//修改相同key值节点的的value 值
e.value = value;
//修改次数 加 1
++modCount;
}
break;
}
e = e.next;
}
else {
if (node != null)
//从scanAndLockForPut()中得到的节点非空;桶的头结点插入第一个元素
node.setNext(first);
else
//(11 本方法节点) node为空创建新节点,并设置他的下一个节点 first节点
node = new HashEntry(hash, key, value, first);
int c = count + 1;
//扩容
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
//根据本方法内新建的节点(11 处新建节点)加入到table中,形成链表
//setEntryAt()操作以实现对链头的延时写,以提升性能,因为此时并不需要将该更新写入到内存
setEntryAt(tab, index, node);
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
//这个方法的主要作用是在 未 获取锁的情况下,一直缓存缓存当前链表的节点到cpu缓存,提高缓存命中率;
//这个方法的重点不是在找到节点或者新建节点,而是为了缓存;尽可能多的缓存节点;顺便找到了相同的节点
//或者为空,创建了新节点,这是很好的。如果没有也没事,而是在本方法scanAndLockForPut的调用的方法put内重新创建节点。
//在未获取锁的情况下,
private HashEntry scanAndLockForPut(K key, int hash, V value) {
//定位到是哪个桶的第一个节点
HashEntry first = entryForHash(this, hash);
HashEntry e = first;
HashEntry node = null;
//遍历链表标志
int retries = -1; // negative while locating node
//重点是这个while循环; 条件是 未 获取到锁;进行循环
//不管有没有找到相同key的节点或者新建节点,只要被其他线程唤醒,立刻返回当前节点
while (!tryLock()) {
HashEntry f; // to recheck first below
if (retries < 0) {
//如果第一个节点为null
if (e == null) {
//新节点也为null
//一直遍历,到为空的尾节点,新建节点
if (node == null) // speculatively create node
node = new HashEntry(hash, key, value, null);
retries = 0;
}
//找到节点也不在遍历
else if (key.equals(e.key))
retries = 0;
else
//节点循环
e = e.next;
}
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;
}
- 1.8 concurrentHashMap put(K key, V value)方法
- 1.8 concurrentHashMap get(K key)方法
public V get(Object key) {
Segment s; // manually integrate access methods to reduce overhead
HashEntry[] tab;
int h = hash(key);
long u = (((h >>> segmentShift) & segmentMask) << SSHIFT) + SBASE;
//根据hash定位到segment
if ((s = (Segment)UNSAFE.getObjectVolatile(segments, u)) != null &&
(tab = s.table) != null) {
//在根据hash 再定位到链表头,进行遍历
for (HashEntry e = (HashEntry) UNSAFE.getObjectVolatile
(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE);
e != null; e = e.next) {
K k;
if ((k = e.key) == key || (e.hash == h && key.equals(k)))
return e.value;
}
}
return null;
}
参考
https://my.oschina.net/7001/blog/896587
https://zhuanlan.zhihu.com/p/40327960
https://blog.csdn.net/fw0124/article/details/43308193
https://juejin.im/entry/59bfa3106fb9a00a652afd2f
https://juejin.im/entry/59fc786d518825297f3fa968