神奇的散列表(哈希表)

散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存储存位置的数据结构。也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。这个映射函数称做散列函数,存放记录的数组称做散列表。

一个引子

// 一个的字符串,里面只含有小写字母,如何统计每个字母出现的次数?
1. 我们首先创建一个数组
	int[] list = new int[26];
2. 给每个字母(letter)在数组中设定一个:letter - 'a'
3. 遍历字符串,给相应下标的值+1
4. 代码如下:
public class Main {
    public static void main(String[] args) {
        String s = "aabbd";
        int len = s.length();
        int[] list = new int[26];
        for (int i = 0; i < len; i++) {
            char letter = s.charAt(i);
            list[letter - 'a']++;
        }
        System.out.println(Arrays.toString(list));
    }
}
// [2, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

这样实现延伸的两个问题

  • 如果字符串里不只是字母,甚至可能是更复杂的对象呢。(我们需要一个更好的散列函数)
  • 这只是一个普通的数组,虽然我们可以根据下标还原字母的值,但是不够通用。(我们考虑把字母和出现的次数合并在一起:key-value)

001、散列表

  • 散列表是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。
  • 由定义我们可以知道,散列表用的是数组支持下标访问数据的特性,所以散列表是数组的一种扩展,由数组演化而来。
  • 数组中实际存储的是一个指针,一般指向一个链表(在JDK1.8后,也可以是红黑树),链表(红黑树)里的节点就是一个个的key-value键值对(entry)。

在这里插入图片描述

010、散列函数

  • 散列函数(也叫哈希函数)就是一个无论你输入什么数据,它都会还你一个数字的函数。
  • 它必须是一致的。如果你第一次输入apple返回的是3,那么每次输入apple,这个散列函数都必须返回3。
  • 散列函数应尽可能为每个输入映射到不同的数字。如果无论输入什么,都返回3的话,那它就不是一个好的散列函数。

引子中的 letter - ‘a’ 其实就是一个散列函数,它使用的方法是直接定址法。

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

  • 通过散列函数返回的值(hashcode),我们就可以得到一个下标,通常来说,他的下标是 hash值 % 数组容量。
  • 通过下标,我们就可以找到key-value的地址,从而取得相应的值。

011、冲突

当散列函数不够好,很容易就造成哈希冲突,多个key返回相同的值。当然,再好的散列函数,都难以避免冲突的发生。

开放寻址法

  • 当一个关键字和另一个关键字发生冲突时,使用某种探测技术在Hash表中形成一个探测序列,然后沿着这个探测序列依次查找下去,当碰到一个空的单元时,则插入其中。
  • 线性探测:当根据hash值查到的key和想要的不一样,那就继续往下寻找,最坏的情况会变成遍历一遍散列表。
  • 此外还有二次探索、双重散列等

链表法(常用)

  • 产生hash冲突后在存储数据后面加一个指针,指向后面冲突的数据(hashmap)
  • 在散列表中每个下标位置对应一个链表,所有经过散列函数得到的散列值相同的元素,我们都放到对应下标位置的链表中。
  • 查询的时候,先使用hash值找到下标,然后用equals找到正确的key对应的value。

还有一些比如公共溢出区法、再散列法等

100、扩容

再好的散列函数也可能出现hash冲突,除了解决冲突,还能通过增加容量来减少冲突的发生–负载因子,也叫填装因子。散列表的负载因子=散列表保存的元素数/位置总数。当散列表中元素的个数大于数组容量*负载因子时,就会触发散列表扩容。

  • 通常严格限制在0.7-0.8以下,其中java的hashmap是0.75。
1. 在java中HashMap的长度是2的n次方,为什么?
- 计算机中取模(%)效率比位运算低。
- HashMap中一个key,他的下标是hash&(cap-1)
- 当容量cap为2的n次方,cap-1的二进制会全为1,位运算时可以充分散列,避免不必要的哈希冲突
- 如果cap为2的n次方,那么我可以知道hash%cap==hash&(cap-1)
- 用位运算(hash&(length-1))替代模运算,提高效率

2. Java中HashMap负载因子为什么是0.75
- 一种可能的说法:如果 负载因子 是 3/4 的话,那么和 容量 的乘积结果就可以是一个整数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值