HashMap又称之为散列表,这是一种使查找,插入和删除都具备良好性能的数据结构,其查找的时间复杂度为O()。散列表的核心技术就是散列,其核心原理就是依赖一个标识符来确定数据的位置,散列又分为静态散列和动态散列,下边来分析一下这两种散列方式:
1.静态散列
1.1散列表
在静态散列表中,会将所有的标识符存在一张表中,这张表就叫做散列表。通过一个函数可以确定
在散列表中位置,这个函数就叫做散列函数。散列表往往存放在一个连续的空间中,这个空间会被切割成n个空间,被称之为散列桶。每个散列桶可能又被切割成n个空间,被称之为槽,往往一个散列桶只包含一个槽。

为了统计衡量散列表的效率,分别用,
和
来表示一个散列表的标识符密度和装载密度。其中
表示标识符可能不同的组合次数,
,例如a和b两个字符,合组成如下a,b,aa,bb,ab,ba的6中组合。则标识符密度就为
,即为
。若散列表一共有
个散列桶,每个散列桶有
个槽,那么装载密度或者装载因子
,按上图算即为
。当两个不同的标识符
和
,通过散列函数得到
,那么则称
和
为同义词,只要散列桶中有空闲的槽,就可以将互不相同的同义词放到同一个散列桶中,这种现象被称之为散列冲突。但是如果将一个同义词放入到一个已满的散列桶中,那么就会发生溢出。
1.2散列函数
散列函数的作用就是将表示符
转换成散列表中的地址。一个优秀的散列函数应该是不侧重某一个散列桶,而是均匀分布的。
1.3散列冲突和溢出处理
1.3.1线性探测法
散列表被存储为一个线性数组,其下标从0到散列表的期望大小-1。当插入一个时,如果通过
计算的位置没有值,那么则将
存储在这个位置上,如果这个位置的散列桶已满,那么就将其存储在离
最近的并且未满的散列桶中,这种解决散列冲突的方法就被称之为线性探测法。

从上图可以看出ab和af是同义词当插入af的时候,首先会去找散列表下标为1的位置,发现这个位置有值了,并且不是af,那么继续往下找,一直找到下表为3的地方发现了一个未满的散列桶,将af存储在这个位置。
1.3.2拉链法
通过上边的分析,我们可以看出线性探测法的效率是很低,容易形成标识符堆积。还有一种常用的方法就是拉链法。如上图ac和af是同义词,那么我们可以用一个单向的链表来存储同义词。

这种结构可以说比线性探测法要好上很多,插入的时候只需要计算散列值,将同义词放在一张同义词链表中,查找的时候只需要查找这张同义词链表即可。
2.动态散列
静态散列有一个非常致命的缺点,那就是我们无法确定静态散列表的期望大小,如果过小,非常容易产生溢出,如果过大,因为静态散列表要提前非配好内存,这样就会造成内存浪费。而动态散列就是能够动态的对散列表进行扩展的一中技术。
对于动态散列表来说,散列桶被称之为页面。假如说一个页面容量为2,有以下几个标识符和散列值

要求根据散列值的低两位来确定标识符所处页面的位置,0选择上边的分支,1选择下边的分支,则有如下的存储

加入说这个时候插入一个,并且
,那么可以看到这个时候就会产生溢出了,如果溢出了动态散列表会怎么处理呢?

因为一个页面的容量为2,a,b,c三者的低两位都为10,这个时候就会产生溢出,那么就新增一个页面,按照低三位来进行索引,这样就实现了动态扩容,所以动态散列的散列函数就是将标识符转换成二进制表示,来对标识符的在散列表中的位置进行索引。
3.散列表的实现及分析-----Java中的HashMap
HashMap是Java中一个非常重要的集合,其基本原理也是基于散列表进行扩展而实现的,下边来仔细分析一下其核心代码。

可以看出数据节点中包含了一个节点的hash值,key,value已经一个next节点,通过这个基本可以看出它是采用拉链法来解决散列冲突的。

这段代码定义了HashMap的散列表,可以看出它是一个线性数组。使用的是静态散列表的方式。其散列函数很简单,即节点元素的hashCode无符号右移16位即可,就不重点介绍了下边重点来分析它的插入和查找方法。

通过代码我们可以看到Java中的HashMap是这样插入一条新数据的,首先通过散列函数来计算出节点所处于的散列桶位置,如果为空,那么直接将该节点放入到这个位置,如果这个位置不为空,但是插入的节点和老节点的key相同,那么会将新的value值赋予到这个节点上,如果不满足这一条件,即产生了散列冲突,那么这个时候就采用拉链法进行处理,当然Java中的HashMap在这里有一个细节,即当散列桶的链表长度超过8时,将其转换成红黑树,红黑树的好处下一章节来进行分析。

这个逻辑就很简单了,首先判断第一个元素是不是要找的元素,如果是直接返回,如果不是,就遍历这个链表,找到了就返回这个元素,没找到就返回null值。
网友评论