哈希表由浅入深🔥
一、前置理解
哈希表
哈希表(也叫散列表),是根据键(Key)而直接访问在内存存储位置的数据结构。 哈希表底层就是数组。
hashCode
hashCode 称为 哈希码 也叫 散列码。是一个 int 值, 用来表示一个对象。 默认是通过地址转换的,一般重写让它是通过将该对象的某些信息进行转换而生成的。 hashCode方法 主要是为了给诸如HashMap这样的哈希表使用 。
哈希算法
简单的理解就是把 hashcode 转成下标的规则方式。
二、哈希表案例
举例: 我存储了一个学校的学生对象。现在有个需求, 我要查找姓名叫张三的学生。
数组解决
使用数组存储学生,然后从第一个元素遍历。 判断每一个学生的名字等不等于张三。 弊端 如果学生比较多。每次查询需要从第一个元素遍历,直到找到元素,效率太低了
哈希表解决
添加元素的时候,每个对象的 hashcode 通过算法转成下标。该学生对象保存在该下标。 我要找张三的话, 不管多少元素,直接把张三的 hashcode 转成下标, 通过下标一次查找就找到该元素了。
比如现在我使用的算法就是 (hashcode 对 数组长度取模),为了容易理解, 真正的算法肯定不是这样的。
弊端 可能会出现哈希冲突。
三、什么是哈希冲突
哈希冲突就是两个不同的元素,通过哈希函数得出的实际存储地址相同了。也叫哈希碰撞。
举个例子
数组长度是 10,张三对象的 hashcode 通过算法得到下标为 3, 李四对象的 hashcode 通过算法得到下标也是 3,这就是哈希冲突。
疑问:好的算法会哈希冲突吗
由于通过哈希函数产生的哈希值是有限的,拿 Java举例 hashcode的取值范围为 -2^31 到 2^31-1 而数据(对象)理论上是无穷的。 稍微思考一下就可以发现,既然数据是不定长的,但是哈希值是定长的,哈希值是有限集,而数据可以是无限多的。 所以“碰撞”是必然会发生的,一个成熟的哈希算法会有更好的防碰撞性。 但是还是会出现哈希冲突
四、解决哈希冲突
JDK1.7 实现方式
哈希冲突的解决方案有很多种,这里讲其中一种:链地址法 JDK 1.7 即之前版本 HashMap 使用的就是这种方式
当出现冲突时,在冲突的地址上生成一个链表,将冲突的元素的 key,通过equals进行比较。 这时候就算张三和李四冲突了。equals 比较他们不是同一个人,李四放在链表的第二个位置即可。是同一个人就不会添加重复元素张三了。 查找张三和李四的时候 还是通过 哈希算法 得到索引,再拿索引中链表的元素一个一个比较, 效率仍然很高
JDK1.8 实现方式
JDK1.7 中,如果链表过长的话,查询效率会降低, JDK1.8 之后,又添加了红黑树,如果链表过长了会自动把该链表转成红黑树,从而提高效率
五、为什么必须重写 hashcode 和 equals
为什么重写 hashcode
hashcode 方式是Object 中的本地方法,返回的是对象地址转成的 int 值。 因为要保证相同的对象hashcode相同,所以需要重写。
1. 未重写hashcode
未重写hashcode
public static void main(String[] args) {
Student student = new Student("小明", 18);
Student student2 = new Student("小红", 20);
Student student3 = new Student("小红", 20);
System.out.println(student.hashCode());
System.out.println(student2.hashCode());
System.out.println(student3.hashCode());
}
1735600054
21685669
2133927002
在我们看来 student2 和 student3 是同一个元素, 但是 hashcode 值却不同。 问题: 这样的话,使用 hashMap 保存数据就会导致添加的数据在数组中重复,因为他是通过 hashcode 确定索引位置的。
2. 重写hashcode
因为要保证相同的对象 hashcode 相同,所以必须要重写。 可以使用 IDEA 快速重写,大概重写规则就是和属性关联,属性相同的对象,hashcode相同。
public static void main(String[] args) {
Student student = new Student("小明", 18);
Student student2 = new Student("小红", 20);
Student student3 = new Student("小红", 20);
System.out.println(student.hashCode());
System.out.println(student2.hashCode());
System.out.println(student3.hashCode());
}
23458772
23653826
23653826
为什么重写equals
因为需要使用 equals 判断对象是否相等,避免一个链表上重复添加相同对象。
结论
重写 hashcode 保证数组中相同元素不重复 重写 equals 保证链表中相同元素不重复
- equals相等,hashCode一定相等
- equals不等,hashcode不一定不等
|