Java底层探究–HashMap
简介
HashMap在是java中一种常用的数据类型,其数据存储是以键-值对的形式进行存储,并且,键是不可以重复的,可以为null,作为双列,元素无序的Map集合,在使用中有着不可替代的作用;
相关类与接口
HashMap继承自AbstractMap,而AbstarctMap实现了Map接口;Map接口中定义了get,put,containsKey,containsValue等方法;
底层实现
HashMap的实现是基于Hash表(数组加链表,其中数组查找快,链表增删快)的数据结构,(其链表结构在JDK1.8以后,当某一链表长度超过8,且数组长度超过64时,会将链表转换为红黑树,所以也可以将Hash表看成是数组加链表加红黑树的数据结构,红黑树是一种平衡的树,其左右子树高度近乎一致,也使得查找时间复杂度为log(n));
红黑树的特点
(1).所有节点均为红色或者黑色;
(2).红色节点的两个子结点一定是黑色结点;
(3).叶子结点一定是黑色结点;
(3).从根节点出发,达到每一个叶子结点所经过的黑色结点数相同;
在HashMap中其键不可以重复,在其键进行存储时,会使用hashCode()方法进行判断,如果不同则判断不同,如果相同,则调用其equals()方法进行判断,如果相同,则判断键重复,会覆盖掉原有键的值,如果不同,则进行存储;所以在其中存储的key的类对象要求重写hashCode()和equals()方法;
Hash冲突
使用哈希的方法,也就是HashMap在进行key的存储时,会使用其hash值对其容量-1进行取余操作,如当前容量为16,需要存储的键的哈希值为31,则计算得到将其存放在下标为1的链表上,但是很容易想到,如果又有一个哈希值为46的元素,那么根据计算,其存储位置又是1,这时,就会链接到其链表的下一个位置;
这使用的方法就是处理hash冲突时常用的拉链法;
此外,处理哈希冲突的方法还有:
开放定址法:空间表中空间不足时,会去寻找下一个空的散列地址;
双哈希法:建立多个hash函数,当第一个发生冲突会使用第二个,第三个,直到不发生冲突;
建立公共溢出区:建立基本表和溢出表,将发生哈希冲突的元素均存入到溢出表中;
HashMap的性能
影响其性能的因素有两个:初始容量和负载因子;即初始哈希表长度和哈希表的扩容时机,如果初始容量太大,使用的时候只存储很少的数据,会造成空间的大量浪费,如果容量太小,使用时就需要多次扩容,降低效率;
初始容量是创建hash表时的数组容量,默认是16,最大为2的30次方:
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
static final int MAXIMUM_CAPACITY = 1 << 30;
负载因子默认为0.75;阈值=负载因子*容量,即达到容量的3/4会进行扩容操作,其容量左移一位,即为原容量的2倍,而ArrayList的扩容为达到最大值才进行扩容,并扩容为原有的1.5倍;
static final float DEFAULT_LOAD_FACTOR = 0.75f;
扰动函数
为什么需要扰动函数呢,在数据进行hash值计算时,往往会有在某一数据值附近聚集,那么会很容易发生哈希冲突,使用扰动函数,可以降低其聚集在一起的概率;
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
h >>> 16 表示将hashCode的二进制码右移16位
^ 表示按位异或,即2个二进制码异或,2个数不同则结果为1,否则为0。
其好处就是可以混合高位和低位,加大随机性;