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

jdk容器之HashMap

2016-04-07 00:00 197 查看
摘要: 该博客主要记录博客园上面一位博主写的《给jdk写注释系列之jdk1.6容器》系列文章的笔记。

一、HashMap简介

原博客地址:http://www.cnblogs.com/tstd/p/5055286.html

hash就是通过散列算法,将一个任意长度关键字转换为一个固定长度的散列值,由于hash函数是一个压缩映射,所以可能存在不同的关键字进行hash后所得到的hash值相同的现象。
hash函数定义如下:

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);
}
/**
* Returns index for hash code h.
*/
static int indexFor(int h, int length) {
return h & (length - 1);
}

解析:

首先看第二个函数indexFor,这个函数用于寻找hash值h在一个长度为length的数组中的位置(即下标值),然后将该hash值所对应的元素存储在相应的位置上。
由于HashMap中定义的数组长度均为2的幂的形式,而2^n-1表示为2进制时的形式为01....1(1个0后面n个1的形式),所以通过h&(length-1),可以将h映射到0~(length-1)之间的数,达到h%length一样的效果,却拥有比h%length更高的效率;

再看hash函数,由于h的低位相同(尤其是等于length位数的部分)时,经过hash后冲突的概率比较大,所以在hash函数中给h中的各个位加入了一些随机性,从而尽可能降低冲突;

HashMap的底层是用一个Entrry数组实现的,通过indexFor函数计算hash值为h的元素在一个长度为length的Entry数组中的位置,当出现冲突时,将hash值相同的元素以一个单向链表的形式存储在Entry数组的同一位置,并且最先存储的元素位于链表尾部,最后到达的元素位于链表头。也就是说HashMap的底层结构是一个数组,而数组的元素是一个单向链表。当存储一个key=null的元素时,HashMap默认将该元素置于Entry[0]的位置。

Entry是HashMap的内部类,它继承了Map中的Entry接口,它定义了键(key),值(value),和下一个节点的引用(next),以及hash值。

HashMap中定义了初始容量、加载因子、阈值和最大容量等参数。容量即HashMap中Entry数组所能包含的元素数;初始容量为16,即默认最小容量为16,使用时可以通过含参构造函数构造一个满足用户所需大小的HashMap;加载因子和阈值主要用于定义扩容临界值(阈值=容量*加载因子),当已使用容量达到阈值时,将会扩容;最大容量即默认HashMap所能扩容达到的最大大小,为2^30。
注意:

HashMap进行扩容时,数组长度发生了变化,原本在同一链表中的元素现在可能并不在同一链表中,所以需要对数组中的每一个元素都重新计算位置,并存储到新数组中,这是一个非常耗时的操作,所以如果事先能够预知需要使用的HashMap的长度的话,最好将长度设置为所需大小,这样可以减少时间消耗,提高效率;

默认加载因子=0.75,但可以根据需要自己设置一个恰当的加载因子。加载因子越大,数组填充得越满,空间利用率越高,但这样的话可能会增大冲突机率,影响查询效率;而加载因子过小的话,则会造成空间上的浪费。所以需要通过设置一个有效地加载因子,在时空这两者间寻找一个平衡点。

二、HashMap的增删改查

构造方法
HashMap有4个构造方法

//构造一个指定初始容量和加载因子的HashMap
public HashMap( int initialCapacity, float loadFactor)
//构造一个指定初始加载容量和使用默认加载因子(0.75)的HashMap
public HashMap( int initialCapacity)
//构造一个使用默认初始容量(16)和默认加载因子(0.75)的HashMap
public HashMap()
//构造一个指定map的HashMap,所创建HashMap使用默认加载因子(0.75)和足以容纳指定map的初始容量
public HashMap(Map<? extends K, ? extends V> m)

当使用指定初始容量的构造函数创建HashMap时,所得到的HashMap的实际容量是大于指定容量的最小的2的幂次方,也就是说,无论使用何种构造函数,最终所得到的HashMap的容量均为2的幂

向HashMap中增加元素

HashMap中增加元素的方法和修改元素的方法相同,均为public V put(K key, V value),具体操作流程为:

计算hash = hash(key.hashCode()),得到键值key的hash值

计算index = indexFor(hash,table.length),得到该元素在数组中的位置

取得index位置的链表,遍历链表,若链表中包含hash和key值均相同的元素,则用新的value值替换原有的value值,返回原value值;否则在该链表头新增一个Entry节点,将该元素存入新建的Entry节点中,并返回null;

注意:在新增Entry节点时,数组大小会加1,当size>阈值时,将调用resize(2*table.length)方法扩容,也就是说若新增节点时,数组大小达到了扩容阈值,则会将原数组扩大一倍。数组扩容时需要通过遍历对原数组中的所有元素重新计算下标值,并添加到新数组相应的位置,该算法比较耗时,所以在使用HashMap时若能提前预知数组容量,则最好预设初始容量。

从 HashMap中删除元素
HashMap中删除元素的方法为public V remove(Object key),即根据元素的key值删除该元素,并返回所删除元素的value值

计算hash = hash(key.hashCode()),得到键值key的hash值

计算index = indexFor(hash,table.length),得到该元素在数组中的位置

取得该位置处的第一个链表节点

遍历该链表,找到key所在的位置,若key位于链表头,则将key节点的next赋值给table[index],否则,将key的下一节点赋值给key的上一节点

从 HashMap中查找元素

从HashMap中查找元素的方法为public V get(Object key),该方法通过遍历HashMap,比较是否存在(e.hash == hash && ((k = e.key) == key || key.equals(k)))的元素,若存在,返回该元素的value值,否则,返回null

HashMap中是否包含键为key或值为value的元素

查询HashMap中是否包含某一键key或某一值value的元素时,均需要遍历数组进行查找。但是针对key进行查找时,可以通过hash函数计算出key所在的位置,从而只需要遍历该位置处的链表即可。而针对value进行查找时,需要遍历整个数组。所以针对key进行查找的效率要高于针对value进行查找的效率。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: