共计 3981 个字符,预计需要花费 10 分钟才能阅读完成。
我们知道,List
是一种顺序列表,如果有一个存储学生 Student
实例的 List
,要在List
中根据 name
查找某个指定的 Student
的分数,应该怎么办?
最简单的方法是遍历 List
并判断 name
是否相等,然后返回指定元素:
List<Student> list = ... | |
Student target = null; | |
for (Student s : list) {if ("Xiao Ming".equals(s.name)) { | |
target = s; | |
break; | |
} | |
} | |
System.out.println(target.score); |
这种需求其实非常常见,即通过一个键去查询对应的值。使用 List
来实现存在效率非常低的问题,因为平均需要扫描一半的元素才能确定,而 Map
这种键值(key-value)映射表的数据结构,作用就是能高效通过 key
快速查找value
(元素)。
用 Map
来实现根据 name
查询某个 Student
的代码如下:
import java.util.HashMap; | |
import java.util.Map; | |
public class Main {public static void main(String[] args) {Student s = new Student("Xiao Ming", 99); | |
Map<String, Student> map = new HashMap<>(); | |
map.put("Xiao Ming", s); // 将 "Xiao Ming" 和 Student 实例映射并关联 | |
Student target = map.get("Xiao Ming"); // 通过 key 查找并返回映射的 Student 实例 | |
System.out.println(target == s); // true,同一个实例 | |
System.out.println(target.score); // 99 | |
Student another = map.get("Bob"); // 通过另一个 key 查找 | |
System.out.println(another); // 未找到返回 null | |
} | |
} | |
class Student {public String name; | |
public int score; | |
public Student(String name, int score) {this.name = name; | |
this.score = score; | |
} | |
} |
通过上述代码可知:Map<K, V>
是一种键 - 值映射表,当我们调用 put(K key, V value)
方法时,就把 key
和value
做了映射并放入 Map
。当我们调用V get(K key)
时,就可以通过 key
获取到对应的 value
。如果key
不存在,则返回 null
。和List
类似,Map
也是一个接口,最常用的实现类是HashMap
。
如果只是想查询某个 key
是否存在,可以调用 boolean containsKey(K key)
方法。
如果我们在存储 Map
映射关系的时候,对同一个 key 调用两次 put()
方法,分别放入不同的value
,会有什么问题呢?例如:
import java.util.HashMap; | |
import java.util.Map; | |
public class Main {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>(); | |
map.put("apple", 123); | |
map.put("pear", 456); | |
System.out.println(map.get("apple")); // 123 | |
map.put("apple", 789); // 再次放入 apple 作为 key,但 value 变为 789 | |
System.out.println(map.get("apple")); // 789 | |
} | |
} |
重复放入 key-value
并不会有任何问题,但是一个 key
只能关联一个 value
。在上面的代码中,一开始我们把key
对象 "apple"
映射到 Integer
对象 123
,然后再次调用put()
方法把 "apple"
映射到 789
,这时,原来关联的value
对象 123
就被“冲掉”了。实际上,put()
方法的签名是 V put(K key, V value)
,如果放入的key
已经存在,put()
方法会返回被删除的旧的value
,否则,返回null
。
始终牢记
Map 中不存在重复的 key,因为放入相同的 key,只会把原有的 key-value 对应的 value 给替换掉。
此外,在一个 Map
中,虽然 key
不能重复,但 value
是可以重复的:
Map<String, Integer> map = new HashMap<>(); | |
map.put("apple", 123); | |
map.put("pear", 123); // ok |
遍历 Map
对 Map
来说,要遍历 key
可以使用 for each
循环遍历 Map
实例的 keySet()
方法返回的 Set
集合,它包含不重复的 key
的集合:
import java.util.HashMap; | |
import java.util.Map; | |
public class Main {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>(); | |
map.put("apple", 123); | |
map.put("pear", 456); | |
map.put("banana", 789); | |
for (String key : map.keySet()) {Integer value = map.get(key); | |
System.out.println(key + "=" + value); | |
} | |
} | |
} |
同时遍历 key
和value
可以使用 for each
循环遍历 Map
对象的 entrySet()
集合,它包含每一个 key-value
映射:
import java.util.HashMap; | |
import java.util.Map; | |
public class Main {public static void main(String[] args) {Map<String, Integer> map = new HashMap<>(); | |
map.put("apple", 123); | |
map.put("pear", 456); | |
map.put("banana", 789); | |
for (Map.Entry<String, Integer> entry : map.entrySet()) {String key = entry.getKey(); | |
Integer value = entry.getValue(); | |
System.out.println(key + "=" + value); | |
} | |
} | |
} |
Map
和 List
不同的是,Map
存储的是 key-value
的映射关系,并且,它 不保证顺序 。在遍历的时候,遍历的顺序既不一定是put()
时放入的 key
的顺序,也不一定是 key
的排序顺序。使用 Map
时,任何依赖顺序的逻辑都是不可靠的。以 HashMap
为例,假设我们放入 "A"
,"B"
,"C"
这 3 个 key
,遍历的时候,每个key
会保证被遍历一次且仅遍历一次,但顺序完全没有保证,甚至对于不同的 JDK 版本,相同的代码遍历的输出顺序都是不同的!
注意
遍历 Map 时,不可假设输出的 key 是有序的!
练习
请编写一个根据 name
查找 score
的程序,并利用 Map
充当缓存,以提高查找效率:
import java.util.*; | |
public class Main {public static void main(String[] args) { | |
List<Student> list = List.of(new Student("Bob", 78), | |
new Student("Alice", 85), | |
new Student("Brush", 66), | |
new Student("Newton", 99)); | |
var holder = new Students(list); | |
System.out.println(holder.getScore("Bob") == 78 ? "测试成功!" : "测试失败!"); | |
System.out.println(holder.getScore("Alice") == 85 ? "测试成功!" : "测试失败!"); | |
System.out.println(holder.getScore("Tom") == -1 ? "测试成功!" : "测试失败!"); | |
} | |
} | |
class Students { | |
List<Student> list; | |
Map<String, Integer> cache; | |
Students(List<Student> list) {this.list = list; | |
cache = new HashMap<>();} | |
/** | |
* 根据 name 查找 score,找到返回 score,未找到返回 -1 | |
*/ | |
int getScore(String name) {// 先在 Map 中查找: | |
Integer score = this.cache.get(name); | |
if (score == null) {// TODO: | |
} | |
return score == null ? -1 : score.intValue();} | |
Integer findInList(String name) {for (var ss : this.list) {if (ss.name.equals(name)) {return ss.score; | |
} | |
} | |
return null; | |
} | |
} | |
class Student { | |
String name; | |
int score; | |
Student(String name, int score) {this.name = name; | |
this.score = score; | |
} | |
} |
下载练习
小结
Map
是一种映射表,可以通过 key
快速查找value
;
可以通过 for each
遍历 keySet()
,也可以通过for each
遍历entrySet()
,直接获取key-value
;
最常用的一种 Map
实现是HashMap
。
