HashMap 原理解析

首先:说明HashMap是怎样去存储数据的。我们都知道是以‘键值对’的形式存在的。无序,不可重复。那么在存储的时候具体做了什么呢?

1.HashMap做了什么?

当集合要添加新的元素时,先调用这个元素的hashCode方法,就能定位到它应该放置的存储位置。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就表示发生冲突了,散列表对于冲突有具体的解决办法,但最终还会将新元素保存在适当的位置。这样一来,实际调用equals方法的次数就大大降低了,几乎只需要一两次。

2.HashMap的数据结构

HashMap的数据结构是基于数组和链表的。(以数组存储元素,如有hash相同的元素,在数组结构中,创建链表结构,再把hash相同的元素放到链表的下一个节点

hashMap的结构类似这样
  元素0-->[hashCode=0, key.value=x1的数据]
  元素1-->[hashCode=1, key.value=y1的数据]
  。。。。。。
  元素n-->[hashCode=n, key.value=z1的数据]

假设没有hashCode=1的元素加入,但是有两个hashCode=0的数据,它的结构就变成这样
  元素0-->[hashCode=0, key.value=x1的数据].next-->[hashCode=0, key.value=x2的数据]
  元素1-->[null]
  ......
  元素n-->[hashCode=n, key.value=z1的数据]

put和get都首先会调用hashcode方法,去查找相关的key,当有冲突时,再调用equals

3.HashMap原理:

HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存 键值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每个链表节点中储存键值对对象。

4.什么是hashing原理?(特别重要的思想)

例如,如果让你维护一个名字和电话号码的表,你该怎么组织这个表,以便你可以能够根据指定名字查找电话号码?

最显而易见的解决方案就是按照字母顺序保持列表的顺序,并使用某种形式的搜索,通常是二进制,来定位名字。这非常有效,但这只对保持严格顺序的列表有用。

如果添加名字和电话号码的频率很少,那么你可以使用一个不保持顺序的溢出表(overflow table)。然后你可以在排序表上进行字节搜索来检索名字,如果你没有找到该名字,在溢出表上进行线性搜索可以找到该名字或者确认该名字是否为未知。

以这种方式使用溢出表只需要保持溢出表的简短。如果你添加了大量名字,那么溢出表的线性查找的时间中将会迅猛增长。在这种情况下,是时候该考虑使用哈希了。

哈希是一种伟大的计算思想,每个程序员都应该对其有所了解。哈希的基本思路非常简单,事实上,它是如此的简单,你可以花时间来考虑它的魔力是从哪里来的。

假设你有这样一个函数: 
hash(name)

该函数根据输入的名字,通过一些计算法则返回区间[1,N]中的某个数值,那么为什么不将名字和电话号码存储在表的第 hash(name)位置呢。

注意哈希函数有点小奇特,它必须是完全确定性的。按理说就是对于你输入的同一名字,哈希函数始终返回相同的数值,而每个不同的名字则应返回不同的固定值。正如你所料这种特性并不总是能满足的。

使用这种方案,在表中查找名字,实际仅就是计算 hash(name)的值,然后在这个值标识的 表中的那个位置 去看看你要查的名字是不是存储在那了。

是的,这就是关于hash的全部,尽管还有一些其他的实用细节,但其核心思想就是 用 hash(name)来确定名字在表中的存储地址

例子

为了消除对‘哈希到底有多简单’的疑惑, 让我们看一个小小的例子。
 
如果哈希函数是:名字前两个字母的asc码值之和 减去128,也就是:

hash(ABCDE)=ASC(A)+ASC(B)-128   

其中ASC返回字母的asc编码值,那么名字"JAMES"将被存储在:

hash("JAMES") =  ASC("J")+ASC("A")-128

这个地址,说白了就是表中的第11个地址处。

现在如果你想知道你是否有JAMES的电话,那简单了,计算hash("JAMES")然后就去表中的第11个地址处看看有没有电话号码就行了。

不必排序,不必搜索,只要计算一下这个hash函数,你就知道在哪里存储和找到数据。

核心思想就是哈希函数接收文本或任何类型的数据然后输出基于这些数据的一组数值。

冲突

现在有一个具体的例子来看看我们一开始会遇到的一些问题。尤其是我们的哈希函数将JAMESON的电话号码存在哪里了?

是的,你已想到我们将JAMES也存储在同一个地方了。所以很清楚,怎样设计哈希函数是很重要的。事实上,很难给你演示不管你如何努力去解决冲突问题,诸如JAMES和JAMESON的问题依然会出现。 这就是hashCode为什么会出现相同的原因。


5.什么是buket?

对于 HashMap 及其子类而言,它们采用 Hash 算法来决定集合中元素的存储位置。当系统开始初始化 HashMap 时,系统会创建一个长度为 capacity 的 Entry 数组,这个数组里可以存储元素的位置被称为“桶(bucket)”,每个 bucket 都有其指定索引,系统可以根据其索引快速访问该 bucket 里存储的元素。

无论何时,HashMap 的每个“桶”只存储一个元素(也就是一个 Entry),由于 Entry 对象可以包含一个引用变量(就是 Entry 构造器的的最后一个参数)用于指向下一个 Entry,因此可能出现的情况是:HashMap 的 bucket 中只有一个 Entry,但这个 Entry 指向另一个 Entry ——这就形成了一个 Entry 链。

    如果你仔细看完这篇文章的话,相信你对HashMap有更深的理解了!













猜你喜欢

转载自blog.csdn.net/qq_38669394/article/details/80257135