Java hashmap
本文基于Java 1.7 进行分析,这次分析主要是作为自己的笔记留存。
hashmap的基本原理
HashMap基于哈希算法,使用put存放对象,get获取对象,在初始设置hashmap容量时,尽量设置为2的幂次方
- get,用户传入K,然后根据hash值,计算位置,确定在桶中的位置,然后获取value,中间需要通过K的equals()方法,hash值,key是否相等,来确定是否是取到的值。
- put,用户将K-V传递给put后,通过K对象的hashcode()方法来计算本次存储的hash值,然后通过indexFor()方法,获取需要存入hash桶的位置,hash桶是一个数组,默认初始大小是16,里面存放的是Entry
put
public V put(K key, V value) {
//如果table为空,使用默认值设置talbe数组和门限threshold
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//key可以为空,处理key为空的情况,从这里可以看出为什么HashMap允许key为空
if (key == null)
return putForNullKey(value);
//通过key获取hash值,hash方法里面用到了key的hashcode()方法
int hash = hash(key);
//获取在桶中的位置
int i = indexFor(hash, table.length);
//遍历hash桶,判断是否已经有值,本次是重新写入,还是已经hash冲突了
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
//hash值相同且对象相同时,认为是重新写入值,覆盖旧值,hash冲突时判断对象是否相等,来区分是否覆盖写入
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
//在hash桶中添加对象以及解决hash冲突
addEntry(hash, key, value, i);
return null;
}
通过上文源码以及注释可以看到,在使用hashmap时,需要作为key的对象,必须实现了hashcode和equals两个方法,保证key的唯一性,因为在hashmap中,使用hashcode来确定位置,使用equals来确定是否重复,以及区分hash冲突的情况,因为hash冲突时,一般是hashcode值一样导致,但是两个对象通过equals方法,是可以确实是不等的,这也是为什么String为什么适合作为key的原因。
在put中有一个addEntry方法,这个方法用来将k-v存入hash桶,包括了hash冲突时的处理。
void addEntry(int hash, K key, V value, int bucketIndex) {
//如果桶超过限制,一般为容量*0.75,就resize桶大小,扩展为2 * 当前大小
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
//创建Entry对象,并写入桶中
createEntry(hash, key, value, bucketIndex);
}
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++;
}
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
通过上文代码可以看到,Entry是实际存储K-V的对象,同时也是用来解决hash冲突的单链表,当我们遇到hash冲突时,例如A,B,A已经存在桶中13这个位置,A,B hash冲突,这是,就是将B写入到A目前的位置,同时将A放入Entry的next的位置,链接起来。
get
public V get(Object key) {
if (key == null)
return getForNullKey();
//获取Entry<K,V>对象
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
//如果size为0,返回null
if (size == 0) {
return null;
}
//计算hash值
int hash = (key == null) ? 0 : hash(key);
//在桶中指定位置,遍历Entry链表,获取需要的值
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;
}
//未找到 返回null
return null;
}