深度理解HashMap

什么是HashMap

HashMap是Java中Map接口的一个实现类,其实HashMap实现了Map,cloneMap,serializable三个接口,并且hashMap继承自AbstractMap类。

为什么要使用HashMap

任何一种数据结构的产生,都是为了去解决一个实际问题。HashMap主要是为了解决如何有效存取一组key-value键值对的问题。

HashMap的结构是什么

HashMap是基于哈希表结构实现的。也可以说利用的是一种数组+链表的存储方式。具体的结构实现细节,在下一部分进行说明。

HashMap是如何实现的

既然是存储key-value键值对,我们就要弄清楚这样几个问题:

  1. 如何表示键值对?
    通过一个Node类来表示一个键值对,同时为了做到面向接口编程,把操作键值对的get,set等方法的描述写在一个抽象出来的Entry接口中,然后,让Node类去实现这个接口,以及接口中定义的一些方法。
class Node<K,V> implements Map.Entry<K,V>{
    K key;
    V value;
}
  1. 如何存储键值对?
    在上面我们知道了键值对是用一个实现Entry接口的Node类来表示,那么我们如何把确定值的key,value存入上述所说的结构中呢?也就是说,如何存储这些键值对的集合呢?
    我们知道,键值对已经被我们表示为Node对象了,通常情况下,想要存储对象的集合,无非就是两种结构类存储它们:
    数组:比较适合进行查找操作,时间复杂度为O(1)
    链表:比较适合进行插入删除操作
    因为在对hashMap的实际应用中呢,我们用的更多的是查找操作,所以这里采用了数组的形式进行key-value对象的存储。但是!如果每次查找都是遍历数组找到想要的key-value 那样太慢了—> 于是,采取了通过索引来查找的方式(建立一种key-index的映射关系,根据key映射到数组对应的下标index):也就是通过哈希算法实现的这个过程。
    同时采用哈希算法,也解决了数组存储的插入删除性能短板问题。(因为采用哈希算法后,数组中的对象不再按顺序存储,因此插入删除时候只需要关注一个存储位置即可)。
    采用了哈希算法,接下来又如何解决哈希冲突呢—>采用链地址法(当然了,最理想的情况还是Entry数组的每个位置都只有一个元素,这样查询效率最高了,而且就不需要遍历同index下的单链表,也不需要equals去比较key了),链地址法解决冲突后,之前的数组结构就理所当然变成了链表的数组。注意!!!链表中,每一个节点是一个Entry数组:包含key+value。
    经过上面这些,键值对的表示也就很明了了:
class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
    
    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }
    ...
}

!!!在JDK1.8中有一个改进:当链表长度大于8时,采用红黑树存储
3. 存储完键值对,如何查找我们想要的key值,又如何根据key查找到对应的value?
首先根据key值计算出映射到的index,是将key的哈希值,进行高16位和低16位异或操作,增加低16位的随机性,降低哈希冲突的可能性。

HashMap的put()插入操作

jdk1.7中是头插法;jdk1.8中是尾插法,下面以1.8为例
1、首先根据key,生成一个hash
2、拿到了hash值后,调用 putVal(),存储key-value:
①若table没有进行初始化,则调用resize()方法进行初始化。
②计算命中的哈希表的索引位置。
③判断哈希表中对应索引中的首节点是否为空,若为空,则创建节点。
④判断首节点是否与插入的键值对的key和value一致,若一致,则替换该节点的值为新的value。否则进入下一步。
⑤判断首节点是树节点,则遍历红黑树;是链表节点,则遍历链表。
⑥若当前链表长度大于8,则转化为红黑树。

HashMap定义了自己的哈希方法:
//h ^ h >>> 16 其实就是将hashCode的高16位和低16位进行异或,
//这充分利用了高半位和低半位的信息, 对低位进行了扰动,
//目的是为了使该hashCode映射成数组下标时可以更均匀。

HashMap定义了自己的哈希方法: 
//h ^ h >>> 16 其实就是将hashCode的高16位和低16位进行异或, 
//这充分利用了高半位和低半位的信息, 对低位进行了扰动, 
//目的是为了使该hashCode映射成数组下标时可以更均匀。
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

HashMap的扩容

数组的扩容很耗费cpu,每次扩容的幅度太大或者太小,都不好。
通常情况下,数组空间已使用3/4的时候,就要进行扩容操作了、hashmap每次数组扩容,大小变为原数组长度的2倍。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值