HashMap实现了Map接口,是基于哈希表的非同步实现,它以键值对(key-value)的形式存储元素,键和值都可以为null。HashMap不保证映射的顺序,特别是它不保证该顺序不变。
HashMap底层实现
它的底层是通过数组实现的,数组的每个元素是链表,由Entry内部类实现,Entry重要的属性有 key,value,next。
其中Java源码如下:
transient Entry[] table;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
……
}
可以看出,Entry就是数组中的元素,每个 Entry 其实就是一个key-value对,它持有一个指向下一个元素的引用,这就构成了链表。
HashMap构造函数
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY,DEFAULT_LOAD_FACTOR);
}
HashMap有3个构造函数,以上是无参数的构造函数,它们完成的工作就是对loadFactor和threshold这两个成员属性赋值。
threshold: 初始容量,表示哈希表中桶的数量,即初始时数组的大小。 loadFactor:负载因子,默认为0.75,表示当前哈希表的最大填满比例。当threshold * loadFactor < 当前哈希表中桶数目时,哈希表的threshold需要扩大为当前的2倍。
HashMap的存取实现
存储时,HashMap其实首先会判断key是否为null,如果为null, 则将该key-value键值对插入到table中索引为0的位置;
如果key不为null,则计算key的hash值,之后根据该hash值生成它在table中的下标索引。遍历此下标对应的链表,看是否存在该key值,有则替换,无则在链表头部插入新的元素。
存储:put(key,value)
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);
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;
}
}
modCount++;
addEntry(hash,key,value,i);
return null;
}
读取时,判断key是否为null,如果是则直接查找下标为0的链表中key为null的Entry的value值;
否则,计算key的hash值,根据hash值找到它在table中的索引,遍历该索引对应的链表,查找key值对应的Entry的value值。
读取:get(key)
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);
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;
}
最后一个if里面的判断条件,(k = e.key) == key || (key != null && key.equals(k))牵扯出一个很经典的经验:重写equals方法时一定要重写hashCode方法。
因为默认对象的hashCode值一般是内存地址对应的数字,所以不同的对象其hashCode值一般不同。
所以当重写equals方法时,如果我们想让两个对象逻辑上相同,不重写hashCode的话,就会在HashMap中出现问题,两个对象明明逻辑上相同,但是因为默认hashCode值不同,就会认为两个对象不同。所以当用HashMap存储对象的时候,一定要重写hashCode()方法。
hashCode()方法重写规则:当equlas方法返回true时,两个对象的hashCode也相同。 (编辑:北几岛)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|