HashMap 介绍
介绍
HashMap 是一个散列表,它存储的内容是键值对 (key-value) 映射。每一个键值对也叫做 Entry。它的 键 是不重复且无序的。
HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
HashMap 是无序的,即不会记录插入的顺序。
创建对象
HashMap map = new HashMap();
方法
方法 | 描述 |
---|---|
clear() | 删除 hashMap 中的所有键/值对 |
clone() | 复制一份 hashMap |
isEmpty() | 判断 hashMap 是否为空 |
size() | 计算 hashMap 中键/值对的数量 |
put() | 将键/值对添加到 hashMap 中 |
putAll() | 将所有键/值对添加到 hashMap 中 |
putIfAbsent() | 如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。 |
remove() | 删除 hashMap 中指定键 key 的映射关系 |
containsKey() | 检查 hashMap 中是否存在指定的 key 对应的映射关系。 |
containsValue() | 检查 hashMap 中是否存在指定的 value 对应的映射关系。 |
replace() | 替换 hashMap 中是指定的 key 对应的 value。 |
replaceAll() | 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。 |
get() | 获取指定 key 对应对 value |
getOrDefault() | 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值 |
forEach() | 对 hashMap 中的每个映射执行指定的操作。 |
entrySet() | 返回 hashMap 中所有映射项的集合集合视图。 |
keySet() | 返回 hashMap 中所有 key 组成的集合视图。 |
values() | 返回 hashMap 中存在的所有 value 值。 |
merge() | 添加键值对到 hashMap 中 |
compute() | 对 hashMap 中指定 key 的值进行重新计算 |
computeIfAbsent() | 对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中 |
computeIfPresent() | 对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。 |
举例:HashMap 使用练习
HashMap<Integer,String> map = new HashMap<>();
map.put(9527,"Mr White");
map.put(9528,"PinkMan");
map.put(9529,"Goodman");
//添加重复的键,新的值会覆盖以前的值
map.put(9528,"Gale");
map.put(9530,null);
map.put(null,"---");
System.out.println(map.size());//输出 5
//数据显示无序
System.out.println(map);//{null=---, 9527=Mr White, 9528=Gale, 9529=Goodman, 9530=null}
System.out.println(map.get(9527));//Mr White
System.out.println(map.get(9999));//null
System.out.println(map.containsKey(9527));//true
System.out.println(map.containsValue("Gus"));//false
//返回被移除的值
System.out.println(map.remove(9529));//Goodman
System.out.println(map.remove(9999));//null
举例:统计字符串中字母个数
System.out.println("请输入字母:");
String s = new Scanner(System.in).nextLine();
/*
* 1.新建 HashMap<Character,Integer> 赋值给 map
* 2.循环字符串 s 从0到 <s.length()递增
* 3.从s取i位置字符赋值给c
* 4.从map取出字符串c对应的计数值
* 赋值给count
* 5.如果count==null
* 6.向map放入字符c和计数1
* 7.否则
* 8.向map放入字符c和计数值count+1
* 9.打印map
*/
HashMap<Character, Integer> map = new HashMap<>();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
Integer count = map.get(c);
if (count == null) {
map.put(c, 1);
} else {
map.put(c, count + 1);
}
}
System.out.println(map);
}
运行结果:
了解哈希算法
存放数据
1、key.hashCode() 获得哈希值
2、用哈希值计算下标值
3、创建 Entry 对象封装键和值
4、如果负载率达到75%,容量翻倍
5、Entry对象放入 i 位置
-------6、如果是空位置,直接放入
-------7、如果不是空位置
--------------8、依次用 equals() 比 较每个键是否相等
--------------9、找到相等的覆盖值
取出数据
1、key.hashCode() 获得哈希值
2、用哈希值计算下标值
3、依次 equals() 比较每个键是否相等
-------4、找到相等的,返回对应的值
-------5、没有找到相同的键,返回 null
举例:哈希算法理解练习
新建 Student 类
class Student {
private int id;
private String name;
private String gender;
private int age;
public Student() {
}
public Student(int id, String name, String gender, int age) {
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
java
Student s1 = new Student(5, "李小明", "男", 22);
Student s2 = new Student(5, "李小明", "男", 22);
//两个对象的哈希值相同
//才能计算出相同的下标位置
//新分配的内存地址作为哈希值,所以不同
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
//即使hashCode()相等
//必须equals()也相等才能覆盖
System.out.println(s1.equals(s2));
HashMap<Student, Integer> map = new HashMap<>();
map.put(s1, 92);
map.put(s2, 98);
System.out.println(map);
输出结果
381259350
2129789493
false
{com.company.Student@7ef20235=98, com.company.Student@16b98e56=92}
对程序进行解释:
打印 map 时,自动调用类的toString()
方法,默认的是 类名+内存地址,可以在类中重写toString()
方法。
hashCode()
:获得对象的哈希值。Object 中默认实现是: 使用内存地址值作为哈希值,如果需要,可以在子类中重写这个方法 ,使用属性数据来计算产生哈希值。
学生对象 s1 和 s2 是同一个人,后面的成绩应该覆盖前面的成绩,但是由于hashCode()
不同,所以不会覆盖。这样实际上违背了我们的意图。因为我们在使用HashMap 时,希望利用相同内容的对象索引得到相同的目标对象,这就需要HashCode() 在此时能够返回相同的值。
要想用 s1 的值覆盖 s2 的值,不仅需要hashCode()
相等,equals()
必须也相等才可以,所以在 Student 中重写这3个方法
Student类
class Student {
private int id;
private String name;
private String gender;
private int age;
public Student() {
}
public Student(int id, String name, String gender, int age) {
this.id = id;
this.name = name;
this.gender = gender;
this.age = age;
}
public void setId(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "\n学号:" + id +
"\n姓名:" + name +
"\n性别:" + gender +
"\n年龄:" + age;
}
public int hashCode() {
/* 让不同的属性值
* 尽量计算出不同的哈希值
*
* 有一种算法
* 是数学家发明的
* 是一种有效的惯用算法
*/
int p = 31;
int r = 1;
r = r * p + id;
r = r * p + name.hashCode();
r = r * p + gender.hashCode();
r = r * p + age;
return r;
}
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj == this) {
return true;
}
if (!(obj instanceof Student)) {
return false;
}
Student s = (Student) obj;
return id == s.id &&
age == s.age &&
name.equals(s.name) &&
gender.equals(s.gender);
}
}
重新运行程序:
-617173112
-617173112
true
{
学号:5
姓名:李小明
性别:男
年龄:22=98}
Java 遍历 Map 对象的四种方式
首先初始化一个 map 数据
Map<String, String> map = new HashMap<String, String>();
map.put("0", "毫米");
map.put("1", "厘米");
map.put("2", "分米");
map.put("3", "米");
期望遍历此 map,输出 key 或 value,例如:
key=0,value=毫米
key=1,value=厘米
key=2,value=分米
key=3,value=米
方式一:使用 entrySet
这是最常见的并且在大多数情况下也是最可取的遍历方式
在键值都需要时使用
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("key=" + entry.getKey() + ",value=" + entry.getValue());
}
方式二:在 for-each 循环中遍历 keys 或 values
在 for-each 循环中遍历 keys 或 values
如果只需要 map 中的键 或者 值,你可以通过 keySet 或 values 来实现遍历,而不是用 entrySet,该方法比 entrySet 遍历在性能上稍好(快了10%),而且代码更加干净
//遍历map中的键
for (String key : map.keySet()) {
System.out.println("key=" + key);
}
//遍历map中的值
for (String value : map.values()) {
System.out.println("value=" + value);
}
方式三:使用 Iterator 遍历
该种方式看起来冗余却有其优点所在。首先,在老版本 java 中这是惟一遍历 map 的方式。另一个好处是,你可以在遍历时调用 iterator.remove()
来删除 entries,另两个方法则不能。根据 javadoc 的说明,如果在 for-each 遍历中尝试使用此方法,结果是不可预测的。
从性能方面看,该方法类同于for-each遍历(即方法二)的性能
--------使用泛型:
Iterator<Map.Entry<String, String>> entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry<String, String> entry = entries.next();
System.out.println("key=" + entry.getKey() + ",value=" + entry.getValue());
}
--------不使用泛型:
Iterator entries = map.entrySet().iterator();
while (entries.hasNext()) {
Map.Entry entry = (Map.Entry) entries.next();
System.out.println("key=" + entry.getKey() + ",value=" + entry.getValue());
}
方式四:通过键找值遍历(效率低)
作为方法一的替代,这个代码看上去更加干净;但实际上它相当慢且无效率。因为从键取值是耗时的操作(与方法一相比,在不同的Map实现中该方法慢了20%~200%)。所以尽量避免使用
for (String key : map.keySet()) {
String value = map.get(key);
System.out.println("key=" + key + ",value=" + value);
}
参考
Java HashMap