文章目录
1、什么是哈希值?
对哈希表就是通过将关键值,也就是key通过一个散列函数加工处理之后得到一个值,这个值就是数据在数组中存放的位置,我们就可以根据这个值快速的找到我们想要的数据
键值对 --> entry
其他的某些语言可能叫做键值对;
在JDK起了一个高大上的名字:entry;
本质上面是一个东西
1.1、关于哈希值
哈希表就是根据key值来通过哈希函数计算得到一个值;
这个值就是用来确定这个Entry要存放在哈希表中的位置的;
实际上这个值就是一个下标值,来确定放在数组的哪个位置上;
1.2、value 当然和 HashCode 不一样
1、从名字上来讲,不是一个东西;
2、value 是键值对里面的;
3、HashCode 是 key 经过哈希函数然后得到的,目的是找到entry 也就是键值对在哈希表中应该存储的下标,也就是存储的位置
1.3、解决哈希冲突的办法
1.3.1、开放寻址法
当前的哈希表中的位置被占用了,找一个旁边的位置,把相关的元素放进去;
1.3.2、拉链法
使用了数组和链表结合的方式,在键值对也就是 entry 里面加一个指向下一个元素的指针,进行元素的存放:
- 当拉链里面的键值对个数 >= 8 的时候,将会把链表转换成为树的结构;
- 当拉链里面的键值对个数 <= 6 的时候,保持原来的链表的形式;
- 拉链的键值对个数是 7 的时候,为了避免缓冲,避免频繁的进行链表和树之间的转换;
1.4 关于哈希表的扩容机制
1.4.1、为什么扩容?
当哈希表被占的位置比较多的时候,出现哈希冲突的概率也就变高了,所以很有必要进行扩容;
简单理解成为在开放寻址法中,里面的位置都被占用光了,没有存放的位置可以使用了,需要进行扩容;
1.4.2、怎么扩容?
有一个增长因子的概念,也叫作负载因子,简单点说就是已经被占的位置与总位置的一个百分比,比如一共十个位置,现在已经占了七个位置,就触发了扩容机制,因为它的增长因子是0.7,也就是达到了总位置的百分之七十就需要扩容。
- 在HashMap中,当它当前的容量占总容量的百分之七十五的时候就需要扩容了。
而且这个扩容也不是简单的把数组扩大,而是新创建一个数组是原来的2倍,然后把原数组的所有Entry都重新Hash一遍放到新的数组;
- Hash也就是把之前的数据通过新的哈希函数计算出新的位置来存放。
2、JDK 中HashMap 的相关实现知识
/**
* 哈希表 或者叫做 散列表数据结构
* 1、HashMap 的底层数据结构?
* 底层是哈希表 / 散列表的数据结构
* 2、哈希表是什么样子的数据结构?
* 哈希表是一个数组和链表的结合体;
* 数组:在查询方面效率比较高,在随机增删方面的效率比较低下;
* 链表:在随机增删方面的效率比较低下,在查询方面的效率很低;
* 哈希表将上面的两种数据结构进行结合,充分发挥各自的优点;
* 3、HashMap集合底层的源代码
* public class HashMap{
* // 实际上是一个数组(一纬的数组)
* Node<K,V>[] table;
*
* // 静态的内部类
* static class Node<K,V> {
* final int hash; // 哈希值,是 key 的hashCode() 方法执行结果,哈希值通过了哈希函数的算法,转换成为了数组的下标
* final K key; // 存储带 Map 集合中的 key
* V value; // 存储到 Map 集合中的 value
* Node<K,V> next; // 下一个节点的内存地址
* }
* }
*
* 哈希表 / 散列表:是一个一维数组,数组中的每一个元素是一个单向链表,数组和链表的结合体
*
* 4、需要掌握的是:
* map.put(k,v);
* v = map.get(k);
* 上面的两个方法是需要掌握的;
*
* map.put(k,v) 实现原理;
* 1、将 k,v 封装到 Node 对象之中
* 2、底层调用 key 的hashCode() 方法得到 hash 值,通过哈希函数/哈希算法将 hash 值转换为数组的下标,
* 下标的位置上面如果没有任何的元素,就将 Node 添加到这个位置上面,如果说下面对应的位置上面,有链表节点,
* 此时会拿着 key 和链表的每一个节点进行 equals ,如果所有的 equals 返回的都是 false ,这个节点会添加到链表的结尾
* 如果其中有一个 equals 返回了true ,那么将相等节点上面的 value 替换掉即可;
*
* map.get(k) 实现原理:
* 1、先调用 k 的hashCode 方法得到哈希值,通过哈希算法转换成为数组下标;
* 2、通过数组下标快速定位到某个位置上面,如果这个位置上面什么也没有,返回 null ,如果在这个位置上面有一个单向链表(因为
* 产生了哈希冲突),会拿着参数 key 和单向链表上面的节点中的 key 进行 equals ,所有的 equals 方法返回 false,
* get 返回 null ,只要有其中的一个节点返回的是 true ,说明这个时候已经找到了自己想要找到的元素,那么返回这个节点
* 的 value 即可;
*
* 5、为什么哈希表的随机增删以及查询的效率都是比较高的?
* 因为随机增删是在链表上面操作的(选择了这个处理哈希冲突的方式,有了冲突直接形成一个链表);
* 因为索引是在数组上面进行的,使用了哈希函数,将 key 转换成为了数组中的下标,查询时候,只需要扫描其中的一个即可;
* 没有纯数组和纯链表的性能好,这是一个结合体,是一个中间的选择;
*
* 6、总结上面的内容:HashMap 集合中的 key ,会先后使用了两个方法,一个是 hashCode(),一个是equals 方法,需要对其进行重写;先是用了hashCode 方法进行了数组位置的寻找,使用 equals() 方法将节点装进去,装的时候,需要比较 value 中的值;
*
* 7、为什额需要重写 equals 方法?
* 因为 equals 方法默认比较的是两个对象的地址,现在需要比较内容才能进行下一步的操作;
*
* 8、HashMap 集合中 key 的特点:
* 无序,不可重复
* 为什么无序?
* 因为在加入的时候,不知道会加入到什么地方,随机的链表的添加
* 为什么不可重复?
* 因为加入的时候,重复的东西被覆盖掉了;
*/