HashMapHashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。map = new HashMap (); map.put("语文" , 80.0); map.put("数学" , 89.0); map.put("英语" , 78.2);
当程序执行 map.put("语文" , 80.0); 时,系统将调用"语文"的 hashCode() 方法得到其 hashCode 值――每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置。
我们可以看 HashMap 类的 put(K key , V value) 方法的源代码:
// 当向HashMap中添加mapping时,由key的hashCode值决定Entry对象的存储位置,当两个 key的hashCode相同时,
// 通过equals()方法比较,返回false产生Entry链,true时采用覆盖行为
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry
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;
}
当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。
上面方法提供了一个根据 hashCode() 返回值来计算 Hash 码的方法:并且会调用 indexFor() 方法来计算该对象应该保存在 table 数组的哪个索引,源代码如下:
static int hash(int h) {
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
length是table.length,所以数组的长度总是 2 的 n 次方,通过h&(length-1)计算后,保证计算得到的索引值位于 table 数组的索引之内,计算的函数并不总是这样的,好的计算函数应该还会让索引值尽量平均分布到数组中。
当调用put()方法向 HashMap 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false),而且新添加的 Entry 位于 Entry 链的头部,这是通过调用addEntry()方法来完成的, 源码如下:void addEntry(int hash, K key, V value, int bucketIndex){
Entry
e = table[bucketIndex]; // 获取指定 bucketIndex 索引处的 Entry
table[bucketIndex] = new Entry
(hash, key, value, e); // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry // 如果 Map 中的 key-value 对的数量超过了极限 if (size++ >= threshold) resize(2 * table.length); // 把 table 对象的长度扩充到 2 倍 }
程序总是将新添加的 Entry 对象放入 table数组的 bucketIndex 索引处――如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链),如果 bucketIndex 索引处没有 Entry 对象, e 变量是 null,也就是新放入的 Entry 对象指向 null,也就是没有产生 Entry 链。
接下来看一下HashMap是如何读取的。 get()方法的源代码如下:
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
// 搜索该Entry链的下一个Entry,有多个Entry链时必须顺序遍历,降低了索引的速度
// 如果Entry链过长,说明发生“Hash”冲突比较频繁,需要采用新的算法或增大空间
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;
}
当 HashMap 的每个 bucket 里存储的 Entry 只是单个 Entry 时的 HashMap 具有最好的性能:当程序通过 key 取出对应 value 时,只要先计算出该 key 的 hashCode() 返回值,在根据该 hashCode 返回值找出该 key 在 table 数组中的索引,然后循环遍历查找 hash值相同,key值相同的value。
下面来看一下HashMap是如何实现如下的三个方法的,源代码如下:
public SetkeySet() { Set ks = keySet; return (ks != null ks : (keySet = new KeySet())); } public Collection values() { Collect