jdk1.7的HashMap源码学习笔记,分析主要写在注释里,写的比较啰嗦,大佬们看看就好
transient Entry[] table = (Entry[]) EMPTY_TABLE;
static class Entry implements Map.Entry {
final K key;
V value;
Entry next;
int hash;
……
}
总体来说就是数组+链表
有四个,但最终都是调的这个
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 = initialCapacity;
init(); //空方法,作用是为子类提供扩展的可能性,实际上LinkedHashMap中覆盖了这个方法
}
第一个参数是初始容量,第二个是加载因子,threshold是一个阈值,map长度达到这个值后就会扩容,但在此时threshold中暂存的是初始容量
看下其中Float.isNaN这个方法:
static public boolean isNaN(float v) {
return (v != v);
}
这里的方法参数类型为基本类型float,当传入例如 0.0f / 0.0f
的值时,jvm在处理浮点数运算时,不会抛出任何运行时异常,当一个操作产生溢出时,将会使用NaN值来表示,NaN是唯一与自己不相等的值,这里要区别于Float类型(比较的是引用)
另外看一下参数类型为map的构造方法,类似于clone,后面会分析
public HashMap(Map<? extends K, ? extends V> m) {
//保证初始容量*加载因子要大于map长度
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
//扩充容量,threshold从初始容量修改为阈值,实例化table,初始化hashseed
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);
}
//取大于等于number且最接近的2^n
private static int roundUpToPowerOf2(int number) {
// assert number >= 0 : "number must be non-negative";
return number >= MAXIMUM_CAPACITY
? MAXIMUM_CAPACITY
: (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
/**
* 取二进制形式左边的最高一位后面全部补零的值
* 如果是0,返回0;如果是负数,则返回-2^31;如果是正数,返回小于 * 等于i且最接近的2^n)
*/
public static int highestOneBit(int i) {
// HD, Figure 3-1
i |= (i >> 1); //最高1位右边1位变为1
i |= (i >> 2); //最高2位右边2位变为1
i |= (i >> 4); //最高4位右边4位变为1
i |= (i >> 8); //最高8位右边8位变为1
i |= (i >> 16); //最高16位右边16位变为1
return i - (i >>> 1); //去掉最高位右边的所有1
}
//初始化hashseed,默认为0,异或用的很巧妙
final boolean initHashSeedAsNeeded(int capacity) {
boolean currentAltHashing = hashSeed != 0;
boolean useAltHashing = sun.misc.VM.isBooted() &&
(capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean switching = currentAltHashing ^ useAltHashing;
if (switching) {
hashSeed = useAltHashing
? sun.misc.Hashing.randomHashSeed(this)
: 0;
}
return switching;
}
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
//容量达到上限后不再扩容
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
//遍历旧的数组
for (Entry<K,V> e : table) {
//链表遍历
while(null != e) {
Entry<K,V> next = e.next;
//扩容后达到阈值(非threshold)则重新计算hash
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
//计算新的所属下标,并插入到其中链表的表头
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
先看寻找数组下标的方法,length为2^n时,h & (length-1)
就是h对length取模,位运算比模运算效率高得多,而且可以很好的解决负数的问题。这也是hashmap容量一定要是2的幂次方的原因。
X % 2^n = X & (2^n – 1)
假设n为3,则2^3 = 8,表示成2进制就是1000。2^3 = 7 ,即0111。
此时X & (2^3 – 1) 就相当于取X的2进制的最后三位数。
从2进制角度来看,X / 8相当于 X >> 3,即把X右移3位,此时得到了X / 8的商,而被移掉的部分(后三位),则是X % 8,也就是余数。
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// 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);
}
这段代码是为了对key的hashCode进行扰动计算,防止不同hashCode的高位不同但低位相同导致的hash冲突。简单点说,就是为了把高位的特征和低位的特征组合起来,降低哈希冲突的概率,也就是说,尽量做到任何一位的变化都能对最终得到的结果产生影响。具体原理这里不做讨论。
需要结合前面非空map扩容方法阅读
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
//判断key是否已存在,有需要时可以重写K类型的equals方法
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
/**
* 修改次数,与线程安全相关,只在迭代器中使用,
* 在使用迭代器的过程中有其他线程修改了map,将抛出异常
*/
addEntry(hash, key, value, i);
modCount++;
return null;
}
//key为null时hash为0,所以存到talbe[0]中
private V putForNullKey(V value) {
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;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
//长度达到阈值且数组当前位置已有值时扩容成2倍
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
//同样是头插法,Entry构造函数里实现
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
clone方法和拷贝构造函数都会调用以下方法,都为浅拷贝
private void putAllForCreate(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
putForCreate(e.getKey(), e.getValue());
}
private void putForCreate(K key, V value) {
int hash = null == key ? 0 : hash(key);
int i = indexFor(hash, table.length);
/**
* Look for preexisting entry for key. This will never happen for
* clone or deserialize. It will only happen for construction if the
* input Map is a sorted map whose ordering is inconsistent w/ equals.
*/
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
e.value = value;
return;
}
}
//传递的是引用,所以是浅拷贝
createEntry(hash, key, value, i);
}
查找相比插入来说比较简单
public V get(Object key) {
if (key == null)
return getForNullKey(); //和put类似,从table[0]中查找
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
//根据hash找到数组下标并遍历链表
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 != null && key.equals(k))))
return e;
}
return null;
}
这个一般用的不多
public V remove(Object key) {
Entry<K,V> e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i]; //前缀节点
Entry<K,V> e = prev; //当前节点
//链表遍历
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
//判断是否是头结点
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}