HashMap源码解析
HashMap是继承自AbstractMap<K,V>,那我们先来看一个AbstractMap<K,V>
AbstractMap<K,V>是接口Map<K,V>的实现,那我们看一个Map<K,V>
Map<K,V>定义了一系列方法的接口,AbstractMap<K,V>对Map<K,V>进行了方法的抽象,基本
上实现在Map的方法,留了一个接口
以下是子类的这现方法:
有人可能郁闷了,这怎么实现的?,我们来看一个EntrySet类
这里使用了一个AbstractSet,抽象集合类型,使用Hashmap的属性来初始化Set中的四个方法,从而达到初始化entrySet的目的.
我们来看一个这个Iterator:
这个遍历器实现了hasNext(),Hashmap下一个元素是否为空,nextEntry()为Itarator的next()方法.
这HashIterator()方法初始化好了第一个元素,从HashMap的最后一个数开始查找,只到第一个不为空的元素,设置为next
此下标的设置为index,在调用next()时,首先把next附值给e,然后再调用e.next,如果e.next不为空,则下一个元素为同一木桶链,
如果为空,则查找数组table中的下一个元素.然后把当前元互设置为e,下一个元素设置为e.next(table[index]).这里还重写了Itrator的
remove()方法,移除当前key实体.
这是移除一个木桶的方法,如果当前待移除的木桶对象和当前待移除的木桶的上一个元素相同,则移走这个木桶,当前下标为下一个元素的对象,则当前元素的和下一个元素不相同,则当前的下一个为下一个元素.当前素和下一个元素相同的情况只有在一级木桶的时候出现.
有人称table对象为一个视图,我称之为木桶,如下图:
[img]http://dl.iteye.com/upload/attachment/265348/26be0150-f680-3a10-8573-d849ce656876.jpg[/img]
蓝色框里的为一级木桶,下面的为木桶链.其实HashMap使用的就是这种原理实现的.
知道了HashMap是如何实例化实体对象后,以下我来介绍一个HashMap是如何put,get相对就应的实体对象.
这是put方法,很简单,首先通过你put进来的对象的hashCode()(经过位运算)和table对象的大小进行位&运算得到一个0到table.length的下标,如果此下标有值,并且key也相同,则发生替代行为,如果此下标没值,或者有值,则添加这个实体对象.
这里添加这个实体对象,如果大小不够的话,会自动添加Hashmap的大小.这里的bucketIndex是一个木桶,可能只有一个也可能是一个链.
这是实体对象,本身就有一个next指向实体对象本身,这就形成了一个木桶链.
以上是put方法,下面介绍一个get方法:
通过key的hashCode()换算,直接到得对象在table[]中的index.得到对象后,循环得到这个对象链直到e.hash=hash,eq(k,e.key).然后返回这个值,如果找到最后都没有则返回null.
HashMap之后以速度会快,是因为对象的HashCode()都是唯一的,经过优化的hash算法可以实现对象链的减少,能够快速定位到对象本身[数组下标].
2010-10-14 日添加说明如下:
HashMap:
其实现方式
transient Entry[] table;
采用实体数组的方式存储,以一个hashCode[经过换算后的数组下标]
具体算法如下:
int hash = hash(key.hashCode());
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
这里先根据对象本身的hashCode()方法,然后再移位操作,最后执行与操作,生成数组下标。
有人会问了,原来HashMap就是一个数组啊,对了,就是数组,只不过他在数组的级别上稍微做了改变,比如其下标的生成方式,还有一点也是HashMap特有的,散列表:
如果数组下标相同,那么这个数组的值就会被重写,HashMap也一样:
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;
}
}
这里的e.value = value就是把原先的值改为新的值,这里的e是对象的引用.Entry是一个Map的内部封装类型,其作为对象存储在table的值中.
接下来当hash值在同一个范围,key不同时,那么生成的下标原先存在,而值却不同,接下来就看HashMap的散列表:
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);
}
如果pucketIndex[根据hash生成的数组下标]在table中不存在,那么e就为空,也就是next为空。Table[pucketIndex]就为当前对象。如果不为空,那么原先存在于这个下标下的元素就成为当前的next,这个列表可能会不停的加长,10个,20个….
如果你的HashMap效率很低,那么你要考虑是不是对象的HashCode()方法有问题.
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
HashMap是继承自AbstractMap<K,V>,那我们先来看一个AbstractMap<K,V>
public abstract class AbstractMap<K,V> implements Map<K,V> {
AbstractMap<K,V>是接口Map<K,V>的实现,那我们看一个Map<K,V>
public interface Map<K,V> {
Map<K,V>定义了一系列方法的接口,AbstractMap<K,V>对Map<K,V>进行了方法的抽象,基本
上实现在Map的方法,留了一个接口
public abstract Set<Entry<K,V>> entrySet();
以下是子类的这现方法:
/**
* Returns a collection view of the mappings contained in this map.
Each
* element in the returned collection is a <tt>Map.Entry</tt>. The
* collection is backed by the map, so changes to the map are
reflected in
* the collection, and vice-versa.
*/
public Set<Map.Entry<K,V>> entrySet() {
Set<Map.Entry<K,V>> es = entrySet;
return (es != null ? es : (entrySet = (Set<Map.Entry<K,V>>)
(Set) new EntrySet()));
}
有人可能郁闷了,这怎么实现的?,我们来看一个EntrySet类
private class EntrySet extends AbstractSet/*<Map.Entry<K,V>>*/ {
public Iterator/*<Map.Entry<K,V>>*/ iterator() {
return newEntryIterator();
}
public boolean contains(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<K,V> e = (Map.Entry<K,V>) o;
Entry<K,V> candidate = getEntry(e.getKey());
return candidate != null && candidate.equals(e);
}
public boolean remove(Object o) {
return removeMapping(o) != null;
}
public int size() {
return size;
}
public void clear() {
HashMap.this.clear();
}
}
这里使用了一个AbstractSet,抽象集合类型,使用Hashmap的属性来初始化Set中的四个方法,从而达到初始化entrySet的目的.
我们来看一个这个Iterator:
private abstract class HashIterator<E> implements Iterator<E> {
Entry<K,V> next; // next entry to return
int expectedModCount; // For fast-fail
int index; // current slot
Entry<K,V> current; // current entry
HashIterator() {
expectedModCount = modCount;
Entry[] t = table;//table数据过去了
int i = t.length;
Entry<K,V> n = null;
if (size != 0) {
while (i > 0 && (n = t[--i]) == null)
;
}
next = n;
index = i;
}
public boolean hasNext() {
return next != null;
}
Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
Entry<K,V> n = e.next;
Entry[] t = table;
int i = index;
while (n == null && i > 0)
n = t[--i];
index = i;
next = n;
return current = e;
}
public void remove() {
if (current == null)
throw new IllegalStateException();
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Object k = current.key;
current = null;
HashMap.this.removeEntryForKey(k);
expectedModCount = modCount;
}
}
这个遍历器实现了hasNext(),Hashmap下一个元素是否为空,nextEntry()为Itarator的next()方法.
这HashIterator()方法初始化好了第一个元素,从HashMap的最后一个数开始查找,只到第一个不为空的元素,设置为next
此下标的设置为index,在调用next()时,首先把next附值给e,然后再调用e.next,如果e.next不为空,则下一个元素为同一木桶链,
如果为空,则查找数组table中的下一个元素.然后把当前元互设置为e,下一个元素设置为e.next(table[index]).这里还重写了Itrator的
remove()方法,移除当前key实体.
/**
* Removes and returns the entry associated with the specified key
* in the HashMap. Returns null if the HashMap contains no mapping
* for this key.
*/
Entry<K,V> removeEntryForKey(Object key) {
Object k = maskNull(key);
int hash = hash(k);
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;
if (e.hash == hash && eq(k, e.key)) {
modCount++;
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
这是移除一个木桶的方法,如果当前待移除的木桶对象和当前待移除的木桶的上一个元素相同,则移走这个木桶,当前下标为下一个元素的对象,则当前元素的和下一个元素不相同,则当前的下一个为下一个元素.当前素和下一个元素相同的情况只有在一级木桶的时候出现.
transient Entry[] table;
有人称table对象为一个视图,我称之为木桶,如下图:
[img]http://dl.iteye.com/upload/attachment/265348/26be0150-f680-3a10-8573-d849ce656876.jpg[/img]
蓝色框里的为一级木桶,下面的为木桶链.其实HashMap使用的就是这种原理实现的.
知道了HashMap是如何实例化实体对象后,以下我来介绍一个HashMap是如何put,get相对就应的实体对象.
public V put(K key, V value) {
K k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
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);
return null;
}
这是put方法,很简单,首先通过你put进来的对象的hashCode()(经过位运算)和table对象的大小进行位&运算得到一个0到table.length的下标,如果此下标有值,并且key也相同,则发生替代行为,如果此下标没值,或者有值,则添加这个实体对象.
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);
}
这里添加这个实体对象,如果大小不够的话,会自动添加Hashmap的大小.这里的bucketIndex是一个木桶,可能只有一个也可能是一个链.
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
final int hash;
Entry<K,V> next;
Entry(int h, K k, V v, Entry<K,V> n) {
value = v;
next = n;
key = k;
hash = h;
}
public K getKey() {
return HashMap.<K>unmaskNull(key);
}
public V getValue() {
return value;
}
public V setValue(V newValue) {
V oldValue = value;
value = newValue;
return oldValue;
}
public 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 int hashCode() {
return (key==NULL_KEY ? 0 : key.hashCode()) ^
(value==null ? 0 : value.hashCode());
}
public String toString() {
return getKey() + "=" + getValue();
}
void recordAccess(HashMap<K,V> m) {
}
void recordRemoval(HashMap<K,V> m) {
}
}
这是实体对象,本身就有一个next指向实体对象本身,这就形成了一个木桶链.
以上是put方法,下面介绍一个get方法:
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
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的hashCode()换算,直接到得对象在table[]中的index.得到对象后,循环得到这个对象链直到e.hash=hash,eq(k,e.key).然后返回这个值,如果找到最后都没有则返回null.
HashMap之后以速度会快,是因为对象的HashCode()都是唯一的,经过优化的hash算法可以实现对象链的减少,能够快速定位到对象本身[数组下标].
2010-10-14 日添加说明如下:
HashMap:
其实现方式
transient Entry[] table;
采用实体数组的方式存储,以一个hashCode[经过换算后的数组下标]
具体算法如下:
int hash = hash(key.hashCode());
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
static int indexFor(int h, int length) {
return h & (length-1);
}
这里先根据对象本身的hashCode()方法,然后再移位操作,最后执行与操作,生成数组下标。
有人会问了,原来HashMap就是一个数组啊,对了,就是数组,只不过他在数组的级别上稍微做了改变,比如其下标的生成方式,还有一点也是HashMap特有的,散列表:
如果数组下标相同,那么这个数组的值就会被重写,HashMap也一样:
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;
}
}
这里的e.value = value就是把原先的值改为新的值,这里的e是对象的引用.Entry是一个Map的内部封装类型,其作为对象存储在table的值中.
接下来当hash值在同一个范围,key不同时,那么生成的下标原先存在,而值却不同,接下来就看HashMap的散列表:
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);
}
如果pucketIndex[根据hash生成的数组下标]在table中不存在,那么e就为空,也就是next为空。Table[pucketIndex]就为当前对象。如果不为空,那么原先存在于这个下标下的元素就成为当前的next,这个列表可能会不停的加长,10个,20个….
如果你的HashMap效率很低,那么你要考虑是不是对象的HashCode()方法有问题.