- HashMap重要成员变量默认值
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
{
//默认的数组长度 2^4=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;
//hash种子,初始为0
transient int hashSeed = 0;
}
- HashMap构造方法
//调用空参构造方法,数组长度和负载因子使用默认值
public HashMap() {
//this(16,0.75f);
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
//空参调用有参构造方法
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;
//校验负载因子,小于等于0或非数值抛出异常
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
//负载因子赋值0.75
this.loadFactor = loadFactor;
//数组扩容的阈值,暂时为数组长度16
threshold = initialCapacity;
//空方法
init();
}
// init方法
void init() {
}
- put方法
// 调用put方法 参数为String类型的key和value
public V put(K key, V value) {
// 当前数组为空则进行初始化
if (table == EMPTY_TABLE) {
//初始化数组,长度为默认的16
inflateTable(threshold);
}
// key如果是null,使用特殊的方法
if (key == null)
return putForNullKey(value);
// 获取key的hash值
int hash = hash(key);
// 根据hash值和数组长度计算要放入的数组下标位置
int i = indexFor(hash, table.length);
// 获取下标位置数组的链表头,不满足条件则一个一个向下遍历链表
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 判断key的值是否相等
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
// key的值相等则写入新的value值,将旧value返回
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 修改次数+1
modCount++;
// 下标位置为空或没有能够匹配的key值,创建Entry放入链表
addEntry(hash, key, value, i);
// 没有旧的value,返回null
return null;
}
put方法中调用的方法inflateTable
private void inflateTable(int toSize) {
// 找到大于等于指定数组长度的2的n次方
int capacity = roundUpToPowerOf2(toSize);
// 扩容阈值,数组长度*负载因子
threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
// 创建数组
table = new Entry[capacity];
// 是否重新赋值hashSeed
initHashSeedAsNeeded(capacity);
}
//roundUpToPowerOf2方法
private static int roundUpToPowerOf2(int number) {
// 使用Integer的highestOneBit方法找到大于等于指定数组长度的2的n次方
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
//initHashSeedAsNeeded方法
final boolean initHashSeedAsNeeded(int capacity) {
// hashSeed 初始值为0,false
boolean currentAltHashing = hashSeed != 0;
// 数组长度是否 >= 2^31-1
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
// 使用异或,currentAltHashing 为false,只有数组长度>= 2^31-1时返回true
boolean switching = currentAltHashing ^ useAltHashing;
//switching为true,则hashSeed重新赋值(一般不会出现)
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
// Integer.highestOneBit方法
put调用的方法putForNullKey
private V putForNullKey(V value) {
// 当key为null时,数组下标指定为0
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
// 判断现在有没有key为null的Entry
if (e.key == null) {
//value替换为新值,返回旧的value
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//修改次数+1
modCount++;
// table[0]中没有值或没有匹配到null的key,创建一个Entry放入table[0]
addEntry(0, null, value, 0);
return null;
}
put 和 putForNullKey 都调用的addEntry
//addEntry方法
void addEntry(int hash, K key, V value, int bucketIndex) {
// 如果当前数组长度>=扩容阈值 并且 当前数组下标位置不为null
if ((size >= threshold) && (null != table[bucketIndex])) {
// 数组扩容为当前长度*2
resize(2 * table.length);
// key是否为null,不为null计算hash值,null则直接hash值为0
hash = (null != key) ? hash(key) : 0;
// 根据hash值和新数组长度计算下标位置
bucketIndex = indexFor(hash, table.length);
}
//创建Entry放入table中
createEntry(hash, key, value, bucketIndex);
}
// createEntry方法
void createEntry(int hash, K key, V value, int bucketIndex) {
// 获取当前数组下标位置的链表头
Entry<K,V> e = table[bucketIndex];
// 在链表头位置创建新的Entry,next指向原链表头(头插法)
table[bucketIndex] = new Entry<>(hash, key, value, e);
// 数组长度+1
size++;
}
put 和 addEntry 都调用的indexFor
static int indexFor(int h, int length) {
// 计算数组下标位置,数组长度必须为2的n次方
return h & (length-1);
}
addEntry中调用的resize
void resize(int newCapacity) {
// 原数组
Entry[] oldTable = table;
// 原数组长度
int oldCapacity = oldTable.length;
// 如果原数组长度为2^30,不进行扩容,把扩容阈值修改为2^31-1
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
// 根据新的数组长度,创建数组
Entry[] newTable = new Entry[newCapacity];
// 转移数组数据
// 调用initHashSeedAsNeeded方法,根据新数组的长度判断是否会hashSeed重新赋值
transfer(newTable, initHashSeedAsNeeded(newCapacity));
// table指向新数组
table = newTable;
// 计算新的扩容阈值
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
// transfer方法
void transfer(Entry[] newTable, boolean rehash) {
// 新数组长度
int newCapacity = newTable.length;
// 遍历原数组的Entry
for (Entry<K,V> e : table) {
// Entry不为null
while(null != e) {
// 先找到链表中下一个要转移的Entry
Entry<K,V> next = e.next;
// 如果hashSeed变了,重新进行hash计算
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
// 重新计算数组下标,结果分两种:1.与原下标一直 2.原下标+本次扩容了多长
int i = indexFor(e.hash, newCapacity);
/*原数组统一链表中的数据转移到新数组中时,所处链表顺序颠倒,因此多线程的情况下可能会出现环形链表问题*/
// Entry的next指向新数组的链表头
e.next = newTable[i];
// Entry放入新数组的链表中
newTable[i] = e;
// 下一次进行操作的就是原数组链表的下一个
e = next;
}
}
}
- get方法
public V get(Object key) {
// 判断key是否为null
if (key == null)
// 通过特殊方法获取key的value
return getForNullKey();
// key不为null,获取Entry
Entry<K,V> entry = getEntry(key);
//Entry是否匹配到,有的话返回对应的value,否则返回null
return null == entry ? null : entry.getValue();
}
//getForNullKey方法
private V getForNullKey() {
// 数组长度为0直接返回null
if (size == 0) {
return null;
}
// 遍历table[0]位置的链表
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
// 是否存在key为null
if (e.key == null)
// 返回对应的value
return e.value;
}
// 没有匹配到,返回null
return null;
}
// getEntry方法
final Entry<K,V> getEntry(Object key) {
// 数组长度为0直接返回null
if (size == 0) {
return null;
}
// key是否为null,不为null计算hash值,null则直接hash值为0
int hash = (key == null) ? 0 : hash(key);
// 计算数组下标,遍历下标位置的链表
for (Entry<K,V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) {
Object k;
// 判断key是否相等
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))
// 返回链表中的Entry
return e;
}
// 没有匹配到,返回null
return null;
}
问题
- 为什么数组长度为2的n次方?
计算数组下标时,当数组长度为2的n次方时,hash/length
等同于hash&(length-1)
static int indexFor(int h, int length) {
// 计算数组下标位置,数组长度必须为2的n次方
return h & (length-1);
}
假设数组长度为16,hash值为 0110 0010 1001(瞎写的)
16 转换为2进制 0000 0001 0000
15 转换为2进制 0000 0000 1111
使用hash值和15进行与运算
0110 0010 1001
0000 0000 1111
&
结果 0000 0000 1001
由上面的计算可以看出:只有在数组长度为2的n次方时,数组长度-1转换为2进制时才可以转换为低位全部为1的二进制,和hash值进行与运算时,就可以确保最终下标位置的结果为0-15了
- transfer方法进行数据转移到新数组时,为什么hashSeed变了就要重新进行hash计算呢?
下面是计算hash值的方法,在扰动函数中用到了hashSeed的值
(待补充)
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}