HashMap解析

HashMap的结构

最基本的是一个数组(),其中存储的是一个Entry,Entry中存储的是hash值,key,value和Node(节点),
当hash值相同,key不同时将出现链表,而当链表的超过阈值8时,并且整个map中键值对数量超过64将转化为红黑树
在这里插入图片描述

存储位置的计算(hash值的由来)

Step.1:重写的hashcode()方法会将key的对象的存储位置转换为int类型,即key.hashcode()
Step.2:然后通过hashmap中的hash()函数(即散列函数)进行扰动,获得扰动后的值,hash值

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

Step.3:调用indexFor()函数:与数组长度做"与"操作,得到的值即元素散列在数组中的位置

static int indexFor(int h, int length) {
    return h & (length-1);
}

数据存入流程

在这里插入图片描述

拓展

HashMap链表存储原理

hashmap中存在静态内部类Node,做为链表的节点,存储hash值,key,value,Node(下一节点位置
);如果没有下一个节点则next为null

static class Node<K,V> implements Map.Entry<K,V> {
       final int hash;//hashcode()值
       final K key;//存入对象的key
       V value;//存入对象的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;
       }

扰动函数的工作原理及作用

原理:将hashcode()值右移16位之后,与其自身做一个"异或"(相同取0,不同取1)的操作,然后将得到的值与数组长度做"与"(有0取0,无0取1)操作

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

作用:使得到的结果分布更加均匀,即散列程度更高
举一个体现扰动函数作用的例子:
使用扰动函数前:

// Hash碰撞示例:(不使用扰动函数,这两个对象计算所得的位置是一样的,即发生hash碰撞)
H1: 00000000 00000000 00000000 00000101 & 1111 = 0101
H2: 00000000 11111111 00000000 00000101 & 1111 = 0101

使用扰动函数后:

00000000 00000000 00000000 00000101 //H1:hashcode值
00000000 00000000 00000000 00000000 //h1: H1>>>16
00000000 00000000 00000000 00000101 //H1^h1(hash值)
在数组中的位置为:00000000 00000000 00000000 00000101 & (16-1) = 0101 = 5
00000000 11111111 00000000 00000101 //H2:hashcode值
00000000 00000000 00000000 11111111//h2: H2>>>16
00000000 00000000 00000000 11111010 //H2^h2(hash值)
在数组中的位置为:00000000 00000000 00000000 11111010 & (16-1) = 1010= 10

可知扰动函数能够起到降低hash碰撞几率的作用

PUT方法(1.8)

在这里插入图片描述
分步解析:

  1. 判断当前桶是否为空(resize方法中判断),如果为空进行初始化
  2. 根据key的hashcode值定位到对应的桶位置,如果为空则不存在hash冲突,直接放入
  3. 当前位置有值,比较当前桶中的key,key的hashcode值与传入的是否相同,相同则赋值给e
  4. 判断是否为红黑树结构,是则按照红黑树的方式存入数据
  5. 如果是链表,则将key,hashcode,value写入Node放在当前桶之后
  6. 接着判断当前链表的大小是否大于预设的阈值,大于时就要转换为红黑树
  7. 如果在遍历过程中找到 key 相同时直接退出遍历
  8. 存在相同key时覆盖原有的值
  9. 判断是否扩容

GET方法(1.8)

1     public V get(Object key) {
 2         Node<K,V> e;
 3         return (e = getNode(hash(key), key)) == null ? null : e.value;
 4     }
 5 
 6     final Node<K,V> getNode(int hash, Object key) {
 7         Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
 8         if ((tab = table) != null && (n = tab.length) > 0 &&
 9             (first = tab[(n - 1) & hash]) != null) {
10             if (first.hash == hash && // always check first node
11                 ((k = first.key) == key || (key != null && key.equals(k))))
12                 return first;
13             if ((e = first.next) != null) {
14                 if (first instanceof TreeNode)
15                     return ((TreeNode<K,V>)first).getTreeNode(hash, key);
16                 do {
17                     if (e.hash == hash &&
18                         ((k = e.key) == key || (key != null && key.equals(k))))
19                         return e;
20                 } while ((e = e.next) != null);
21             }
22         }
23         return null;
24     }
  • 首先将 key hash 之后取得所定位的桶。
  • 如果桶为空则直接返回 null 。
  • 否则判断桶的第一个位置(有可能是链表、红黑树)的 key 是否为查询的 key,是就直接返回 value。
  • 如果第一个不匹配,则判断它的下一个是红黑树还是链表。
  • 红黑树就按照树的查找方式返回值。
  • 不然就按照链表的方式遍历匹配返回值。

参考

存储流程参考
扰动函数详解
HashMap

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值