HashMap是一个Entry数组实现的,本质上是一个数组,数组中的每一个位置上存放的都是Entry实体。
1.HashMap的基本构成:
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE; // HashMap的基本构成Entry数组
对应的Entry类的结构://HashMap的内部类,jdk8中使用的是Node类
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
final int hash; //hash值
Entry<K,V> next;
..........
}
(1).当新建一个HashMap的时候,就会初始化table数组
(2).每当往HashMap中存放key-value的时候,都会实例化一个Entry对象,根据hash值将Entry对象存放到table数组中
2.HashMap的存取
(1)哈希表
哈希表是由数组+链表组成的
当往HashMap中put值时,先根据key值调用hash()函数,得到该元素在数组中的位置(即存放到table中的下标的值),然后将Entry对象存放入HashMap中。
当key值得到的hash值相同时,如果key值相同(即key和hash值都相同),则将其对应的value覆盖;如果key值不同(hash值相同),则将值以链表的形式存放在链表的尾部。
(2)存放值----put()
public V put(K key, V value) {
if (key == null)
return putForNullKey(value); //null总是放在数组的第一个链表中
int hash = hash(key);//求取哈希值
int i = indexFor(hash, table.length);
//遍历i位置的链表
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;//返回old值,是原来存放的值
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e); //参数e,是Entry.next,实现链表结构
//如果size超过threshold,则扩充table大小。再散列
if (size++ >= threshold)
resize(2 * table.length);//默认扩容为原来的2倍
}
(3)确定数组位置---index
HashMap存取时,都需要计算当前key应该对应Entry[]数组的哪个下标,代码如下:
/*
* 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);
}
(4)HashMap的resize(rehash)----扩容
HashMap初始化的设置
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // HashMap初始容量大小(16)
static final int MAXIMUM_CAPACITY = 1 << 30; // HashMap最大容量
transient int size; // The number of key-value mappings contained in this map
static final float DEFAULT_LOAD_FACTOR = 0.75f; // 负载因子
HashMap的容量size乘以负载因子[默认0.75] = threshold; // threshold即为开始扩容的临界值
当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。
那么HashMap什么时候进行扩容呢?
当HashMap中的元素个数超过数组大小(数组总大小length,不是数组中个数size)*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12(这个值就是代码中的threshold值,也叫做临界值)的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。
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);
table = newTable;
//更新临界值
threshold = (int)(newCapacity * loadFactor);
}
//旧数组中元素往新数组中迁移
void transfer(Entry[] newTable) {
//旧数组
Entry[] src = table;
//新数组长度
int newCapacity = newTable.length;
//遍历旧数组
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);//放在新数组中的index位置
e.next = newTable[i];//实现链表结构,新加入的放在链头,之前的的数据放在链尾
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
4.HashMap线程不安全
HashMap源码
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
if (size++ >= threshold)
resize(2 * table.length);
}
在hashmap做put操作的时候会调用到以上的方法。现在假如A线程和B线程同时对同一个数组位置调用addEntry,两个线程会同时得到现在的头结点,
然后A写入新的头结点之后,B也写入新的头结点,那B的写入操作就会覆盖A的写入操作造成A的写入操作丢失。
2.synchronizedMap线程安全
Map m = Collections.synchronizedMap(new HashMap(...));
更好的选择:ConcurrentHashMap
java5中新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap。
ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的锁机制。
Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操作;
而ConcurrentHashMap中则是一次锁住一个桶。
ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。
这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。
上面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。