Java内置的集合框架,除了提供Collection单列集合外,还提供所谓双列集合Map,即所谓存储"键值对"的集合。键和值是两个对象,而键值对在Java中称为entry。
与Collection相似,Map是一个接口,有HashMap、LinkedHashMap和TreeMap三种实现类,这三种实现类与Set的三种实现类(HashSet、LinkedHashSet和TreeSet)在底层实现上相同。实际上,Map只是比Set增加了一个"值对象"。准确来说,HashSet的内部实现基于 HashMap,将其值视为一个固定值。因此,Map具有与Set相似特点:不保证顺序、键不可重复(但值可重复)、不可索引。
Map的方法与Set也很相似,但是额外多了有关值的方法。Map常见方法如下:
java
V put(K key, V value); // 添加键值对,如果键存在会返回旧值,如果键不存在则返回null
V remove(Object key); // 删除键值对,返回旧值。若键不存在则返回null
boolean remove(Object key, Object value); // 删除指定键值对
void clear(); // 清空Map
V get(Object key); // 获取指定键的值
boolean containsKey(Object key); // 是否包含键
boolean containsValue(Object value); // 是否包含值
int size(); // 元素数量
boolean isEmpty(); // 是否为空
对常见方法的代码演示如下:
java
public class Demo0 {
public static void main(String[] args) {
// 创建Map集合对象
Map<String, String> map = new HashMap<>();
// boolean isEmpty(); 判断集合是否为空
System.out.println(map.isEmpty()); // true
// V put(K key, V value); 添加键值对,如果键存在会返回旧值,同时覆盖旧值,如果键不存在则返回null
System.out.println(map.put("郭靖", "黄蓉")); // null
System.out.println(map.put("杨过", "小龙女")); // null
System.out.println(map.put("韦小宝", "沐剑屏")); // null
System.out.println(map.put("韦小宝", "阿珂")); // 沐剑屏
System.out.println(map); // {郭靖=黄蓉, 杨过=小龙女, 韦小宝=阿珂}
// int size(); 获取键值对数量
System.out.println(map.size()); // 3
System.out.println(map.isEmpty()); // false
// boolean containsKey(Object key); 判断集合中是否包含指定的键
System.out.println(map.containsKey("郭靖")); // true
System.out.println(map.containsKey("小龙女")); // false
// boolean containsValue(Object value); 判断集合中是否包含指定的值
System.out.println(map.containsValue("阿珂")); // true
System.out.println(map.containsValue("沐剑屏")); // false
// V remove(Object key); 删除键值对,返回旧值。若键不存在则返回null
System.out.println(map.remove("郭靖")); // 黄蓉
System.out.println(map.remove("小龙女")); // null
}
}
Map的遍历有三种方式:
- 通过键找值:获取Map中的所有key为新Set,再通过key找到value。
- 通过键值对:获取Map中的键值对对象成一个新Set,再遍历键值对对象找key、value。
- 使用foreach方法用lamda表达式
java
public class Demo1 {
public static void main(String[] args) {
// 创建Map集合
Map<String, String> map = new HashMap<>();
// 添加元素
map.put("郭靖", "黄蓉");
map.put("杨过", "小龙女");
map.put("韦小宝", "沐剑屏");
// 遍历方法1: 通过键找值:获取Map中的所有key为新单列集合,再通过key找到value。
Set<String> keys = map.keySet(); // keySet()方法获取Map中的所有key集合
for (String key : keys) {
String value = map.get(key); // get(key)方法通过key找到value
System.out.println(key + "=" + value);
}
System.out.println("---------分割线-----------");
// 遍历方法2: 通过键值对:获取Map中的键值对对象成一个新单列集合,再遍历键值对对象找key、value。
Set<Map.Entry<String, String>> entries = map.entrySet(); // entrySet()方法获取Map中的键值对对象集合
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey(); // Entry对象的getKey()方法获取key
String value = entry.getValue(); // Entry对象的getValue()方法获取value
System.out.println(key + "=" + value);
}
System.out.println("---------分割线-----------");
// 遍历方法3: 通过foreach方法
map.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
HashMap
HashMap是Map的实现类,底层使用哈希表。
HashMap与HashSet相似,HashSet节点Node中存储的内容是元素本身,而HashMap存储的内容是键值对对象,又称为Entry对象。
HashMap的元素插入逻辑:
- 将键和值两个对象,组成一个键值对Entry对象
- 用键调用hashCode()方法,计算键的哈希值
- 通过哈希值和哈希表数组的长度,计算数组索引值
- 若数组为null,则存储键值对对象
- 若数组不为null,则调用equals()方法,逐一判断链表中已存在的键值对的键,是否相等
- 若键相同,则覆盖键值对对象
- 若链表中的键都不同,则最后插入到链表尾部。
因此,若用HashMap,与HashSet相似,需要重写键类的hashCode()和equals()方法。
LinkedHashMap
LinkedHashMap是Map的实现类,底层使用哈希表。
LinkedHashMap与HashMap的关系,就跟LinkedHashSet与HashSet的关系相似,LinkedHashMap是在HashMap的节点基础上,增加了指向上一个插入元素Entry对象的节点和指向下一个插入元素Entry对象的节点。
因此,LinkedHashMap特点是有序、键不重复、不可索引。如下可以看出,遍历获取的顺序与插入顺序相同:
java
public class Demo2 {
public static void main(String[] args) {
// 创建一个Map集合对象
Map<Student, String> map = new LinkedHashMap<>();
// 添加三个元素
map.put(new Student("张三", 18), "成都");
map.put(new Student("李四", 19), "上海");
map.put(new Student("王五", 20), "北京");
// 遍历键值对
for (Map.Entry<Student, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}
shell
Student{name='张三', age=18}=成都
Student{name='李四', age=19}=上海
Student{name='王五', age=20}=北京
进程已结束,退出代码为 0
TreeMap
TreeMap是Map的实现类,底层使用红黑树。
TreeMap与TreeSet相似,HashSet节点Node中存储的内容是元素本身,而HashMap存储的内容是键值对对象,又称为Entry对象。
树这一数据结构要求存储的元素需要能够比较,因此TreeMap存储的键值对对象的键也需要支持比较。