Java集合源码分析之基础(二):哈希表

不管是数组仍是链表,其对数据的查询表现都比较无力,要想知道一个元素是否在数组或链表中,只能从前向后挨个对比。出现这个问题的根源在于,咱们没有办法直接根据一个元素找到它存储的位置,那有没有办法消除这个对比的过程呢?git

哈希表就是解决查询问题的一种方案。在后续将会分析的二叉排序树中,还会将数据排序以进行二分查找,将时间复杂度从O(n)下降到O(lg n)github

哈希表与Hash函数

通俗来说,哈希表就是经过关键字来获取数据的一种数据结构,它经过把关键字映射为表中的位置来获取元素,这种映射主要是使用Hash函数。编程

Hash函数,其实是创建起key值与int值映射关系的函数。这就比如咱们每一个人都有一个身份证号同样,不管是男是女,出生在何处,均可以经过身份证号来分辨,这就是把人的信息映射成一串数字的典型作法。Hash函数和此相似,不过是把任意的Java对象,映射成一个int数值,供哈希表使用。数组

而哈希表,就是一个数组,只是其元素不是按照数组的规则排列的。任何一个元素要放进哈希表中,都必须先经过Hash函数获取到一个int数值,这个数值通过处理后将做为它的存放位置,而后这个元素才能放进哈希表中。微信

能够发现,数组与哈希表的操做不一样之处主要在于,前者是直接插入,后者须要经过Hash函数计算后再插入。能够经过下图对比来理解:数据结构

数组的插入

哈希表的插入

哈希表彻底继承了数组的优势,又显著的提升了查询的速度,经过Hash函数使得查询速度达到了O(1)。既然有了哈希表,它这么优秀,为什么还须要数组的存在呢?那是由于Hash表是有缺陷的,这个缺陷就是哈希碰撞函数

哈希碰撞

Hash函数所作的事,就是不管什么对象,都根据一个规则映射为一个int值。被转换的对象有无数种可能,可是int的值是有限的,它只有2^32^个,这样一来,必然会有不一样的对象,映射获得相同的int值,这就是所谓的哈希碰撞。发生碰撞以后,就要把不一样的元素插入到相同的位置,这时候单纯的使用一维数组已经没法知足需求了。源码分析

解决哈希碰撞的方法

要解决哈希碰撞,咱们能够想到多种解决方案。例如使用二维数组,将碰撞的元素按顺序存储起来,相似下图:post

二维数组存储

这样的方式有一个很大的诟病,由于数组大小是固定的,因此第二维的数组长度都是同样的,可是哈希碰撞必定是比较少发生的状况,也就是咱们声明了一个很大的数组,可是其中大部分都是闲置的,这就浪费了大量的内存。性能

还有一些方案是考虑了哈希表的散列化,将元素插入到空闲的位置去。由于哈希表基本不会像数组同样每一个位置都有元素,这样就能够将碰撞的元素插入到这些空闲的位置中区,这种方案称为定址法。可是这个方法在扩展性上表现不佳,咱们这里就再也不浪费篇幅来解释它了。

目前比较通用的方法,就是使用数组+链表组合的方式。当出现哈希碰撞时,在该位置的数据就经过链表的方式连接起来,以下图所示:

哈希表的结构示意图

这是当前比较理想的方法,既继承了数组的优势,又在碰撞时继承了链表的优势,这也是哈希表强大的地方之一。

在JDK1.7及以前的版本中,HashMap的存储结构和上图是一致的,在JDK1.8以后还加入了红黑树以进一步优化,在后续文章中咱们会对其进行详尽的分析。

哈希表的优缺点

哈希表是一种优化存储的思想,具体存储元素的依然是其余的数据结构。设计良好的哈希表,能同时兼备数组和链表的优势,它能在插入和查找时都具有良好的性能。然而设计很差的哈希表,有可能会出现较多的哈希碰撞,致使链表过长,从而哈希表会更像一个链表。还有当数据量很大时,为防止链表过长,就须要对数组进行扩容,这时就涉及到了数组的拷贝,其对性能的影响也很严重,因此须要提早对可能的状况有良好的预测,才能真正发挥哈希表的优点。

上一篇:Java集合源码分析之基础(一):数组与链表

下一篇:Java集合源码分析之基础(三):树与二叉树

本文到此就结束了,若是您喜欢个人文章,能够关注个人微信公众号:大大纸飞机

或者扫描下方二维码直接添加:

公众号

您也能够关注个人github:https://github.com/LtLei/articles

编程之路,道阻且长。惟,路漫漫其修远兮,吾将上下而求索。

相关文章
相关标签/搜索