百度百科
散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。
也就是说,它通过计算一个关于键值的函数,将所需查询的数据映射到表中一个位置来访问记录,这加快了查找速度。
这个映射函数称做散列函数,存放记录的数组称做散列表。
哈希表可以根据一个key值来直接访问数据,因此查找速度快
哈希表底层实现用了数组
哈希表的实现,无论是哪一种,都离不开数组:
- 数组+链表
- 数组+二叉树
哈希表和普通数组的区别:
哈希表的数组存放的是键值对,而数组存放的就是单一数据
散列函数
比如说,我现在给你个电话本,上面记录的有姓名和对应的手机号,我想让你帮我找张三的手机号是多少,那么你会怎么做呢?
在什么都没有的情况下,我们只能挨个去找张三,这种做法确实是可以的,但是如果张三是这个电话本里面最后一个呢,那前面几页就白找了。所以我们会想着给这个电话本分一个类,比如按照首字母来排序,就abcd的顺序,这样根据张三我就知道去找z就快很多了。
人家叫张三,我们为什么要用一个z来标志人家呢?因为用这个姓氏分类方法,我们找z可以更快的定位到张三。
这里要引出的就是散列函数。
通过一些特定的方法去得到一个特定的值,比如这里取人名的首字母,那么如果是放到数学中,是不是就是类似一个函数,给你一个值,经过某些加工得到另外一个值,就像这里的给你个人名,经过些许加工拿到首字母,那么这个函数或者是这个方法在哈希表中就叫做散列函数。
关键值key
这个也好理解,就如上图,z是怎么得出来的呢,是根据未加工的张三得出来的,这个加工过程其实就是散列函数,而丢给散列函数的“张三”就是关键值,为啥叫它关键值呢,那是因为我们要对它做加工才能得出我们想要的z呀。
键值对和Entry
学生的学号和姓名就是一个键值对,根据这个学号就能找到这个学生的姓名,那啥是Entry呢,我们都知道键值对,在很多语言中也许都有键值对,说白了就是个大众脸啊,在咱jdk中可不能那么俗气,不能再叫键值对了,叫Entry。
这里的学号是个key,我们之前也知道了,哈希表就是根据key值来通过哈希函数计算得到一个值,这个值就是用来确定这个Entry要存放在哈希表中的位置的,实际上这个值就是一个下标值,来确定放在数组的哪个位置上。
比如这里的学号是200100,那么经过哈希函数的计算之后得到了1,这个1就是告诉我们应该把这个Entry放到哪个位置,这个1就是数组的确切位置的下标,也就是需要放在数组中下表为1的位置,如图中所示。
之前已经介绍过什么是Entry了,数组中1的位置存放的是一个Entry,它不是一个简单的单个数值,而是一个键值对,也就是存放了key和value,key就是学号200100,value就是张三,我们经过哈希函数计算得出的1只是为了确定这个Entry该放在哪个位置而已。
哈希冲突
那么也有可能我们另一个关键值通过哈希函数得到的结果和张三的一样,如下图
既然出现了这情况,不能不管李四啊,总得给他找个位置,怎么找呢?
处理哈希冲突
- 开放寻址法
- 拉链法
开放寻址法
拉链法
这时候这个1的位置存放的不单单是之前的那个Entry了,此时的Entry还额外的保存了一个next指针,这个指针指向数组外的另外一个位置,将李四安排在这里,然后张三那个Entry中的next指针就指向李四的这个位置,也就是保存的这个位置的内存地址,如果还有冲突,那就把又冲突的那个Entry放在一个新位置上,然后李四的Entry中的next指向它,这样就形成了一个链表。
如果冲突过多的话,这块的链表会变得比较长,怎么处理呢?这里举个例子,拿java集合类中的HashMap来说,如果这里的链表长度大于等于8的话,链表就会转换成树结构,如果长度小于等于6的话,就会还原链表,以此来解决链表过长导致的性能问题。这样设计是因为中间有个7作为一个差值,来避免频繁的进行树和链表的转换,因为转换频繁也是影响性能
哈希表的扩容
当哈希表被占的位置比较多的时候,出现哈希冲突的概率也就变高了,所以很有必要进行扩容。这就涉及到了增长因子的概念,也叫作负载因子,简单点说就是已经被占的位置与总位置的一个百分比,比如一共十个位置,现在已经占了七个位置,就触发了扩容机制,因为它的增长因子是0.7,也就是达到了总位置的百分之七十就需要扩容。
拿HashMap来说,当它当前的容量占总容量的百分之七十五的时候就需要扩容了。
这个扩容也不是简单的把数组扩大,而是新创建一个数组是原来的2倍,然后把原数组的所有Entry都重新Hash一遍放到新的数组。
哈希表读数据
比如我们现在要通过学号100200来查找学生的姓名,怎么操作呢?我们首先通过学号利用哈希函数得出位置1,然后我们就去位置1拿数据,拿到这个Entry之后我们得看看这个Entry的key是不是我们的学号100200,一看是100100,这不是我们要的key啊,然后根据这个Entry的next知道下一给位置,在比较key,成功找到李四。
参考文章:https://blog.csdn.net/sinat_33921105/article/details/103344078