HashMap源码:
首先看看HashMap到底是一个什么样的类,它继承了哪些类。
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
由上面HashMap头部可以看到,HashMap是AbstractMap的子类、Map的子接口,并且实现了其他的一些额外的东西,Cloneable和Serializable,我们先看看AbstractMap的部分源码。
package asdf;
import java.util.*;
/**
* Created by ping.miao on 2015/8/3.
*/
public abstract class AbstractMap<K,V> implements Map<K,V> {
/**
* 构造方法
*/
protected AbstractMap() {
}
/**
* 得到当前map的长度
*/
public int size() {
return entrySet().size();
}
/**
* 判断当前map是否为空
* 假如当前map的长度为0是那么该map为空,返回true
*/
public boolean isEmpty() {
return size() == 0;
}
/**
* 判断map是否包含一个或者多个key所对应的value等于参数中给出的value
* 及判断满足条件(value == null ? v == null : value.equals(v))的v
* 是否存在,存在返回true,不存在返回false
*/
public boolean containsValue(Object value) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (value==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getValue()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (value.equals(e.getValue()))
return true;
}
}
return false;
}
/**
* 判断map中是否存在一个key值与参数给出的key值相等
* 及判断满足条件(key == null ? k == null : key.equals(k))的k
* 是否存在,存在返回true,不存在返回false
*/
public boolean containsKey(Object key) {
Iterator<Map.Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return true;
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return true;
}
}
return false;
}
/**
* 根据当前key值得到对应的value
*/
public V get(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
/**
* 删除map中的键值对(key,value),并返回未删除前key所对应的value
*/
public V remove(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
Entry<K,V> correctEntry = null;
if (key==null) {
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
if (e.getKey()==null)
correctEntry = e;
}
} else {
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
correctEntry = e;
}
}
V oldValue = null;
if (correctEntry !=null) {
oldValue = correctEntry.getValue();
i.remove();
}
return oldValue;
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
public void clear() {
entrySet().clear();
}
transient volatile Set<K> keySet = null;
transient volatile Collection<V> values = null;
public Set<K> keySet() {
if (keySet == null) {
keySet = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
private Iterator<Entry<K,V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public K next() {
return i.next().getKey();
}
public void remove() {
i.remove();
}
};
}
public int size() {
return AbstractMap.this.size();
}
public boolean isEmpty() {
return AbstractMap.this.isEmpty();
}
public void clear() {
AbstractMap.this.clear();
}
public boolean contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
}
return keySet;
}
public Collection<V> values() {
if (values == null) {
values = new AbstractCollection<V>() {
public Iterator<V> iterator() {
return new Iterator<V>() {
private Iterator<Map.Entry<K,V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public V next() {
return i.next().getValue();
}
public void remove() {
i.remove();
}
};
}
public int size() {
return AbstractMap.this.size();
}
public boolean isEmpty() {
return AbstractMap.this.isEmpty();
}
public void clear() {
AbstractMap.this.clear();
}
public boolean contains(Object v) {
return AbstractMap.this.containsValue(v);
}
};
}
return values;
}
public abstract Set<Entry<K,V>> entrySet();
/**
* 判断map与o是否相等,相等返回true,不相等返回false
*/
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Map))
return false;
Map<K,V> m = (Map<K,V>) o;
if (m.size() != size())
return false;
try {
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
if (value == null) {
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
/**
* 计算Map的hashCode,它的值为map的entrySet中每一个元素的hashcode的和
*/
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
}
底层实现:
static final Entry<?,?>[] EMPTY_TABLE = {};
transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;
构造方法:
/**
* 无参构造方法,使用默认容量16,填装因子0.75,构造一个空的HashMap
*/
public HashMap() {
this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
}
/**
* 以指定的initialCapacity和loadFactor构造一个空的HashMap
*/
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);
this.loadFactor = loadFactor;
threshold = initialCapacity;
init();
}
/**
* 以指定的initialCapacity,填装因子0.75构造一个空的HashMap
*/
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
/**
* 构造一个包含指定的Map中的元素的HashMap
* 使用默认的填装因子0.75和可以包含Map中所有元素的初始容量来创建一个新的HashMap
*/
public HashMap(Map<? extends K, ? extends V> m) {
this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
inflateTable(threshold);
putAllForCreate(m);
}
由上面的底层实现及构造方法可以看出,当程序初始化HashMap的时候,会创建一个长度为capacity的Entry数组,这个数组里面可以存储元素的位置被称作为“桶(bucket)”,每个bucket都有其指定的索引,程序可以根据其索引快速访问该bucket里存储的元素。
需要注意的是:无论何时,HashMap的每个桶只能存储一个元素也就是一个Entry,由于Entry对象可以包含一个引用变量指向下一个Entry,那么这样可以出现的情况就是:HashMap的bucket中只有一个Entry,这个Entry指向另一个Entry,这样就形成了一个Entry链,它的结构图如下所示:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
//如果 key 为 null,调用 putForNullKey 方法进行处理
if (key == null)
return putForNullKey(value);
// 根据 key 的 keyCode 计算 Hash 值
int hash = hash(key);
// 搜索指定 hash 值在对应 table 中的索引
int i = indexFor(hash, table.length);
// 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
// 找到指定 key 与需要放入的 key 相等(hash 值相同
// 通过 equals 比较放回 true)
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
// 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry
modCount++;
// 将 key、value 添加到 i 索引处
addEntry(hash, key, value, i);
return null;
}
从上面的代码可以看出:当HashMap存储键值对时,没有考虑过当前Entry中的value,仅仅是根据key值来计算并决定每个Entry的存储位置。这说明了一个事情那就是:我们可以把Map中的value当成是key的附属,当key的位置被决定后,value的值也随之保存在那个位置就可以了。
HsahMap存储键值对时,根据hashCode()来计算哈希码,这个方法是一个纯粹的数学计算,源码如下:
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// 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);
}
我们可能都知道,在HashMap中,对于任意给定的对象,只要它的hashCode()返回值相同,那么程序调用hash(int h)方法所计算的到的哈希码值是相同的,这时候,出现这种情况就叫Hash表中出现了冲突,怎么解决这个冲突呢。程序中是这样来解决这个冲突的,通过调用indexFor(int h, int length) 方法来计算该对象应该保存在table数组的哪一个索引处。indexFor(int h, int length) 方法的源码如下:
static int indexFor(int h, int length) {
return h & (length-1);
}
这个方法非常巧妙,它总是通过h&(table.length-1)来得到对象的保存位置,而HashMap的底层数组长度总是2^n。那么,当length总是2的倍数的时候,h&(length-1)将是一个非常巧妙的设计:假设当前h=5,length=16,那么h&(length-1)将得到5;当h=6,length=16,h&(length-1)将得到6,这样依次,当h=15时,h&(length-1)将得到15;当h=16时,h&(length-1)将得到0,;当h=17时,h&(length-1)将得到1。就这样,它所得到的索引值总是在table数组的索引内。
根据上面的put方法的源码可以看出,当程序试图将一个键值对放入HashMap中,程序首先根据key值的哈希码决定Entry的存储位置,如果两个Entry的key值的哈希码相同,那么它们的存储位置应该是相同的。如果这两个Entry的key相同,新添加的Entry的value将会覆盖集合中原有的Entry的value。如果两个Entry的key不相容的话,新添加的Entry降雨集合中原有的Entry形成Entry链(开链法解决冲突),并且新添加的Entry位于Entry链的头部。具体的实现是在addEntry方法中实现的,我们来看看addEntry的源码:
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
从上面的代码可以看到:HashMap总是将新添加的Entry对象放入table数组的bucketIndex索引处,假如bucketIndex索引处已经有一个Entry对象,那么新添加Entry对象指向原有的Entry对象,这样就产生一个Entry链,假如bucketIndex索引处没有Entry对象,放入新的Entry对象。
HashMap数据读取:
public V get(Object key) {
if (key == null)
return getForNullKey();
Entry<K,V> entry = getEntry(key);
return null == entry ? null : entry.getValue();
}
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
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 != null && key.equals(k))))
return e;
}
return null;
}
从上面的代码可以看出,HashMap读取它的某个键值对时,先要计算出key的哈希码,再根据哈希码找出key在table上的索引,根据索引取出Entry,最后返回key值对应的value即可。这时候你可能就会想到,假如HashMap的每个bucket只有一个Entry时,那么HashMap就可以很快的根据索引取出该bucket里的Entry,时间复杂度可以达到o(1),但是当出现冲突时,这里是采用开链法来处理冲突,这时候一个bucket可能对应对一个Entry链,刚好待读出的数据就在链末尾,这时候时间复杂度就不能为o(1)了,它的时间复杂度为o(n)。
另外,需要注意的是,创建HashMap时有一个默认的填装因子,默认值为0.75,这个是时间和空间成本上的一个折中,增大这个填装因子可以减少hash表所占用的内存空间,但是这样会增加查询数据的时间开销,而查询又是最频繁的操作;减少负载因子呢会提高数据查询的性能,但是会增加hash表所占用的空间。在使用的时候还是要以实际情况而定,一般都选择填装因子的默认值。
上面的内容已经将HashMap的原理及实现差不多介绍完了,到此时还剩下一个东西,那就是HashMap的扩容,如果你开始就知道HashMap会保存很多的键值对,那么就可以在创建时使用较大的初始化容量,假如HashMap中的Entry的数量一直都不会超过最大的容量,那么就无需调用resize方法来进行扩容,那么我们来看看resize方法源码是怎么实现的:
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, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}