Map接口实现类之一:HashMap集合
1. 底层是哈希表/散列表的数据结构
哈希表:是一个一维数组,这个数组中的每一个元素是一个单向链表(是数组和单向链表的结合体)
/*源代码:
* public class HashMap<K,V>{
* //1. HashMap的底层是一个一维数组
* transient Node<K,V>[] table;
* //2. 有静态内部类:HashMap.Node
* static class Node<K,V> implements Map.Entry<K,V> {
final int hash; //哈希值(哈希值是key的hashCode()方法执行的结果,哈希值hash通过哈希函数/算法,可以得到数组的下标)
final K key; //存储到Map集合中的key
V value; //存储到Map集合中的value
Node<K,V> next; //同一个单链表上的下一个结点的内存地址
}
* }*/
2.掌握 map. put(k,v) 和 v = map.get(k)方法的实现原理
1. map. put(k,v) 存
- 先将k和v封装到Node对象中
- 底层会调用k的hashCode()方法得到hash值,通过哈希函数/算法,可以得到数组的下标
- 下标位置上如果没有任何的元素,就把Node添加到这个位置上了;如果下标对应的位置上有单链表,就会拿着k和链表上的每一个结点中的k进行equals,若所有的equals方法返回的都是false,那么这个新节点就将被添加到链表的末尾;若其中有一个equals返回了ture,那么原来结点的value 就会被新节点大的value所覆盖。
2. v = map.get(k) 取
- 底层会调用k的hashCode()方法得到hash值,通过哈希函数/算法,可以得到数组的下标
- 通过数组的下标快速定位某位置,若下标位置上如果没有任何的元素,就返回null;如果下标对应的位置上有单链表,就会拿着k和链表上的每一个结点中的k进行equals,若所有的equals方法返回的都是false,那么get方法就返回null;若其中有一个equals返回了ture,那么此结点所对应的value就是要找的,get方法就返回此结点所对应的value。
3. 解释:HashMap集合的key的特点:无序和不可重复 why?
(1)无序: 因为往集合中存时,不一定挂到哪个单链表上;所以存进去的顺序和取出时的不同
(2)不可重复:在 map. put(k,v) 方法中,equals方法来保证HashMap集合的key不可重复,若key重复了,value 将会被覆盖
4. 解释:哈希表的随机增删和查询效率都很高?
因为增删是在链表上完成,查询也不需要将所有数据都扫描一遍
数组在查询方面效率较高,随机增删效率低;而单向链表在查询方面效率较低,随机增删效率高;哈希表将二者结合在一起,发挥其优点。
3. HashMap集合的key部分和放在HashSet集合中的元素,要同时重写hashCode方法和equals方法
以上可知 :HashMap集合的key,会先后调用hashCode()方法和equals方法,要重写这两个方法;又因为HashMap集合的key是放到HashSet集合,所以HashSet集合也要重写这两个方法。
- 注意:同一个单向链表上所有结点的hash值相同,因为它们所对应的数组下标相同,但同一个单向链表上所有结点的k和k的equals方法返回的都是false,都不相等
- hashCode()方法重写时:若返回值是个固定的值,那么会导致底层的哈希表变成了纯单向链表;若返回值都设定为不同的值,那么会导致底层的哈希表变成了一维数组;称以上两种情况为 :散列分布不均匀;
- 散列分布均匀:100个元素,对应了10个数组元素,即对应了10个单向链表,每个单向链表上有10个结点
- hashCode()方法和equals方法都是子类继承于祖先类Object中的,其中,equals方法用==,比较的是两个对象的内存地址,要重写,来比较二者内容
- HashMap集合的默认初始化容量是16,默认加载因子是0.75,(当HashMap集合底层的数组的容量达到75%的时候,数组开始扩容)
- 官方推荐: HashMap集合的初始化容量必须是2的倍数;因为要达到散列均匀,有利于提高HashMap集合的存取效率。
package 集合练习;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class Map3 {
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
//key的类型是Integer,Integer的hashCode()和equals方法都重写了
map.put(1, "diyi");//1是自动装箱
map.put(2, "dier");
map.put(3, "disan");
map.put(3, "disi");
System.out.println(map.size());//3
//遍历
Set<Map.Entry<Integer, String>> set = map.entrySet();
for(Map.Entry<Integer, String> entry:set) {
System.out.println(entry.getKey()+"="+entry.getValue());
}
}//验证了:key重复的时候,value会被新的所覆盖; key的特点:无序不可重复
/*
* 3
1=diyi
2=dier
3=disi
*/
}
如果一个类的equals方法重写了,那么hashCode方法必须重写!equals方法返回若是true, hashCode方法的值必须一样
- 向Map集合中存以及取,都是先调用 key的hashCode方法,再调用equals方法。
- 如果equals方法返回true,表示两个对象相同,即在同一个单向链表上的同一个结点来说,它们的哈希值hash值也相同,那么hashCode方法的返回值相同
package 集合练习;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Map4 {
public static void main(String[] args) {
Student s1 = new Student("li");
Student s2 = new Student("li");
// //重写equals方法之前
// System.out.println(s1.equals(s2));
// System.out.println("s1的hashCOde="+s1.hashCode());
// System.out.println("s2的hashCOde="+s2.hashCode());
/*false
s1的hashCOde=292938459
s2的hashCOde=917142466*/
//重写equals方法之后
// System.out.println(s1.equals(s2));
// System.out.println("s1的hashCOde="+s1.hashCode());
// System.out.println("s2的hashCOde="+s2.hashCode());
/*true
s1的hashCOde=292938459
s2的hashCOde=917142466*/
// Set<Student> students = new HashSet<>();
// students.add(s1);
// students.add(s2);
// System.out.println(students.size());//2
// s1.equals(s2)是true,表明s1和s2是一样的,那么往HashSet集合(不可重复)中放,应该只能放一个,相矛盾;
//借助工具重写equals和hashcode
System.out.println(s1.equals(s2));
System.out.println("s1的hashCOde="+s1.hashCode());
System.out.println("s2的hashCOde="+s2.hashCode());
/*
* true
s1的hashCOde=3484
s2的hashCOde=3484*/
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
System.out.println(students.size());//1
}
/*总结:
*如果一个类的equals方法重写了,那么hashCode方法必须重写
*equals方法返回若是true, hashCode方法的值必须一样*/
}
class Student{
private String name;
public Student() {
super();
}
public Student(String name) {
super();
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// //手动重写equals方法:
// public boolean equals(Object obj) {
// if(obj==null || !(obj instanceof Student)) return false;
// if(obj==this) return true;
// Student student = (Student)obj;
// return this.name.equals(student.name);
// }
//借助工具重写equals和hashcode
@Override
public int hashCode() {
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Student other = (Student) obj;
return Objects.equals(name, other.name);
}
}
补充:
在JDK8之后,若哈希表的单向链表中的元素超过8个,单向链表这种数据结构就会变成红黑树这种数据结构。
当红黑树上的结点的数量小于6时,会重新把红黑树变成单向链表这种数据结构
这种方式是为了提高检索效率,二叉树的检索会再次缩小扫描范围,提高效率。
分类:
Java的笔记
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 25岁的心里话
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现