本文只是学习笔记,如有错误,欢迎指出
HashMap的成员变量
//默认的初始化容量,必须是2的幂次方
static final int DEFAULT_INITIAL_CAPACITY = 16;
//最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//默认的加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//Entry数组
transient Entry[] table;
//已存放的数量
transient int size;
//下次扩容的临界值,size>=threshold就会扩容
int threshold;
//加载因子
final float loadFactor;
//修改次数,如果在使用迭代器时修改了Hashmap的结构,会导致快速出错
transient volatile int modCount;
构造器
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);
//初始化时的容量。必须是2的幂次方,如果不是,会找到一个大于initialCapacity的值
//比如传入5,通过计算,会使用8作为初始容量
// Find a power of 2 >= initialCapacity
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor);
table = new Entry[capacity];
init();
}
//Entry类,包含key,value,hash方法计算出来的hash和在链表中的next
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
.....
}
put操作
public V put(K key, V value) {
//当key为null,使用putForNullKey方法
if (key == null)
return putForNullKey(value);
//计算key的hashcode,这里调用了hash方法,将原key的hashcode再hash了一次
int hash = hash(key.hashCode());
//通过hash,找到这个key在table数组的下标
int i = indexFor(hash, table.length);
//jdk1.6使用的是拉链法,hash碰撞时会将相同hash值的放在一个链表里,且后放入的放在链表头部
//取出下标为i的链表,通过next取出链表的下一个Entry
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//如果hash相同,且key值相等,就说明找到相同key值的Entry了,需要替换掉
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
//将找到的值赋值给临时变量
V oldValue = e.value;
//更新这个key的value
e.value = value;
e.recordAccess(this);
//返回旧的值
return oldValue;
}
}
//如果整个链表都没找到相同的key
//修改数量加1,如果已经有一个迭代器,修改这个值,会导致迭代时快速失败
modCount++;
//添加一个新key
addEntry(hash, key, value, i);
return null;
}
//将hash值再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);
}
以下引用https://www.zhihu.com/question/20733617 胖胖的回答
Java 8中这步已经简化了,只做一次16位右位移异或混合,而不是四次,但原理是不变的。下面以Java 8的源码为例解释
//Java 8中的散列值优化函数 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); >//key.hashCode()为哈希算法,返回初始哈希值 }
大家都知道上面代码里的key.hashCode()函数调用的是key键值类型自带的哈希函数,返回int型散列值。
理论上散列值是一个int型,如果直接拿散列值作为下标访问HashMap主数组的话,考虑到2进制32位带符号的int表值范围从-2147483648到2147483648。前后加起来大概40亿的映射空间。只要哈希函数映射得比较均匀松散,一般应用是很难出现碰撞的。
但问题是一个40亿长度的数组,内存是放不下的。你想,HashMap扩容之前的数组初始大小才16。所以这个散列值是不能直接拿来用的。用之前还要先做对数组的长度取模运算,得到的余数才能用来访问数组下标。源码中模运算是在这个indexFor( )函数里完成的。
bucketIndex = indexFor(hash, table.length);
indexFor的代码也很简单,就是把散列值和数组长度做一个”与”操作,
static int indexFor(int h, int length) { return h & (length-1); }
顺便说一下,这也正好解释了为什么HashMap的数组长度要取2的整次幂。因为这样(数组长度-1)正好相当于一个“低位掩码”。“与”操作的结果就是散列值的高位全部归零,只保留低位值,用来做数组下标访问。以初始长度16为例,16-1=15。2进制表示是00000000 00000000 00001111。和某散列值做“与”操作如下,结果就是截取了最低的四位值。
10100101 11000100 00100101
& 00000000 00000000 00001111
=======================
00000000 00000000 00000101 //高位全部归零,只保留末四位但这时候问题就来了,这样就算我的散列值分布再松散,要是只取最后几位的话,碰撞也会很严重。更要命的是如果散列本身做得不好,分布上成等差数列的漏洞,恰好使最后几个低位呈现规律性重复,就无比蛋疼。
这时候“扰动函数”的价值就体现出来了,说到这里大家应该猜出来了。看下面这个图,
![]()
右位移16位,正好是32bit的一半,自己的高半区和低半区做异或,就是为了混合原始哈希码的高位和低位,以此来加大低位的随机性。而且混合后的低位掺杂了高位的部分特征,这样高位的信息也被变相保留下来。
//key为null时,在table数组的下标是0
private V putForNullKey(V value) {
//和put方法一样,在index为0的链表里找key为null的值
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0);
return null;
}
//添加新的entry
void addEntry(int hash, K key, V value, int bucketIndex) {
//直接放到对应的bucketIndex下,将原来的Entry赋值给新Entry的next字段
//替换掉原来Entry的位置,所以新加的都是放在链表的头部
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
//如果size大于threshold,将会扩容为原来的2倍,并进行rehash
if (size++ >= threshold)
resize(2 * table.length);
}
//重新修改数值大小
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//如果大于最大容量,只是把threshold修改为Integer.MAX_VALUE
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
//创建一个新容量为原来2倍的Entry数组
Entry[] newTable = new Entry[newCapacity];
//将旧的数组转换到新的数组上
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
//循环取值旧的数组
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
//将旧的数组j位置设置为null
src[j] = null;
do {
//保存e.next
Entry<K,V> next = e.next;
//计算新的下标
int i = indexFor(e.hash, newCapacity);
//在新的数组取出i位置的链表,指向e.next
e.next = newTable[i];
//将e放到新的数组上
newTable[i] = e;
//将保存的next赋值给e,继续上面的操作,找出在新数组的位置
e = next;
} while (e != null);
}
}
}
get操作
public V get(Object key) {
//null进行特殊处理
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
//通过hash和indexFor,找到key的下标,取出链表,在链表中查找hash和key相同的值
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
//如果找到就返回
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
//key为null是的特殊处理,key为null时,放在数组的下标0位置
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}
remove操作
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
//计算hash,key为null时未0,所以所处下标也为0
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
//通过两个引用,来删除元素
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
//保存next元素
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
//如果在链表头部就找到了,直接将next放到数组对应的位置上
if (prev == e)
table[i] = next;
else
//否则e.next赋值给prev.next,这样就从链表上删除了
prev.next = next;
e.recordRemoval(this);
//返回被删除的元素
return e;
}
//找不到,判断链表上下个元素,继续循环
prev = e;
e = next;
}
return e;
}
迭代器
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K,V> current; // current entry
HashIterator() {
//保存当前的modCount,如果在使用迭代器时,调用了put、remove等方法,导致modCount变化
//在执行迭代器的next和remove方法时就会抛ConcurrentModificationException异常,这就是快速失败
expectedModCount = modCount;
//找到数组里第一个不为null的下标
if (size > 0) { // advance to first entry
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
}
public final boolean hasNext() {
return next != null;
}
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
//找到下一个元素,如果链表已经到达尾部,则继续在数组里面找
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
//保存当前的元素,可以用于下面的remove操作
current = e;
return e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
//删除元素
HashMap.this.removeEntryForKey(k);
//然后修改modCount,所以迭代器里删除操作,不会导致快速失败
expectedModCount = modCount;
}
}
//这是三个迭代器的实现,主要是next返回不同
private final class EntryIterator extends HashIterator<Map.Entry<K,V>> {
public Map.Entry<K,V> next() {
return nextEntry();
}
}
private final class ValueIterator extends HashIterator<V> {
public V next() {
return nextEntry().value;
}
}
private final class KeyIterator extends HashIterator<K> {
public K next() {
return nextEntry().getKey();
}
}