最通俗易懂的HashMap深度解析

导言

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下,精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法索引技术有关。

常见的逻辑数据结构有: 数组、栈、队列、链表、树、图、散列表、堆。本文的核心就是讲散列表(Hash表)。以下首先介绍Hash相关知识,再以jdk1.8中的HashMap做一个源码解读。

Hash表

什么是Hash表

它最大的特点就是可以快速实现查找、插入和删除。因为它的快速性,常被广大程序员拿来处理大数据问题。

为什么要Hash表

常和Hash放在一起选型考虑的有数组、链表,数组增删困难、队列寻址困难。而Hash就很好的结合两者的优点,使用自定义的下标索引HashCode,加快寻址、插入、删除的操作,本质上就是一个优化的k-v存储。

Hash表核心原理

核心概念

Hash表

散列表。根据关键值(key)访问其value(真正的数据实体),也就是最常用到的Map。

hash函数

哈希函数是Hahs表的映射函数,它可以把任意长度的输入变换成固定长度的输出,该输出就是哈希值。该哈希值就是该k-v存储的key。设计出一个简单、均匀、存储利用率高的散列函数是散列技术中最关键、最重要的问题。

一句话总结: hash函数是为了快速确定数据所在的"下标"

常见的hash映射策略有:

  1. 直接定址法:取关键字或关键字的某个线性函数值为散列地址。

  2. 数字分析法:假设关键字是以r为基的数,并且哈希表中可能出现的关键字都是事先知道的,则可取关键字的若干数位组成哈希地址。

  3. 平方取中法:取关键字平方后的中间几位为哈希地址。

    通常在选定哈希函数时不一定能知道关键字的全部情况,取其中的哪几位也不一定合适,而一个数平方后的中间几位数和数的每一位都相关,由此使随机分布的关键字得到的哈希地址也是随机的。取的位数由表长决定。

  4. 折叠法:将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址。

  5. 随机数法

  6. 除留余数法:取关键字被某个不大于散列表表长m的数p除后所得的余数为散列地址。不仅可以对关键字直接取模,也可在折叠法平方取中法等运算之后取模。对p的选择很重要,一般取素数或m,若p选择不好,容易产生冲突。

常见冲突解决方法

理想情况下每个Key都被分配到一个唯一的,但大多数的Hash函数都不能支持这一要求,如果要支持则每次分配新的key时需要知道旧的Keys的值,一般来说这并不值的。因此我们需要处理散列冲突。

常见的Hash冲突有如下几种:

开放地址法(再散列法)

开放地址法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1) 其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1,称线性探测再散列。如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。如果di取值可能为伪随机数列。称伪随机探测再散列。

总而言之,就是冲突的时候往后顺序挪若干位插入。

再哈希法

当发生冲突时,使用第二个、第三个哈希函数…计算地址,直到无冲突时。缺点:计算时间增加。比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止.这种方法不易产生聚集,但增加了计算时间。

多次Hash,冲突一次Hash一次。

链地址法(拉链法)

将所有关键字为同义词的记录存储在同一线性链表中.基本思想:将所有哈希地址为i的元素构成一个称为同义词链的单链表,并将单链表的头指针存在哈希表的第i个单元中,因而查找、插入和删除主要在同义词链中进行。链地址法适用于经常进行插入和删除的情况。

jdk 的hashmap就是基于这个的变体,1.7前几乎就是链地址法,1.8及其之后是待红黑树的链地址法。

java HashMap原理浅析

一张图表示(图片来源):

图1

java hashMap就是基于的“链地址法”,链地址法中的“同义词链表”在jdk hashmap中通过java.util.HashMap.Node构成链表表示,Node源码主要内容如下所示(暂不考虑红黑树节点: TreeNode):

static class Node<K,V> implements Map.Entry<K,V> {
   
   
    //省略构造函数和get、set方法,以及其他例如Hash、equals的方法
    final int hash;
    final K key;
    V value;
    Node<K,V> next;
}

所有的桶元素存储在transient Node<K,V>[] table;这里源码396行。jdk1.8中使用(n - 1) & hash来获取桶的下标位置此处的与操作逻辑上等价于%,如果冲突了则在加在其链表尾。

Node的hash值来自于:

static final int hash(Object key) {
   
   
    int h;
    // 从此可以看错HashMap支持key为null的对象
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

hash和key的关系: hash值就是key的hashcode经过一个位运算得到。ps: 对于java.lang.Object#hashCode,该方法是专门用来支持hash数据结构的。

hash值决定该value在哪个桶,key保证在全局上唯一(桶链表上更是唯一)。

java HashMap核心AIP

略过各种便于开发使用的api不谈,java中的HashMap就是个支持增、删、查的散列表。

先从最简单的查开始。所有的查都是调用方法getNode实现的:

final HashMap.Node<K, V> getNode(int hash, Object key) {
   
   
    HashMap.Node<K, V>[] tab;
    HashMap.Node<K, V> first
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值