目录
前言
在Java集合框架中,HashMap以其高效的查找速度而闻名,是使用频率最高的数据结构之一。它巧妙地结合了数组和链表(或红黑树)的优势,通过哈希函数将键值对映射到数组的索引位置,从而实现快速的数据存取。
然而,HashMap的魔力远不止于此。为了充分发挥其性能优势,并避免潜在的性能瓶颈,深入理解其内部实现机制至关重要。
本文章的源码注释取自:https://javaguide.cn/
HashMap特点
源码注释
/**
* Hash table based implementation of the <tt>Map</tt> interface. This
* implementation provides all of the optional map operations, and permits
* <tt>null</tt> values and the <tt>null</tt> key. (The <tt>HashMap</tt>
* class is roughly equivalent to <tt>Hashtable</tt>, except that it is
* unsynchronized and permits nulls.) This class makes no guarantees as to
* the order of the map; in particular, it does not guarantee that the order
* will remain constant over time.
...
*/
翻译过来我们总结出HashMap的几大特性:
1、HashMap基于哈希表的数据结构实现的,并且实现了Map接口。
2、提供了所有可选的Map操作,意味着用户可以使用Map接口定义的所有方法,如:put()、get()、remove()等。
3、比较重要的一点是,HashMap允许多个value为null,一个key为null。
4、HashMap大概等同于HashTable,除了是非同步的并且允许null值。
5、HashMap不保证k-v对的顺序,具体来说,它不保证插入顺序和遍历顺序一致,也不保证顺序在多次遍历中保持不变。这意味着 HashMap 的遍历结果可能会随着时间而变化。
HashMap底层数据结构
JDK1.8之前
HashMap的底层数据结构主要由数组 + 链表组成,HashMap通过key的hashCode经过某个函数(扰动函数,为了便于读者理解,统一称为某个函数)计算得到hash值,然后再将这个hash值放到 (n-1) & hash 得到数组下标index(当前元素存放的位置)。如果当前位置存在元素,首先判断该元素和即将存入的元素hash值和key是否相同,如果相同,直接覆盖;如果不相同(哈希冲突),将新的元素插入到链表当前头部(拉链法)。JDK1.7为了解决哈希冲突,引入了某个函数(扰动函数),降低了index碰撞的概率。
以下为jdk1.7的HashMap的hash方法源码(代码了解即可):
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
拉链法的图示:

以下为JDK1.8之后的hash源码:
static final int hash(Object key) {
int h;
// key.hashCode():返回散列值也就是hashcode
// ^:按位异或
// >>>:无符号右移,忽略符号位,空位都以0补齐
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
对比之下,JDK1.8之前hash的效率稍慢,扰动了4次。
JDK1.8及之后
1.8以后HashMap的底层数据结构主要由数组 + 链表 + 红黑树组成,解决哈希冲突的方式也与之前的版本有了较大的改变。
解释:
1、链表长度>默认阈值8时,首先调用treeifyBin()方法,此方法根据HashMap数组来决定是否转化为红黑树。
2、数组长度>64 or 数组长度=64时,执行转化红黑树。否则仅仅执行resize()方法对数字扩容。

HashMap源码解析
类定义
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
// ...
// 存放Node节点的数组
transient Node<K,V>[] table;
// 默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
// 最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
// 桶中结构转化为红黑树对应的table的最小容量
static final int MIN_TREEIFY_CAPACITY = 64;
// 阈值(容量*负载因子) 当实际大小超过阈值时,会进行扩容
int threshold;
// 当桶(bucket)上的结点数大于等于这个值时会转成红黑树
static final int TREEIFY_THRESHOLD = 8;
// 当桶(bucket)上的结点数小于等于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;
// 负载因子
final float loadFactor;
// 默认的负载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
}
loadFactor:负载因子。控制数组存放数据的疏密程度。loadFactor越大,越趋近于1,也就是会让链表的长度增加,数组中存放的数据(entry)越多越密集;反之loadFactor越小,越趋近于0,entry越少越稀疏。
loadFactor太大导致链表长度增加,检索效率变低;太小导致数组利用率低,资源浪费。loadFactor=0.75f 是官方给出比较好的临界值。
threshold:阈值。数组扩容的判断标准。threshold = capacity * loadFactor, 当size > threshold 时,那么就应该考虑对数组扩容。
举个例子,DEFAULT_INITIAL_CAPACITY= 1 << 4 (默认容量为16),官方给定loadFactor=0.75f,那么扩容标准 threshold = 16 * 0.75 = 12,意味着当容量为12的时候,就需要将当前16进行扩容。
节点类源码
JDK1.8之后,HashMap 使用 Node 类来表示普通节点(链表节点),使用 TreeNode 类来表示红黑树节点:
// Node节点是数组中的一个单元
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; // 经过计算后得到的
final K key;
V value;
Node<K,V> next; // 指向下一个 Node 的指针
// 构造方法和其他方法省略
}
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent; // 父节点
TreeNode<K,V> left; // 左子节点
TreeNode<K,V> right; // 右子节点
TreeNode<K,V> prev; // 前驱节点
boolean red; // 节点颜色(红黑树)
// 构造方法和其他方法省略
}
构造函数
// 默认构造函数。
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
}
// 包含另一个“Map”的构造函数
public HashMap(Map<? extends K, ? extends V> m) {
this.loadFactor = DEFAULT_LOAD_FACTOR;
putMapEntries(m, false);//下面会分析到这个方法
}
// 指定“容量大小”的构造函数
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
// 指定“容量大小”和“负载因子”的构造函数
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 ,在resize中再赋值给 newCap 进行table初始化
this.threshold = tableSizeFor(initialCapacity);
}
注意:在所有的构造函数中,都初始化了loadFactor参数
put方法
假设你是一个小白给你一个空数组,你会如何放置Node节点?

如果说计算出相同的下标,那么就会将已有元素的next指针指向插入元素;
如果插入的元素和之前的元素key值相同,则会遍历链表找到key相同的元素并且覆盖其value

实际上详细的图解:取自https://javaguide.cn/

public V put(K key, V value) {
// hash方法源码在前面
return putVal(hash(key), key, value, false, true);
}
// 为了便于阅读,为部分if逻辑增添了{}
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;
// table未初始化或者长度为0,进行扩容
if ((tab = table) == null || (n = tab.length) == 0){
n = (tab = resize()).length;
}
/* ************情况1:如果数组没有元素,此时tab已经初始化好了*************** */
if ((p = tab[i = (n - 1) & hash]) == null){
// (n - 1) & hash 确定元素存放在哪个桶中,桶为空,新生成结点放入桶中(此时,这个结点是放在数组中)
tab[i] = newNode(hash, key, value, null);
}
/* ************情况2:如果数组的存放位置存在元素,处理hash冲突*************** */
else {
Node<K,V> e; K k;
//快速判断节点table[i]的key的hash值和key是否 与 插入key的hash值和key一样,若相同就直接使用插入的值p替换掉旧的值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) {
// 直到某个元素没有下一个节点,说明遍历到了链表尾部
if ((e = p.next) == null) {
// 在尾部插入新结点
p.next = newNode(hash, key, value, null);
// 结点数量达到阈值(默认为 8 ),执行 treeifyBin 方法
// 这个方法会根据 HashMap 数组来决定是否转换为红黑树。
// 只有当数组长度大于或者等于 64 的情况下,才会执行转换红黑树操作,以减少搜索时间。否则,就是只是对数组扩容。
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
// 跳出循环
break;
}
// 判断链表中结点的key值与插入的元素的key值是否相等
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
// 相等,跳出循环
break;
// 用于遍历桶中的链表,与前面的e = p.next组合,可以遍历链表
p = e;
}
}
// 表示在桶中找到key值、hash值与插入元素相等的结点进行value覆盖
if (e != null) {
// 记录e的value
V oldValue = e.value;
// onlyIfAbsent为false或者旧值为null
if (!onlyIfAbsent || oldValue == null)
//用新值替换旧值
e.value = value;
// 访问后回调
afterNodeAccess(e);
// 返回旧值
return oldValue;
}
}
// 结构性修改
++modCount;
// 实际大小大于阈值则扩容
if (++size > threshold)
resize();
// 插入后回调
afterNodeInsertion(evict);
return null;
}
代码解释
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;
}
1、遍历链表,p = e的操作其实就是将下一个遍历的对象赋值到当前节点。
2、判断遍历的当前节点下一个节点是否为空,如果为空的话将新节点插入到尾部,继续判断如果链表长度大于8,则树化为二叉树。
3、如果不为空则继续比较key的hash和key是否相同,相同则退出循环
计算索引
对于计算键值对在数组中的存储位置(索引),最终采用 i = (n - 1) & hash,可以控制index下标游离在0-15的区间,是为什么呢?
举个例子,数组默认n=16,并且n的类型为int,int为4个字节,1个字节=8 bit,总共4*8=32 bit,
16的二进制为:省略前面的24个0,看后八位为0001 0000
(n-1) = 15, 15的二进制为:0000 1111
假设hash值的二进制为:0101 0101
根据&运算规则:两个对应位都为 1 时,结果为 1;否则结果为 0。那么15和hash值的&运算结果为:0000 0101
可以看出&运算结果的前四位必然为:0000, 而后四位:0101根据二进制转化为十进制为5,1111则为15,后四位的二进制范围为0000-1111之间,十进制取值范围则为0-15。
注意:如果这里n-1=16,最后的&运算都是只有0和1两种结果,所以n-1=15是非常关键的!对于n的取值,也会有很大的限制,我们会选取2的幂次方作为n的取值,如:2、4、8、16、32等。

当我们创建HashMap时,如果传入10
HashMap<String, String> map = new HashMap<>(10);
map.put("key","value");
阅读源码我们可以发现,其实并不是初始化数组大小为10, 而是调用tableSizeFor()方法做对应的策略得到2的幂次方:
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
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;
this.threshold = tableSizeFor(initialCapacity);
}
// 返回一个大于等于cap的2的幂次方数
static final int tableSizeFor(int cap) {
int n = cap - 1;
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;
}
举个例子,如果传入10,则会返回16;如果传入7,则会返回8作为capacity(数组大小)
treeifyBin方法
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;
}
解释:当binCount=0时,链表中有1个元素;binCount=1,有2个元素…以此类推,当binCount=7,有8个元素,p.next = newNode(hash, key, value, null)加入一个新元素后就有9个元素,默认阈值为8,已经超出了,所以if (binCount >= TREEIFY_THRESHOLD - 1)就刚好出现binCount = 8 - 1 = 7然后进入树化treeifyBin逻辑。
// 当前
final void treeifyBin(Node<K,V>[] tab, int hash) {
int n, index; Node<K,V> e;
// 如果tab为null 或 当前数组的长度小于64
if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
resize(); // 数组长度比较小就进行扩容
// 只有当数组长度超过64,并且链表长度超过8时才会触发转化红黑树
else if ((e = tab[index = (n - 1) & hash]) != null) {
TreeNode<K,V> hd = null, tl = null;
do {
// 将链表元素依次转化为红黑树节点TreeNode(属性不变)
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);
}
}
do {
// 将链表元素依次转化为红黑树节点TreeNode(属性不变)
TreeNode<K,V> p = replacementTreeNode(e, null);
if (tl == null)
hd = p;
else {
// 这两行代码所做的事情就是给节点的prev指针赋值,使得成为双向链表
p.prev = tl;
tl.next = p;
}
tl = p;
} while ((e = e.next) != null);

在生成双向链表之后,才会将链表转化为红黑树(红黑树的内部结构也同样存在双向链表)
if ((tab[index] = hd) != null)
hd.treeify(tab);
resize方法(扩容)
JDK1.7的扩容思路很简单:
说白了,数组中存储的元素其实就是Node引用地址,然后引用地址指向堆中的对象,JDK1.7扩容会创建新的数组(更大),然后将引用地址从旧数组改到新数组,然后堆对象指向的数组元素地址也随之变更。
JDK1.8及以后:
还是把老数组的元素转移到新数组
扩容源码:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
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;
}
可以将代码分为两大部分来看:
初始化tab数组:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 初始化
if (oldCap > 0) {
// 老数组是否大于阈值容量,如果超过阈值,则不会继续扩容了
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
// 没超过最大值,就扩充为原来的2倍
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY)
newThr = oldThr << 1;
}
else if (oldThr > 0)
// 创建对象时初始化容量大小放在threshold中,此时只需要将其作为新的数组容量
newCap = oldThr;
else {
// 无参构造函数创建的对象在这里计算容量和阈值(初始化核心代码)
newCap = DEFAULT_INITIAL_CAPACITY; // newCap = 16
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // newThr = 12
}
if (newThr == 0) {
// 创建时指定了初始化容量或者负载因子,在这里进行阈值初始化,
// 或者扩容前的旧容量小于16,在这里计算新的resize上限
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE);
}
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
// 创建Node(初始化核心代码)
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
table = newTab;
// 扩容
// ...
return newTab;
}
扩容:
final Node<K,V>[] resize() {
Node<K,V>[] oldTab = table;
int oldCap = (oldTab == null) ? 0 : oldTab.length;
int oldThr = threshold;
int newCap, newThr = 0;
// 初始化
// ...
// 扩容
if (oldTab != null) {
// 把每个bucket都移动到新的buckets中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e; // 临时节点
if ((e = oldTab[j]) != null) {
// (e = oldTab[j])已经将老数组的元素地址赋给e了,所以老数组的元素地址可以为null了
oldTab[j] = null;
if (e.next == null)
// 当前转移位置只有一个节点,直接计算元素新的位置即可
newTab[e.hash & (newCap - 1)] = e;
else if (e instanceof TreeNode)
// 将红黑树拆分成2棵子树,如果子树节点数 <= UNTREEIFY_THRESHOLD(默认为 6),则将子树转换为链表。
// 如果子树节点数大于 UNTREEIFY_THRESHOLD,则保持子树的树结构。
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
else { // 如果是链表转移不是单纯的照搬,而要考虑到链表的各个节点转移到新数组的下标
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;
}
// 原索引+oldCap
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原索引放到bucket里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 原索引+oldCap放到bucket里
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
链表扩容:
情况1:新数组下标 = 老数组下标 + oldTab.length

情况2:新数组下标 = 老数组下标

所以在新数组中,要么在老位置,要么在老位置上+oldCap
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;
}
// 原索引+oldCap
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
// 原索引放到bucket里
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
// 原索引+oldCap放到bucket里
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
解释:loHead记录的是低位的第一个元素的地址,loTail记录的是低位最后一个元素的地址
hiHead记录的是高位的第一个元素的地址,hiTail记录的是高位最后一个元素的地址
大致思路:根据低位头尾、高位头尾标识移动元素到对应的高低位。

红黑树扩容
解释:同样判断是新数组下标是高位还是低位, 如果低位链表元素个数<=6,则会将红黑树转化为链表(untreeify方法主要将TreeNode类型转化为Node类型)
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;
}
}
if (loHead != null) {
if (lc <= UNTREEIFY_THRESHOLD)
tab[index] = loHead.untreeify(map);
else {
tab[index] = loHead;
if (hiHead != null) // (else is already treeified)
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);
}
}
}
get方法
public V get(Object key) {
Node<K,V> e;
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;
// 数组元素相等
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<K,V>)first).getTreeNode(hash, key);
// 遍历链表从链表中获取,直到搜索到key的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;
}

331

被折叠的 条评论
为什么被折叠?



