最近代码中经常出现一些Cache缓存,以减少大量用户请求导致数据服务器load过高的情况,而这些Cache缓存的底层实现数据结构支持都是Map,于是决定翻看以下各种map的源码。于是从HashMap开始。
static class Entry implements Map.Entry {
final K key; //键
V value; //值
Entry next; //链表下一结点指针
final int hash; //hash值,根据具体的Hash算法以key为输入进行计算获得
/**
* Creates new entry.
*/
Entry(int h, K k, V v, Entry n) {
value = v;
next = n;
key = k;
hash = h;
}
public final K getKey() {
return key;
}
public final V getValue() {
return value;
}
public final V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public final boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry e = (Map.Entry)o;
Object k1 = getKey();
Object k2 = e.getKey();
if (k1 == k2 || (k1 != null && k1.equals(k2))) {
Object v1 = getValue();
Object v2 = e.getValue();
if (v1 == v2 || (v1 != null && v1.equals(v2)))
return true;
}
return false;
}
public final int hashCode() {
return (key==null ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public final String toString() {
return getKey() + "=" + getValue();
}
/**
* This method is invoked whenever the value in an entry is
* overwritten by an invocation of put(k,v) for a key k that's already
* in the HashMap.
*/
void recordAccess(HashMap m) {
}
/**
* This method is invoked whenever the entry is
* removed from the table.
*/
void recordRemoval(HashMap m) {
}
}
上面的Entry类共包含了四个数据域,我在上面加上了中文注释,你会发现,Entry类是一个典型单链表节点类定义(将next视为下一节点指针,剩下的就是数据域),此时你应该已经能够想到Java中HashMap的避碰策略了吧,对就是采用外接链表的避碰策略。
/**
* Constructs an empty HashMap with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity 初始存储能力
* @param loadFactor the load factor 负载
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
// Find a power of 2 >= initialCapacity
int capacity = 1; //保证HashMap初始的存储能力都是2的平方数,可以看到这一点在indexFor函数中很有用
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor); //HashMap执行Resize操作的临界值
table = new Entry[capacity]; //初始化Entry table,也就是初始化所有的桶,每个桶都是一个链表
init();
}
/**
* 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 key, or
* null if there was no mapping for key.
* (A null return can also indicate that the map
* previously associated null with key.)
*/
public V put(K key, V value) {
if (key == null) //HashMap允许键为空(key=null)的情况出现
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length); //根据确定待插入/更新key-value对的bucket位置
for (Entry e = table[i]; e != null; e = e.next) { //遍历对应bucket中存储的链表
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //已存在相应的key值,更新
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue; //更新完毕返回key之前对应的value
}
}
modCount++;
addEntry(hash, key, value, i); //之前i位置对应的bucket处不存在key,则执行插入操作
return null;
}
/**
* Offloaded version of put for null keys
*/
private V putForNullKey(V value) {
for (Entry e = table[0]; e != null; e = e.next) {
if (e.key == null) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(0, null, value, 0); //如果之前不存在null为key的键值对,则最终还是调用插入操作
return null;
}
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length-1); //位运算散列
}
函数里使用位运算的操作进行散列映射,之所以可以这样做就是因为上文说的HashMap的存储能力Capacity始终都是2的幂。
/**
* Adds a new entry with the specified key, value and hash code to
* the specified bucket. It is the responsibility of this
* method to resize the table if appropriate.
*
* Subclass overrides this to alter the behavior of put method.
*/
void addEntry(int hash, K key, V value, int bucketIndex) {
Entry e = table[bucketIndex];
table[bucketIndex] = new Entry(hash, key, value, e); //仔细看Entry的构造函数,你会发现插入操作时在链表头进行的
if (size++ >= threshold)
resize(2 * table.length); //重建HashMap操作,每次重建capacity的值为原来的2倍,保证了始终为2的整数次幂
}
英文注释中说的很明白,就是这个函数要负责在链表的size也就是负载达到threshold临界值时重建Map,这要花费很多的时间,另外一点需要注意的时,HashMap的负载计算并不是按照有值存在的bucket位置的比例来计算的,其计算方式就是按照我们所存储的key-value对的个数比上capacity。
/**
* Rehashes the contents of this map into a new array with a
* larger capacity. This method is called automatically when the
* number of keys in this map reaches its threshold.
*
* If current capacity is MAXIMUM_CAPACITY, this method does not
* resize the map, but sets threshold to Integer.MAX_VALUE.
* This has the effect of preventing future calls.
*
* @param newCapacity the new capacity, MUST be a power of two;
* must be greater than current capacity unless current
* capacity is MAXIMUM_CAPACITY (in which case value
* is irrelevant).
*/
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE; //讲临界值设为最大,由此就不会再出现新的resize操作请求了
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable); //真正重建HashMap的执行函数
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry e = src[j];
if (e != null) {
src[j] = null;
do {
Entry next = e.next;
int i = indexFor(e.hash, newCapacity); //重新散列计算
e.next = newTable[i]; //在链表头插入
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
可以看到HashMap的重建,不但要申请新的内存空间而且对已有的key-value对都要进行重新hash散列计算。
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
for (Entry e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
相比与put来说,get操作就是对这个邻接表结构进行遍历查找操作了。还有相关的containsKey,getEntry等函数,大同小异,就不多说了。
/**
* Removes the mapping for the specified key from this map if present.
*
* @param key key whose mapping is to be removed from the map
* @return the previous value associated with key, or
* null if there was no mapping for key.
* (A null return can also indicate that the map
* previously associated null with key.)
*/
public V remove(Object key) {
Entry e = removeEntryForKey(key);
return (e == null ? null : e.value);
}
/**
* Removes and returns the entry associated with the specified key
* in the HashMap. Returns null if the HashMap contains no mapping
* for this key.
*/
final Entry removeEntryForKey(Object key) {
int hash = (key == null) ? 0 : hash(key.hashCode());
int i = indexFor(hash, table.length);
Entry prev = table[i];
Entry e = prev;
while (e != null) {
Entry next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
删除操作就是对单链表的删除~~