如果说ArrayList的核心是数组,那么HashMap的核心就是链表。因此要想了解HashMap source code,首先来了解一下LinkedLists:
链表的节点通常分为两部分:the data和a reference to the next node 。其中链表由分为:单向、双向、循环链表,这里只讨论单向链表。
针对单向链表来说,一头一尾最特殊了。头节点的引用虽然指向下个一节点,但是在新增头节点例子:
尾节点的引用是指向null,新增尾节点的例子:
综上所述可以看出,链表在添加或删除一个数据的时候不像数组那样需要每个元素都跟着顺移,它只需要改变引用即可,所以链表的特征是新增、删除数据快,而查询相对数组来说就会慢一些,如需详细了解可参考:LinkeLists
对链表有一些基本的了解之后,我们再来看 HashMap,首先我们看下put(K k, V value)方法:
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for this 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 previous value associated with specified key, or <tt>null</tt>
* if there was no mapping for key. A <tt>null</tt> return can
* also indicate that the HashMap previously associated
* <tt>null</tt> with the specified key.
*/
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<K,V> 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;
}
private V putForNullKey(V value) {
int hash = hash(NULL_KEY.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
if (e.key == NULL_KEY) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, (K) NULL_KEY, value, i);
return null;
}
/**
* Add 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);
if (size++ >= threshold)
resize(2 * table.length);
}
关键知识点:HashCode
我们知道对象在内存中都会有一个地址来对应,而放入map的对象对应的内存地址就是经过计算出来的hashcode,所以我们在今后使用HashMap的时候需要注意这个key对象是否真的相等。
要判断一个对象是否相等,我们一般会用到java.lang.Object中的equals(Object o),上面一行也提到了hashmap中是用hashcode来作为地址,所以必须要重写hashCode方法才能判断是否真的相等。
详细概念可参考:Java hashCode() - Wikipedia, the free encyclopedia 、 The 3 things you should know about hashCode()
详细例子可参考:Java 中正确使用 hashCode 和 equals 方法
题外话:
我在看HashMap source code的时候,发现里面有一些静态的常量,但都是protected类型,我再那里纠结了半天为什么不能访问,为此我还提了个问题去Stack Overflow,我的第一个stack overflow就这么逗比的上去了,想想我除了“呵呵”,不知道说什么了。。