1、成员变量
/**
* 默认初始化容量,必须是2的倍数
*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
/**
* 最大容量,如果任意一个带有参数的构造函数隐式指定更高的值,则使用该最大容量。
* 必须是小于等于1<<30
*/
static final int MAXIMUM_CAPACITY = 1 << 30;
/**
* 默认负载因子为0.75
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 树化阈值。链表长度大于等于8,并且数组长度大于64才会树化。
* shrinkage.
*/
static final int TREEIFY_THRESHOLD = 8;
/**
* 树退化为链表的阈值。当桶中树元素小于等于6时,树退化为链表
*/
static final int UNTREEIFY_THRESHOLD = 6;
/**
* 最小树化容量。hashmap桶中链表长度大于8,整个hash表中键值对的个数小于64,仅仅进行扩容。
* 因为这个时候扩容效率还是高于转化为红黑树的。
* 只有当键值对个数大于64时,进行红黑树化。
*/
static final int MIN_TREEIFY_CAPACITY = 64;
2、构造函数
/**
* 构造一个空HashMap,默认初始化容量和默认加载因子。
* 默认初始化容量为16,默认加载因子为0.75
*/
public HashMap() {
//赋值默认加载因子,初始化容量在首次put扩容的时候设置。
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}
/**
* 构造一个指定初始化容量的空HashMap
* 加载因子为默认加载因子0.75
*/
public HashMap(int initialCapacity) {
//调用指定容量和指定加载因子的构造函数,指定为默认加载因子。
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 构造一个空的HashMap,指定初始化容量和初始化加载因子的构造方法
*
* @param initialCapacity 初始化容量
* @param loadFactor 初始化加载因子
*/
public HashMap(int initialCapacity, float loadFactor) {
//如果初始化容量为负数,则抛出非法参数异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
//如果初始化容量大于hashmap最大容量,则初始化容量就是最大容量。
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
//加载因子为负数或者不是float的数值。抛出非法参数异常。
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
//赋值加载因子。 loadFactor);
this.loadFactor = loadFactor;
//调用tableSizeFor方法获取大于等于指定初始化容量的最小2次幂。
this.threshold = tableSizeFor(initialCapacity);
}
/**
*
* 返回指定目标容量的2次幂(大于等于指定容量的最小2次幂)。
* 一个非0的32位整数不为0的时候,32位中至少有一个位置
* 为1,5个位移操作,是将从最高位1开始,一直到最低位的所有
* bit全部设置为1,然后再加1,从最高位1到最低位全部变0,进一位,就得到了大于等于指定容量的最小2次幂
*
* 先来分析有关n位操作部分:先来假设n的二进制为01xxx...xxx。接着
*
* 对n右移1位:001xx...xxx,再位或:011xx...xxx
*
* 对n右移2为:00011...xxx,再位或:01111...xxx
*
* 此时前面已经有四个1了,再右移4位且位或可得8个1
*
* 同理,有8个1,右移8位肯定会让后八位也为1。
*
* 综上可得,该算法让最高位的1后面的位全变为1。
*
* 最后再让结果n+1,即得到了2的整数次幂的值了。
*
*/
static final int tableSizeFor(int cap) {
//int为32位
//令找到的目标值大于或等于原来的值
//例如二进制1000,十进制数值为8。如果不对它减1而直接操作,将得到答案10000,即16。
//显然不是结果。减1后二进制为111,再进行操作则会得到原来的数值1000,即8。
//如果给定的n已经是2的次幂,但是不进行-1操作的话,那么得到的值就是大于给定值的最小2的次幂值。
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
/**
*
假设实参cap的二进制为01xx…xx。
对cap右移1位:01xx…xx,移位之后或:011xx…xx,使得与最高位的1紧邻的右边一位为1,
对cap再右移2位:00011x..xx,移位之后或:01111x..xx,使得从最高位的1开始的四位也为1,
以此类推,int为32位,所以在右移16位后异或最多得到32个连续的1,保证从最高位的1到末尾全部为1。
*/
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
/**
* 利用已存在的map构建HashMap,使用默认初始化容量和默认加载因子。
*/
public HashMap(Map<? extends K, ? extends V> m) {
//设置默认加载因子
this.loadFactor = DEFAULT_LOAD_FACTOR;
//放值。
putMapEntries(m, false);
}
3、put方法
/**
*
* 将指定值与该映射中的指定键相关联。 如果映射先前包含键的映射,则将替换旧*值。
*
* @param key 指定值将与之关联的键
* @param value 与指定键关联的值
*/
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
/**
*key的hashCode是一个int类型的数值,长度32位,将hashCode右移16位,
* 然后把int数值的高16位和低16位进行异或操作(相应位上的数字不相同时,该位才取1,若相同,即为0),
* 这样可以有效避免高16位不同,但是低16位相同的key的hash碰撞
*/
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
* 实现Map.put和相关方法
* 寻址算法:
* &运算代替%运算,主要是为了提升运算效率,
* 这样hashMap的长度必须是2的n次方这样才能满足
* hash % 2^n = hash & (2^n - 1),这也是hashMap长度必须是2的n次方的原因
* put放值
*/
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//创建数组存储hash表引用
Node<K,V>[] tab;
//创建节点存储通过寻址寻到的位置当前节点
Node<K,V> p;
//n存储hash表长度,i存储当前寻址的hash表索引
int n, i;
//如果hash表为空
if ((tab = table) == null || (n = tab.length) == 0)
//初始化hash表,并将hash表长度赋值给n
n = (tab = resize()).length;
//通过hash寻址计算到的数组索引对应的节点,将节点赋值给p,并判断是否为空,如果为空直接创建新节点放到数组的该索引位置。
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
到这里说明目标位置桶里已经有东西了
//如果不为空,则说明,该桶位置已经存在链表或者红黑树了。
//创建节点e,用于存储通过节点p获取到的
Node<K,V> e; K k;
//如果桶中已经有值,
//判定当前桶中的key和当前要存储的key是否相等。
//1.hash相同。
//2.两者‘==’或者equals相等。
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
//已存在当前key, e保存原有的键值对
e = p;
//要保存的桶已被占用,并且占用位置的key和当前key不一致。
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个,就将链表转化为红黑树以提升查找性能。
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;
p = e;
}
}
//map中存在key
if (e != null) { // existing mapping for key
V oldValue = e.value;
//旧值存在或者旧值为空,用新值覆盖旧值。
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
//直接返回
return oldValue;
}
}
++modCount;
//table不存在要新增的key,将新的key插入hash
//新增值了,hash元素个数加1
if (++size > threshold)
//扩容。
resize();
afterNodeInsertion(evict);
return null;
}