我们知道,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
充当缓存,以提高查找效率: