HashMap
基于哈希表的 Map 接口的实现。
此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。
(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)
此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
(from JDK DOC)
缺省的 Map大小是 16. (这里要求必须是 2 的幂数,为什么一定是 2的幂数(16,32,64,128...下边有解释).
static final int DEFAULT_INITIAL_CAPACITY = 16;
缺省的比例因子是 0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
长度必须总是 2 的 幂数。
保存 K,V 对象的数组
transient Entry[] table;
/**
* The next size value at which to resize (capacity * load factor).
* @serial
*/
重设 Entry[] table 的阀值( = capacity * load factor)
int threshold;
当前比例因子
final float loadFactor;
记录修改次数
transient volatile int modCount;
/**
* 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;
while (capacity < initialCapacity)
capacity <<= 1;
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor); loadFactor 默认是 0.75. 所以 threshold 默认是 12
table = new Entry[capacity]; 按照给定的大小初化 Entry 数组。(默认情况下是 16 )
init();
}
看了下边这几行代码就能看出来 HashMap是怎么支持 null 做为 Key了。
static final Object NULL_KEY = new Object();
static <T> T maskNull(T key) {
return key == null ? (T)NULL_KEY : key;
}
static <T> T unmaskNull(T key) {
return (key == NULL_KEY ? null : key);
}
HashMap 对key 值的 hash 算法。
Object 类使用的 对象的 内部地址 提供 hashCode.
String 使用一个算法提供hashCode。(算法:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1])
当然这样一种算法是不能完全保证 得到的hashCode是唯一的。
String s1 = "结算";
String s2 = "罚款";
两个对象的hashcode是相同的(1038116);
经过hash之后是-423929781.
这个函数的算法我就看不大懂了。还有高人指教了。
static int hash(Object x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
根据hashCode值 值和 当前 Entry数组的长度计算 该数据应该在数组中的索引值
因为这里的算法 所以 capacity 大小必须为 2 的幂数。
举例,默认值是 16 , 其减1是 15, 二进制是 00001111 .
h 是一个特殊的 hash值。 与 0x0F进行 & 操作当然结果应该在 0x0F以内了。
但这里必须保证 length -1 全为 1. 否则话某些位可能永远是 0,那么Entry数组
的一个范围可能永远也不会被用到。
这里没使用取余计算应该出于效率考虑的吧。
static int indexFor(int h, int length) {
return h & (length-1);
}
public V get(Object key) {
Object k = maskNull(key); //检查是否是null Key
int hash = hash(k); //计算hash
int i = indexFor(hash, table.length); //计算索引
Entry<K,V> e = table[i]; //取得Entry对象
while (true) { //循环查找链上 对象
if (e == null)
return null;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
检查是否包含指定key的过程 与 取得这个key
所对应 value的过程基本一致。
看了这段代码之后发觉以前有一些不好的写代码习惯:
if(!containsKey(key)){
add();
}
v = get(key)
-----------------------
看来完全可以(这样的代码应该校率更高一些):
v = get(key)
if( null == v){
add();
}
public boolean containsKey(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (e != null) {
if (e.hash == hash && eq(k, e.key))
return true;
e = e.next;
}
return false;
}
public V put(K key, V value) {
K k = maskNull(key); //检查是否是null Key
int hash = hash(k); //计算hash
int i = indexFor(hash, table.length); //计算索引
for (Entry<K,V> e = table[i]; e != null; e = e.next) {//已经存在key的情况下处理方式
if (e.hash == hash && eq(k, e.key)) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this); //一个空操作的方法,不用理他
return oldValue;
}
}
modCount++; //记录修改次数
addEntry(hash, k, value, i); //不存在对应key时添加 K-V
return null;
}
//从下边代码可以看出,最新加入的 Entry 一定会在内部链表的最前边。
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) //如果当前的size 达到了 threshold(缺省参数12) 将会重设 整个 内部 table.length
resize(2 * table.length);
}
Entry的构造方法:
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
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); //计算新的阀值
}
将所有的 Entry 对象进行重新 hash计算/索引计算放置到 新的 table中。
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);
}
}
}