最近代码中经常出现一些Cache缓存,以减少大量用户请求导致数据服务器load过高的情况,而这些Cache缓存的底层实现数据结构支持都是Map,于是决定翻看以下各种map的源码。于是从HashMap开始。
static class Entry<K,V> implements Map.Entry<K,V> { final K key; //键 V value; //值 Entry<K,V> next; //链表下一结点指针 final int hash; //hash值,根据具体的Hash算法以key为输入进行计算获得 /** * Creates new entry. */ Entry(int h, K k, V v, Entry<K,V> 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<K,V> m) { } /** * This method is invoked whenever the entry is * removed from the table. */ void recordRemoval(HashMap<K,V> m) { } }上面的Entry类共包含了四个数据域,我在上面加上了中文注释,你会发现,Entry类是一个典型单链表节点类定义(将next视为下一节点指针,剩下的就是数据域),此时你应该已经能够想到Java中HashMap的避碰策略了吧,对就是采用外接链表的避碰策略。
/** * Constructs an empty <tt>HashMap</tt> 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 <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 (key == null) //HashMap允许键为空(key=null)的情况出现 return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length); //根据确定待插入/更新key-value对的bucket位置 for (Entry<K,V> 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<K,V> 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<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(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<K,V> e = src[j]; if (e != null) { src[j] = null; do { Entry<K,V> 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<K,V> 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 <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 remove(Object key) { Entry<K,V> 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<K,V> removeEntryForKey(Object key) { int hash = (key == null) ? 0 : hash(key.hashCode()); int i = indexFor(hash, table.length); Entry<K,V> prev = table[i]; Entry<K,V> e = prev; while (e != null) { Entry<K,V> 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; }删除操作就是对单链表的删除~~