您的位置:首页 > 编程语言 > Java开发

Java记录 -71- HashMap源码剖析

2015-11-09 07:40 501 查看
HashMap底层维护着一个数组,我们向HashMap中所放置的对象实际上是存储在该数组当中。数组中每个元素都维护着一个链表,每个链表中的所有元素的hash值都一样。
HashMap的四个构造方法:
底层维护的数组 Entry[] table,数组默认大小为16,并为2的整数次幂;

底层数组存放的Entry对象,是HashMap的一个内部类,包含了key,value和next变量;

/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next;
final int hash;
......
}

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);
table = new Entry[capacity];
init();
}

public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}

public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR;
threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
table = new Entry[DEFAULT_INITIAL_CAPACITY];
init();
}

public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
putAllForCreate(m);
}


下面来看做关心的添加方法put:
当添加的key为null时,会取出table的第一个元素,并在该元素所领导的链表中寻找key==null的元素,有则替换,没有则添加;

key不为null时,需要计算key的hash值,调用一个函数式,根据经验计算出一个整数值来;

然后再根据计算出的hash值和数组table的长度计算出新添加元素在数组中的位置;

从底层数组中取出上面计算的位置的元素值,即Entry对象,从该对象领导的链表中寻找key相等的对象,有则替换;

如果上一步没有找到相等的对象,则添加新对象;将对象添加到底层数组table中,位置为上面计算得到的位置值,并将该对象的next指向该位置以前存放的元素;

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) {
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);
return null;
}
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);
}
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);
}

所有集合存放元素就是为了读取使用,下面来看看取元素:
get方法key为null时需要特殊处理,即从底层数组table的第一个元素中寻找key==null的值返回;

如果key不为null,则需要计算key的hash值,根据该值在table中取得一个元素,然后再在该元素所在的链表中寻找对应key的值返回;

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;
}
private V getForNullKey() {
for (Entry<K,V> e = table[0]; e != null; e = e.next) {
if (e.key == null)
return e.value;
}
return null;
}


常用的方法还有:

containsKey,remove,keySet,entrySet,values
这些方法都需要先在底层数组中找,然后遍历对应的链表。

根据设定的阀值,但达到阀值时,HashMap的底层数组将进行扩容。

新加的对象为什么添加到链的头部,而不是尾部?
为了提高性能,操作系统中有这么一个原则,认为最新添加到链中的元素下次访问的几率要大于以前的;因此添加到链表头部,如果下次访问该元素时就可以第一个访问到,设想如果放到尾部则需要变量整个链表来找到该元素。

HashMap添加一个对象的思路过程:
当向HashMap中put一对键值时,它会根据key的hashCode值计算出一个位置,该位置就是此对象准备往数组中存放的位置。
如果该位置没有对象存在,就将此对象直接放进数组当中;
如果该位置已经有对象存在了,则顺着此存在的对象的链开始寻找(Entry类有一个Entry类型的next成员变量,指向了该对象的下一个对象),如果此链上有对象的话,再去使用equals方法进行比较;
如果对此链上的某个对象的key比较为true,则证明已经存在该对象,此时将用新对象替换旧对象;
如果对此链上的所有对象的equals方法比较都为false,则将该对象放到数组当中,然后将数组中该位置以前存在的那个对象链接到此对象的后面。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: