Java内置的集合框架,除了提供Collection单列集合外,还提供所谓双列集合Map,即所谓存储“键值对”的集合。键和值是两个对象,而键值对在Java中称为entry。
与Collection相似,Map是一个接口,有HashMap、LinkedHashMap和TreeMap三种实现类,这三种实现类与Set的三种实现类(HashSet、LinkedHashSet和TreeSet)在底层实现上相同。实际上,Map只是比Set增加了一个“值对象”。准确来说,HashSet的内部实现基于 HashMap,将其值视为一个固定值。因此,Map具有与Set相似特点:不保证顺序、键不可重复(但值可重复)、不可索引。
Map的方法与Set也很相似,但是额外多了有关值的方法。Map常见方法如下:
Vput(Kkey,Vvalue);// 添加键值对,如果键存在会返回旧值,如果键不存在则返回nullVremove(Objectkey);// 删除键值对,返回旧值。若键不存在则返回nullbooleanremove(Objectkey,Objectvalue);// 删除指定键值对voidclear();// 清空MapVget(Objectkey);// 获取指定键的值booleancontainsKey(Objectkey);// 是否包含键booleancontainsValue(Objectvalue);// 是否包含值intsize();// 元素数量booleanisEmpty();// 是否为空对常见方法的代码演示如下:
publicclassDemo0{publicstaticvoidmain(String[]args){// 创建Map集合对象Map<String,String>map=newHashMap<>();// boolean isEmpty(); 判断集合是否为空System.out.println(map.isEmpty());// true// V put(K key, V value); 添加键值对,如果键存在会返回旧值,同时覆盖旧值,如果键不存在则返回nullSystem.out.println(map.put("郭靖","黄蓉"));// nullSystem.out.println(map.put("杨过","小龙女"));// nullSystem.out.println(map.put("韦小宝","沐剑屏"));// nullSystem.out.println(map.put("韦小宝","阿珂"));// 沐剑屏System.out.println(map);// {郭靖=黄蓉, 杨过=小龙女, 韦小宝=阿珂}// int size(); 获取键值对数量System.out.println(map.size());// 3System.out.println(map.isEmpty());// false// boolean containsKey(Object key); 判断集合中是否包含指定的键System.out.println(map.containsKey("郭靖"));// trueSystem.out.println(map.containsKey("小龙女"));// false// boolean containsValue(Object value); 判断集合中是否包含指定的值System.out.println(map.containsValue("阿珂"));// trueSystem.out.println(map.containsValue("沐剑屏"));// false// V remove(Object key); 删除键值对,返回旧值。若键不存在则返回nullSystem.out.println(map.remove("郭靖"));// 黄蓉System.out.println(map.remove("小龙女"));// null}}Map的遍历有三种方式:
- 通过键找值:获取Map中的所有key为新Set,再通过key找到value。
- 通过键值对:获取Map中的键值对对象成一个新Set,再遍历键值对对象找key、value。
- 使用foreach方法用lamda表达式
publicclassDemo1{publicstaticvoidmain(String[]args){// 创建Map集合Map<String,String>map=newHashMap<>();// 添加元素map.put("郭靖","黄蓉");map.put("杨过","小龙女");map.put("韦小宝","沐剑屏");// 遍历方法1: 通过键找值:获取Map中的所有key为新单列集合,再通过key找到value。Set<String>keys=map.keySet();// keySet()方法获取Map中的所有key集合for(Stringkey:keys){Stringvalue=map.get(key);// get(key)方法通过key找到valueSystem.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){Stringkey=entry.getKey();// Entry对象的getKey()方法获取keyStringvalue=entry.getValue();// Entry对象的getValue()方法获取valueSystem.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特点是有序、键不重复、不可索引。如下可以看出,遍历获取的顺序与插入顺序相同:
publicclassDemo2{publicstaticvoidmain(String[]args){// 创建一个Map集合对象Map<Student,String>map=newLinkedHashMap<>();// 添加三个元素map.put(newStudent("张三",18),"成都");map.put(newStudent("李四",19),"上海");map.put(newStudent("王五",20),"北京");// 遍历键值对for(Map.Entry<Student,String>entry:map.entrySet()){System.out.println(entry.getKey()+"="+entry.getValue());}}}Student{name='张三',age=18}=成都 Student{name='李四',age=19}=上海 Student{name='王五',age=20}=北京 进程已结束,退出代码为0TreeMap
TreeMap是Map的实现类,底层使用红黑树。
TreeMap与TreeSet相似,HashSet节点Node中存储的内容是元素本身,而HashMap存储的内容是键值对对象,又称为Entry对象。
树这一数据结构要求存储的元素需要能够比较,因此TreeMap存储的键值对对象的键也需要支持比较。