HashMap的实现过程
一、从Hash说起
还记得,我们第一次接触Hash这个词,是在学数据结构,讲到查找一节,引入哈希表的时候。
对于像顺序查找、折半查找、二叉排序树查找等查找,关键字在存储结构中的位置是随机的,即关键字与它的存储位置之间存在着不确定性的关系,因而这样的查找方法是建立在“比较”的基础上。而查找的效率也主要依赖于查找过程中所进行比较的次数。
哈希表的引入,提供了有别于上面所说查找的另外一种查找方式。即关键字与它所存储的位置之间有着一个确定的对应关系,这样当我们要查找指定关键字时,只要通过这样的一个映射关系,就可以得到它的存储地址,从而可以很快地找到关键字。
而这种映射关系就是所谓的哈希函数了,哈希函数的作用在于,它提供关键字到其存储地址的一种映射。不同的哈希函数,一般来说,就会有不同的映射结果。对于单值的映射来说,只要哈希函数一确定,输入相同的关键字,就会得到相同的映射结果,而输入不同的关键字,也有可能得到相同的映射结果。不过一旦出现这种现象,可就糟了,因为,如果我们直接将映射结果所指空间作为关键字的存储位置,那这样将会出现两个不同的关键字被存储在同一个存储空间中,这样肯定是不行的,所以我们必须对这样的结果还需进行一个加工处理。对于这样现象,我们叫它冲突(Hash冲突),而当冲突出现时,我们需要对它进行处理,以使得最后每个关键字都能够找到它唯一的一个存储空间。
对于冲突的处理有许多方法,在这里,我们用的是挂链法。即,当我们用哈希函数处理不同的关键字,得到相同的映射结果,我们姑且把这样相同的映射结果所对应的空间叫做“第一地址”,然后我们在“第一地址”上挂一个链表(即“第一地址”存储链表的首地址),而链表里的元素就是实实在在所保存的关键字,只要当不同的关键字通过该哈希函数得到相同的映射结果,我们就把该关键字加到“第一地址”下的链表中存储。当我们访问该关键字时,首先通过哈希函数找到“第一地址”,然后遍历链表即可。
我们用哈希表这一名词来表示存储这些关键字的结构。在这里,哈希表就是我们常说的“数组+链表”的结构了。数组空间存储就是上面所说的“第一地址”,即数组下标索引指的就是“第一地址”,通过索引,我们可以找到存有关键字的链表,然后再遍历链表就可以找到指定的关键字了。
附“哈希表”的定义(数据结构(严蔚敏版)):
哈希表:根据设定的哈希函数H(key)和处理冲突的方法,将一组关键字映像到一个有限的连续的地址集(区间)上,并以关键字在地址集中的“像”作为记录在表中的存储位置,这种表便称为哈希表,这一映像过程称为哈希造表或散列,所得存储位置称哈希地址或散列地址。
针对以上构造的哈希表,当我们存储的元素不是单值元素,而是像Map中键值对的形式的元素时,这时这种结构就是我们谈论的HashMap了,不过实际中的HashMap存储的元素还有hash值,以及指向下一个结点的引用。具体的东西,我们一看源码即可知晓。
对于Map,我们知道,有put(Object key , Object value)、get(Object key)、remove(Object key)、containsKey(Object key)、containsValue(Object value)等方法,同样,HashMap中也含有这些方法,下面我们从HashMap的源码中截取这些方法,自定义一个HashMap。
二、从源码中截取经典自定义HashMap
1、定义内部类Entry<K,V>,封装所存数据
/**
* 内部类:封装数据,作为链表的结点
* @author wanghaoliang
*
* @param <K>
* @param <V>
*/
static class Entry<K,V>{
K key;
V value;
int hash;//参与h & (length-1)的hash码值
Entry<K,V> next;
Entry(K k,V v,int h,Entry<K,V> n){
key=k;
value=v;
hash=h;
next=n;
}
}
2、自定义带有初始容量和装载因子的构造器
/**
* 指定初始容量和装载因子的构造器
* @param initialCapacity
* @param loadFactor
*/
public MyHashMap(int initialCapacity,float loadFactor){
//IllegalArgumentException非法参数处理
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);
//初始化参数
int capacity=1;
while(capacity<initialCapacity)
capacity<<=1;
this.loadFactor=loadFactor;
//计算阀值
threshold=(int)(capacity*loadFactor);
//对hash表初始化
table=new Entry[capacity];
}
3、定义put(K key,V value)等函数,关联指定的Key和Value,并加入到链表中
/**
* 关联指定的Key和Value,
* 如果map中已经有了指定的Key的映射,则新的value会覆盖老的value
* 如果map中没有指定的Key,则调用addEntry方法,添加新的mapping
* 返回老的value值
* 如果返回Null,则说明map中不存在指定
* @param key
* @param value
* @return
*/
public V put(K key,V value){
//HashMap中允许存放null键和null值
if(key==null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i= indexFor(hash, table.length);
//遍历table,查看其中是否已有指定的Key,若有,则用新的Value覆盖旧的Value
for(Entry<K,V> e=table[i];e!=null;e=e.next){
Object k=e.key;
if(e.hash==hash&&(key==k||key.equals(k))){
V oldValue=e.value;
e.value=value;
return oldValue;
}
}
//table中没有指定Key的映射,故加入该映射到链表中
addEntry(hash,key, value, i);
return null;
}
4、定义get(K key)等函数,通过指定键获取对应的Value
/**
* 返回指定的键(Key)所对应的值(Value)
* @param key
* @return
*/
public V get(K key){
if(key==null)
return getForNullKey();
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=e.key;
//先比较hash,再比较key值
if(e.hash==hash&&(key==k)||key.equals(k))
return e.value;
}
return null;
}
5、定义remove(K key)等函数,通过指定键移除对应的映射关系
/**
* 移除指定的Key所对应的映射
* 返回Entry<K,V>
* @param key
* @return
*/
public Entry<K,V> removeEntryForKey(K key){
//如果key为null,则对应的hash值为0
int hash=(key==null)?0:hash(key.hashCode());
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;
Object k=e.key;
//遍历链表,找到指定的结点
if(e.hash==hash&&(key==k||key.equals(k))){
size--;
if(prev==e)//要删除的结点为首结点
table[i]=next;
else
prev.next=next;
return e;
}
prev=e;
e=next;
}
return e;
}
6、定义containsKey(K key)和containsValue(V value)函数,判断指定的K或V是否存在于HashMap中
/**
* 判断table中是否含有指定的Key
* @param key
* @return
*/
public boolean containsKey(K key){
return getEntry(key)!=null;
}
/**
* 判断table中是否含有指定的Value
* @param value
* @return
*/
public boolean containsValue(V value){
if(value==null)
return containsNullValue();
Entry[] tab=table;
for(int i=0;i<tab.length;i++){
for(Entry e=tab[i];e!=null;e=e.next)
if(value.equals(e.value))
return true;
}
return false;
}
7、测试
三、源码要点分析
1、为什么table表的大小为2的幂次方?
/**
* The table, resized as necessary. Length MUST Always be a power of two.
*/
transient Entry[] table;
我们知道,选择哈希函数构造哈希表时,应尽量避免冲突,即构造一个均匀的哈希表,使得表中的每个索引空间被映射到的概率都是均等的。对于一片有限的连续的存储空间(数组),如果将不同的关键字比较均匀地映射到这一片存储空间呢?是的,我们一般采用“除留取余法”,即拿hash%table.length所得值,作为索引空间。但是计算机对于%(取余运算)较耗时间,而计算机对于&(位与运算),即hash & (table.length-1) 却是大大地节省了时间,而只有当table.length为2的幂次方时,上面的hash & (table.length-1)才具有等价的hash%table.length效果。
2、为什么还要再哈希?
即为什么当我们调用key.hashcode()返回一个哈希值时,为什么不直接将key.hashcode()作为最终的hash值,而还要经过如下的这一操作?
/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
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);
}
大概看了一下源码中的对此函数的注释可知,原始的key.hashcode()返回得到的hash值,如果参与hash & (table.length-1)运算,在低字节上很容易相同,即就很容易产生冲突,为了使得冲突减少,所以系统增加了上面的这一操作,它是原始hash的补充,旨在使得key的最终的hash码值,在参与hash & (table.length-1)运算能够达到一个分布均匀的结果值,即最大化地减少冲突。
附:完整代码
package whlHashMap2;
import java.util.Map;
/**
* 自定义HashMap
* @author wanghaoliang
*
*/
public class MyHashMap<K,V> {
//最大的初始容量,必须<=2的30次方
static final int MAXIMUM_CAPACITY = 1 << 30;
//阀值=capacity * load factor
int threshold;
//装载因子
final float loadFactor;
//hash表
transient Entry[] table;
//键值对的个数
transient int size;
/**
* 指定初始容量和装载因子的构造器
* @param initialCapacity
* @param loadFactor
*/
public MyHashMap(int initialCapacity,float loadFactor){
//IllegalArgumentException非法参数处理
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);
//初始化参数
int capacity=1;
while(capacity<initialCapacity)
capacity<<=1;
this.loadFactor=loadFactor;
//计算阀值
threshold=(int)(capacity*loadFactor);
//对hash表初始化
table=new Entry[capacity];
}
/**
* 补充性的hash函数
* @param h
* @return
*/
static int hash(int h){
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
/**
* 返回对应的hashCode在数组中的索引
* 等价于h%length操作
*/
static int indexFor(int h, int length) {
return h & (length-1);
}
/**
* 添加链表结点
* @param hash:关键字所对应的hash值
* @param key:键值对中的Key
* @param value:键值对张的Value
* @param bucketIndex数组索引
*/
void addEntry(int hash,K key,V value,int bucketIndex){
Entry<K,V> next=table[bucketIndex];
table[bucketIndex]=new Entry<K,V>(key,value,hash,next);
if(size++ >= threshold)
resize(2*table.length);
}
/**
* 改变table的大小
* @param newCapacity:新的大小容量
* newCapacity必须满足:不超过MAXIMUM_CAPACITY,并且为2的幂次方
*/
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];
//从oldTable中转移数据到newTable中
transfer(newTable);
table=newTable;
threshold=(int)(newCapacity*loadFactor);
}
/**
* 从oldTable中转移数据到newTable中
* @param newTable
*/
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;//清空原始Hash表
//原始链表数据在新的链表中顺序被倒置了
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);
}
}
}
/**
* 关联指定的Key和Value,
* 如果map中已经有了指定的Key的映射,则新的value会覆盖老的value
* 如果map中没有指定的Key,则调用addEntry方法,添加新的mapping
* 返回老的value值
* 如果返回Null,则说明map中不存在指定
* @param key
* @param value
* @return
*/
public V put(K key,V value){
//HashMap中允许存放null键和null值
if(key==null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i= indexFor(hash, table.length);
//遍历table,查看其中是否已有指定的Key,若有,则用新的Value覆盖旧的Value
for(Entry<K,V> e=table[i];e!=null;e=e.next){
Object k=e.key;
if(e.hash==hash&&(key==k||key.equals(k))){
V oldValue=e.value;
e.value=value;
return oldValue;
}
}
//table中没有指定Key的映射,故加入该映射到链表中
addEntry(hash,key, value, i);
return null;
}
/**
* 当Key为Null时的put(K,V)方法处理
* @param value
* @return
*/
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;
return oldValue;
}
}
addEntry(0, null, value, 0);
return null;
}
/**
* 返回指定的键(Key)所对应的值(Value)
* @param key
* @return
*/
public V get(K key){
if(key==null)
return getForNullKey();
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=e.key;
//先比较hash,再比较key值
if(e.hash==hash&&(key==k)||key.equals(k))
return e.value;
}
return null;
}
/**
* 当Key为Null时的get(K)方法处理
* @return
*/
private V getForNullKey(){
for(Entry<K,V> e=table[0];e!=null;e=e.next){
if(e.key==null)
return e.value;
}
return null;
}
/**
* 判断table中是否含有指定的Key
* @param key
* @return
*/
public boolean containsKey(K key){
return getEntry(key)!=null;
}
/**
* 判断table中是否含有指定的Value
* @param value
* @return
*/
public boolean containsValue(V value){
if(value==null)
return containsNullValue();
Entry[] tab=table;
for(int i=0;i<tab.length;i++){
for(Entry e=tab[i];e!=null;e=e.next)
if(value.equals(e.value))
return true;
}
return false;
}
/**
* 当Value为Null是的containsValue(V)方法处理
* @return
*/
private boolean containsNullValue(){
Entry[] tab=table;
for(int i=0;i<tab.length;i++)
for(Entry e=tab[i];e!=null;e=e.next)
if(e.value==null)
return true;
return false;
}
/**
* 返回指定Key所对应的映射
* @param key
* @return
*/
public Entry<K,V> getEntry(K key){
//若key为null,则对应hash码值为0
int hash=(key==null)?0:hash(key.hashCode());
int i=indexFor(hash, table.length);
for(Entry<K,V> e=table[i];e!=null;e=e.next){
Object k=e.key;
if(e.hash==hash&&(key==k||key.equals(k)))
return e;
}
return null;
}
/**
* 移除指定的Key所对应的映射
* 返回Value
* @param key
* @return
*/
public V remove(K key){
Entry<K,V> e =removeEntryForKey(key);
return (e ==null ? null:e.value);
}
/**
* 移除指定的Key所对应的映射
* 返回Entry<K,V>
* @param key
* @return
*/
public Entry<K,V> removeEntryForKey(K key){
//如果key为null,则对应的hash值为0
int hash=(key==null)?0:hash(key.hashCode());
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;
Object k=e.key;
//遍历链表,找到指定的结点
if(e.hash==hash&&(key==k||key.equals(k))){
size--;
if(prev==e)//要删除的结点为首结点
table[i]=next;
else
prev.next=next;
return e;
}
prev=e;
e=next;
}
return e;
}
/**
* 直接移除所指的映射
* @param o:被移除的映射对象
* @return
*/
public Entry<K,V> removeMapping(Object o){
if(!(o instanceof Map.Entry))
return null;
Map.Entry<K, V> entry=(Map.Entry<K, V>)o;
Object key=entry.getKey();
int hash=(key==null)?0:hash(key.hashCode());
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;
Object k=e.key;
if(e.hash==hash&&(key==k||key.equals(k))){
size--;
if(prev==e)
table[i]=next;
else
prev.next=next;
return e;
}
prev=e;
e=next;
}
return e;
}
/**
* 内部类:封装数据,作为链表的结点
* @author wanghaoliang
*
* @param <K>
* @param <V>
*/
static class Entry<K,V>{
K key;
V value;
int hash;//参与h & (length-1)的hash码值
Entry<K,V> next;
Entry(K k,V v,int h,Entry<K,V> n){
key=k;
value=v;
hash=h;
next=n;
}
}
}
<!--EndFragment-->