数据结构–哈希表(Hashtable、又称散列表)


最近做了一个题目:想要查看集合中的某个指定元素,但是不知道具体的位置。
一般情况下是遍历这个数组的全部,然后去找到这个元素。若此时元素基数不是很大的话,还好,若集合的长度 n 趋于无穷大,而恰恰不巧的是这个元素的位置刚好在最后,那么可想而知消耗的时间是非常巨大的。那么就需要一个高效的存储结构来存储这个集合----哈希表。使用了这种结构能够提高查找元素的速度,但是却不能保证元素的顺序。

1. 基本概念

在学习哈希表之前我们需要先明白几个基本概念:

  1. 散列码
    在定义一个对象的时候可以通过重写hasCode() 方法来为不同对象生成一个独一无二的数值字符串,这个数值就是散列码;这个散列码一般情况下是不会相同的(但是也存在例外,这里会在后面说明)
  2. 键(key)
    哈希表是通过一个标记来寻找与这个标记相对应的值的。这个标记与值之间形成一种映射关系。
  3. 值(value)
    即需要获得的信息。
  4. 哈希函数
    散列表是将数据存放在散列表中的,那么一个数据在散列表汇总具体存放的位置就是由哈希函数来进行决定的,通过对 key 值的 散列码进行 哈希函数的计算,得出一个具体的地址(数值),然后将需要存放的数据放入这个地址中。
  5. 哈希地址
    这个地址记录的就是在哈希表中存放数据的的位置,但是:哈希地址只不过是相对于哈希表来说的一个地址,并不是真实的对应于一个屋里存储地址。意思是这个哈希地址只适用于相对应的哈希表中。(而且不同的哈希表可能采用的哈希算法也不同,得到的地址也不同,所以更别说适用了)。

前面几者之间的关系

哈希表是通过 哈希函数 来构建的,哈希函数类似于 address = f(x) address就是哈希地址,x 就是 key 值的散列码,不同的key值有不同的散列码,而 address 地址指向的就是散列表中的一个存储空间,这个存储空间里用来存放 value ,这个 value 和 相应的 key 之间形成了一种映射。

2. 哈希冲突

有的时候因为没有选取合适的 哈希函数 f(x) ,或者出现了相同的散列码,导致经过计算后的 哈希地址(address)出现了重复的现象。那么就会与之前位置上已经存放好的数据产生冲突。这就叫哈希冲突。
哈希冲突只能尽量减少,不能完全能避免

3. 常见的哈希函数

常见的方法有6种:

  1. 直接定制法
    一次函数,或者直接取值。也叫自身函数;
    例如:有一个从1到100岁的人口数字统计表,其中,年龄作为关键字,哈希函数取关键字自身。

  2. 数字分析法
    如果关键字由多位字符或者数字组成,就可以考虑抽取其中的 2 位或者多位作为该关键字对应的哈希地址,在取法上尽量选择变化较多的位,避免冲突发生。
    例如:

  3. 平方取中法
    平方取中法是对关键字做平方操作,取中间几位作为哈希地址(此方法是比较常用的构造哈希函数的方法)
    例如关键字序列为 {421,423,436},对各个关键字进行平方后的结果为{177241,178929,190096},我们则可以取中间的两位{72,89,00}作为其哈希地址。

  4. 折叠法
    将关键字分割成位数相同的几部分(最后一部分的位数可以不同),然后取这几部分的叠加和(舍去进位)作为哈希地址,这方法称为折叠法。
    例如:每一种西文图书都有一个国际标准图书编号,它是一个10位的十进制数字,若要以它作关键字建立一个哈希表,当馆藏书种类不到10,000时,可采用此法构造一个四位数的哈希函数。

  5. 除留余数法
    取关键字被某个不大于哈希表表长m的数p除后所得余数为哈希地址。即:
    address = f(key) = x % p ( p <= m )

  6. 随机数法
    选择一个随机函数,取关键字的随机函数值为它的哈希地址,即
    f(key)=random(key),其中random为随机函数。通常用于关键字长度不等时采用此法。

    注意:这个随机函数是一个伪随机函数 , 一般的随机函数每次生成的值都是不一样的,但是在这里并不是的,这里针对同一个 key 值生成的随机数都是一样的,但是不同的 key 值生成的 随机数是不同的。

4. 解决哈希冲突

冲突:在哈希表中,不同的关键字值对应到同一个存储位置的现象。即关键字 key1 ≠ key2 ,但 f ( key1 ) = f ( key2 ) 。均匀的哈希函数可以减少冲突,但不能避免冲突。发生冲突后,必须解决;也即必须寻找下一个可用地址。
无论哈希函数设计有多么精细,都会产生冲突现象,也就是2个关键字处理函数的结果映射在了同一位置上,因此,有一些方法可以避免冲突。

具体的解决方案可以看我的另一篇博客:哈希冲突的解决方案

5. 哈希表的具体实现

话不多说,咱们直接看源码:

    /*** Constructs a new, empty hashtable with the specified initial* capacity and the specified load factor.** @param      initialCapacity   the initial capacity of the hashtable.* @param      loadFactor        the load factor of the hashtable.* @exception  IllegalArgumentException  if the initial capacity is less*             than zero, or if the load factor is nonpositive.*/public Hashtable(int initialCapacity, float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);if (loadFactor <= 0 || Float.isNaN(loadFactor))throw new IllegalArgumentException("Illegal Load: "+loadFactor);if (initialCapacity==0)initialCapacity = 1;this.loadFactor = loadFactor;table = new Entry<?,?>[initialCapacity];threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);}

在这个构造函数中,前面几行是用来处理错误的机制,注意 table = new Entry[initialCapacity] 可以看出来Hashtable的底层实现使用的 Entry 类数组。

下面看一下这个 Entry 是一个什么样的东东:

这个Entry 类是 HashTable类中的一个内部类,通过Entry 可以构成一个单向的链表,这也就能理解哈希表的底层是一个数组+链表实现的。(HashTable的具体实现示意图文末可见)

那么我们再来看看在向哈希表中添加一个元素时候是怎么实现的。

① HashTable中不允许插入空值。当value = null 时候抛出异常。

② 散列码就是针对 key 值的HashCode,index是哈希地址;而且可以看出使用的哈希函数是除留余数法。

③ 将原先哈希表中的 对应 index 的值取出来,如果当前的哈希地址中已经存在数据,那么久进入循环体,进行判断,是否完全覆盖掉之前的数据,若覆盖掉之前的数据,则返回之前数据,并成功覆盖。(判断两个数据一样的条件是散列码hash一样,然后key值也要相等)

④ 将当前满足的数据 put 入哈希表中。

下面再针对 第④ 步的 addEntry 进行进一步说明:

threshold 是一个数量标志,当哈希表中的数据大于或等于这个标志的时候就进行二次哈希。这里插入一下标志位的定义:

在一开始的构造器中对标志位 threshold 进行赋值,(标志位大小 = 哈希表容量 * 负载因子,即这个标志位就是哈希表中正常存放数据的数量,一旦大于这个数量就触发二次哈希,一般负载因子的默认值为 0.75 ,即一个 initialCapacity 为 16的哈希表,当存储数据量达到 16 x 0.75 = 12 的时候就触发二次哈希机制。 )

② 否则就新建一个 Entry 对象,然后将当前要put 进的元素放入 当前哈希地址所指向的位置。再count++。

刚才说到二次哈希,那么现在再看看二次哈希具体是怎么实现的。


① 可以看出来每次扩容的大小是乘以 2 的 次幂个 然后再 +1 ,即在当前容量的基础上翻一倍 再 +1 。

② 对之前的 哈希表中的数据进行遍历并且二次哈希,之后放入新的哈希表中。
一般情况下,哈希表的初始容量大小是 11 ,负载因子大小是 0.75; 但是HashMap的初始容量大小是 16,这里我也有些不是很明白,为什么要定义为 11.希望有人能够给我一个解答。

刚说完HashTable是怎么实现的以及如何put元素,那么不得不说一说如何get 元素;

给出参数 key ,对 key值 进行哈希运算,得到相应的哈希地址,然后判断是否有相同的哈希地址的数据,若存在,则开始遍历相同地址元素后面的链表。直到找到需要的元素。(判断元素一致的规则前面已经说过了)。

有些写的不完善的地方还请指正。感谢赐教!

参考资料:

jdk8 源码
Java核心技术(卷一)
百度百科
csdn博客

关于哈希表(Hashtable)个人学习理解相关推荐

  1. 在C#中应用哈希表(Hashtable)

    一,哈希表(Hashtable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似key/value的键值对,其 ...

  2. 哈希表(hashtable)的javascript简单实现

    javascript中没有像c#,java那样的哈希表(hashtable)的实现.在js中,object属性的实现就是hash表,因此只要在object上封装点方法,简单的使用obejct管理属性的 ...

  3. C#中哈希表(HashTable)的用法详解

    1.  哈希表(HashTable)简述 在.NET Framework中,Hashtable是System.Collections命名空间提供的一个容器,用于处理和表现类似keyvalue的键值对, ...

  4. 哈希表 Hashtable c# 1613537346

    哈希表 Hashtable c# 1613537346 使用命名空间 using System.Collections; 实例化得到对象 Hashtable 对象 = new Hashtable(); ...

  5. hash table html,javascript 哈希表(hashtable)的简单实现

    首先简单的介绍关于属性的一些方法: 属性的枚举: for/in循环是遍历对象属性的方法.如 var obj = { name : 'obj1', age : 20, height : '176cm' ...

  6. 哈希表 HashTable对象 c#

    导入命名空间 using System.Collections; 生成哈希表对象 Hashtable ht = new Hashtable(); ht是变量名 哈希表特点 以键值对形式存值 模型是驿站 ...

  7. OpenAirInterface中的哈希表hashtable实现

    目录 项目名称 源代码 hashtable.h.obj_hashtable.h hashtable.c.obj_hashtable.c demos 项目名称 OpenAirInterface 源代码 ...

  8. Python数据结构实战——哈希表(HashTable)

    文章目录 1.定义哈希转换函数 2.定义哈希表类 2.1.不使用__setitem__ 2.2.使用__setitem__ 1.定义哈希转换函数 def get_hash(key):hash = 0f ...

  9. 哈希表 HashTable

    (1)哈希表底层存储结构也是线性表 (2)哈希表的核心在于哈希函数,哈希函数用于获取index值,决定了将元素放在哪个位置 (3)hash表的增删查时间复杂度都是O(1) 可以根据hash函数直接定位 ...

  10. 【从蛋壳到满天飞】JS 数据结构解析和算法实现-哈希表

    前言 [从蛋壳到满天飞]JS 数据结构解析和算法实现,全部文章大概的内容如下: Arrays(数组).Stacks(栈).Queues(队列).LinkedList(链表).Recursion(递归思 ...

最新文章

  1. 年薪25万只是白菜价,这几个专业的毕业生正被疯抢
  2. 柚子的小小笔记本-Linux中的简单运算
  3. 《西河大鼓——夸轿车》(唱词文本)
  4. Pull和SAX解析XML,以解析中国省市列表为例子
  5. First Week :Linux系统学习
  6. Android Unable to resolve target 'android-8'
  7. Beginning iCloud in iOS 5 Tutorial Part 2(转载)
  8. TEM014 - 新版阿里云网站界面高保真原型模板-AxureUX
  9. JButton的使用
  10. FCC拆解诺基亚808 PureView 内部细节全曝光
  11. 一名5年工作经验的程序员应该具备的技能
  12. RSA中的中国剩余定理(CRT)和多素数(multi-prime)
  13. 使用微信web开发者工具调试微信企业号页面
  14. java分词支持拼音_java 支持分词的高性能拼音转换工具,速度是 pinyin4j 的两倍...
  15. 国家基金申请书中的科学问题与关键问题
  16. 2023电子科技大学考研分析
  17. 全新国内互联网一线大厂面经:阿里中间件+蚂蚁金服+头条研发岗+抖音+京东+美团+百度
  18. 为什么在人工智能火爆的现在,我们还在做数据分析
  19. lua mysql 事务_Lua 操作数据库(MySQL)
  20. 必联路由器linux系统,bl lw06 ar 驱动下载

热门文章

  1. 网站域名服务器加密,网站域名利用https防劫持方法
  2. gojs开发环境去除水印
  3. 让AI为你制作思维导图 —— ChatMind
  4. Google Map API v3 - 设置边界和中心
  5. 计算机网络——网络硬件和网络设备及其工作原理
  6. 小岳岳逗乐,林志玲亲临,看科技如何助力十一出游
  7. 清除谷歌浏览器input框黄色底色
  8. 考生都难哭了,用 Python 分析了一下,这里才是高考地狱级难度
  9. 湖仓一体化:铁打的数据仓 流水的数据湖产品
  10. Windows Update错误80070003解决方法