HashMap的size指mapping个数,使用Entry[]数据实现,每个Entry有个指向Entry对象的next变量,形成链表。Put()时首先通过hash(key.hashcode())获取key的hash,将key.hashcode()再hash加入了高位运算,防止低位相同时引起的hash冲突。
final int hash(Object k) {
int h = 0;
if (useAltHashing) {
if (k instanceofString) {
return sun.misc.Hashing.stringHash32((String)k);
}
h = hashSeed;
}
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);
}
Java8中的hash():只进行了一次高位运算。
static final int hash(Object key){
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h>>> 16);
}
然后通过indexof(hash)获取key在Entry[]中的位置
static int indexFor(int h, intlength) {
return h & (length-1);
}
这里使用数组长度-1与hash相与,等价于hash%数组长度,与运算快于求模运算。因为length等于2的n次方幂,这也是为什么hashmap数组长度是2的n次方幂的原因。
找到key在数组中的位置后,然后通过key的equals()查找key应该在链表中的位置
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
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);
returnoldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
如果key已经存在,直接替换掉value值,返回旧的value值;如果链表中不存在相等的key,通过addEntry(hash, key, value, i)添加一个新节点,并且将modCount++;modCount表示HashMap已被结构化修改的次数。结构修改是改变HashMap中的映射数量或者修改其内部结构(例如rehash)的修改次数。此字段用于使HashMap的集合视图上的迭代器失效。 (请参阅ConcurrentModificationException)。
void addEntry(int hash, K key, V value, int bucketIndex){
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);
}
如果当前size >= threshold&& null != table[bucketIndex],将调用resize(newCapacity)扩容。
如果不是,直接createEntry()
void createEntry(int hash, K key, Vvalue, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
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];
boolean oldAltHashing = useAltHashing;
useAltHashing |= sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = oldAltHashing ^ useAltHashing;
transfer(newTable, rehash);
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY+ 1);
}
void transfer(Entry[] newTable, booleanrehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null!= e) {
Entry<K,V>next = e.next;
if(rehash) {
e.hash= null == e.key ? 0 : hash(e.key);
}
inti = indexFor(e.hash, newCapacity);
e.next= newTable[i];//标记【1】
newTable[i]= e;
e = next;
}
}
}
注释标记[1]处,将newTable[i]的引用赋给了e.next,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置;这样先放在一个索引上的元素终会被放到Entry链的尾部(如果发生了hash冲突的话);
loadFactory越大,扩容次数少,对空间利用更充分,然而链表太长,查找效率变低;
loadFactory越小,扩容次数多,数据之间就会很松散,对空间造成严重浪费。
因此Java8对hashMap进行了优化,当一条链表上的元素个数超过了8个,链表就转换为红黑树。