hashMap源码分析笔记:JDK7:数组+单链表;JDK8:数组+链表+红黑树(高性能的平衡树)
数据结构
数据结构 | 存储方案 | 实现 |
---|---|---|
数组 | 一片物理上连续的大小确定的存储空间 | int[] |
顺序表(对数组的封装) | 物理上连续;逻辑上连续;大小可以动态增加 | ArrayList |
链表 | 物理上不连续;逻辑上连续;可以动态增删节点 | LinkedList |
ArrayList在插入/删除的时候,首先将元素删除,然后再将其他所以的元素重新赋值(前移一位),不适合频繁删除/添加,效率低
LinkedList : pre<-data->next Node链表形式,增删时只需要修改指向,适用于频繁的增加删除
顺序表 | 链表 | |
---|---|---|
优点 | 物理上连续,所以寻找快捷。查找快 | 空间不连续,逻辑上连续。增删快 |
缺点 | 增删元素要大量移动数据。增删慢 | 物理上不连续,查找要轮询。查找慢 |
Hash表
数组 + 链表
// HashMap部分源码
transient Node<K,V>[] table; // 数组 + 链表
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
...
}
数组和链表如何组织工作
数组存储hash%10的下标值,链表存储实际value,当hash碰撞时,value以链表的形式存在
Hash的原理是什么
hash()位运算得出hash值
Hash的put方法原理
// JDK1.7
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* @param key key with which the specified value is to be associated
* @param value value to be associated with the specified key
* @return the previous value associated with <tt>key</tt>, or
* <tt>null</tt> if there was no mapping for <tt>key</tt>.
* (A <tt>null</tt> return can also indicate that the map
* previously associated <tt>null</tt> with <tt>key</tt>.)
*/
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
// 根据key计算其对应的hash值,然后再计算对应的数组下标
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 如果key值相等,替换value
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
// 如果不相等,在链表中向前添加元素
addEntry(hash, key, value, i);
return null;
}
// 防止低质量的哈希函数
/**
* Retrieve object hash code and applies a supplemental hash function to the
* result hash, which defends against poor quality hash functions. This is
* critical because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
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);
}
// 得到数组下标
/**
* Returns index for hash code h.
*/
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);
}
如果key是自己实现的类,一定要去实现hashCode和equals方法
参考:HashMap中如果key是自定义的类,为什么重写hashcode()和equals()
addEntry()
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);
}
// new Entry<>(hash, key, value, e);
// 以链表的形式向前添加,注意这个e,就是Entry有参构造中的n
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++;
}
// n --> next
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
Hash的get方法原理
// JDK1.7
public V get(Object key) {
if (key == null)
return getForNullKey();
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和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 != null && key.equals(k))))
return e;
}
return null;
}
hash碰撞
计算出来相同的hash值
通常有两类方法处理碰撞:开放寻址(Open Addressing)法和链接(Chaining)法。
HashMap和Hashtable
Hashtable只在hashMap的基础上添加了一个关键字:synchronized,即线程安全
其他
参考: