8、Map(双列集合)
双列集合一次可以添加一对数据
双列集合的特点:
双列集合一次需要存一对数据,分别为键和值
键不能重复,值可以重复
键和值是一 一对应的,每一个键只能找到自己对应的值
键 + 值这个整体我们称为“键值对”或者“键值对对象”,在Java中叫做“Entry对象”
(1)Map的常见API
Map是双列集合的顶层接口,它的功能是全部双列集合都可以继承使用的
public interface Map<K,V>{}
Map的常见API
V put(K key,V value) 添加元素
V remove(Object key) 根据键删除键值对元素
void clear() 移除所有的键值对元素
boolean containsKey(Object key) 判断集合是否包含指定的键
boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isEmpty() 判断集合是否为空
int size() 集合的长度,也就是集合中键值对的个数
get(Object key) 如果存在指定的key对象,则返回该对象对应的值,否则返回null
keySet() 返回该集合中所有key对象形成的Set集合
(1)put方法的细节
在添加数据的时候,如果键不存在,那么直接把键值对对象添加到map集合当中,方法返回null。
在添加数据的时候,如果键存在,那么会把原有的键值对对象覆盖,会把被覆盖的值进行返回。
(2)remove方法的细节
在删除数据时,如果键不存在,那么返回null,如果存在,返回该对象的值。
package MapDemo; import java.util.Map; import java.util.HashMap; public class MapDemo1 { public static void main(String[] args) { Map<String,String> m = new HashMap<>(); m.put("郭靖","黄蓉"); m.put("牢备","孙尚香"); System.out.println(m.put("东方曜","西施"));//返回null m.put("周瑜","小乔"); System.out.println(m.put("东方曜","牢大"));//返回西施 System.out.println(m); System.out.println(m.remove("孙策"));//返回null System.out.println(m.remove("郭靖"));//返回黄蓉 System.out.println(m); // m.clear(); // System.out.println(m); boolean keyResult1 = m.containsKey("牢备"); boolean keyResult2 = m.containsKey("孙尚香"); System.out.println(keyResult1 + "---" + keyResult2);//返回true---false boolean valueResult1 = m.containsValue("周瑜"); boolean valueResult2 = m.containsValue("小乔"); System.out.println(valueResult1 + "---" + valueResult2);//返回的是false---true System.out.println(m.size()); } }(2)Map的遍历方式
键找值
键值对
Lambda表达式
——1、键找值
keySet() 返回该集合中所有key对象形成的Set集合
//获取map集合中所有的键,把这些键放到一个单列集合当中。
例:Set<String> keys = map.keySet();
V get(Object key) 如果存在指定的key对象,则返回该对象对应的值,否则返回null
//通过get方法获取集合中每个键对应的值。
package MapDemo; import java.util.Iterator; import java.util.Map; import java.util.HashMap; import java.util.Set; import java.util.function.Consumer; public class MapDemo2 { public static void main(String[] args) { Map<String,String> map = new HashMap<>(); map.put("周瑜","小乔"); map.put("孙策","大乔"); map.put("云樱","赵怀真"); Set<String> keys = map.keySet(); for (String key : keys) { String value = map.get(key); System.out.println(key+":"+value); } Iterator<String> it = keys.iterator(); while (it.hasNext()){ String key = it.next(); String value = map.get(key); System.out.println(key+":"+value); } keys.forEach(new Consumer<String>() { @Override public void accept(String key) { String value = map.get(key); System.out.println(key+":"+value); } }); keys.forEach(key-> { String value = map.get(key); System.out.println(key+":"+value); }); } }——2、键值对
通过键值对对象进行遍历
Entry内部类
Map中包括一个内部类接口Entry,该类封装了一个key-value对。Entry包含如下三个方法。
Object getKey() 返回该Entry里面包含的key
Object getValue() 返回该Entry里面包含的value值
Object setValue(V value) 返回该Entry里面包含的value值,并返回新设置的value值
语法:通过map里的的entrySet()方法获取所有的键值对对象,返回一个Set集合。
如果没有导包:
Set<Map.Entry<String,String>> entries = map.entrySet();
如果导包:
import java.util.Map.Entry;
Set<Entry<String,String>> entries = map.entrySet();
package MapDemo; import java.util.Iterator; import java.util.Map; import java.util.HashMap; import java.util.Map.Entry; import java.util.Set; import java.util.function.Consumer; public class MapDemo3 { public static void main(String[] args){ Map<String,String> map = new HashMap<>(); map.put("混子","瑶"); map.put("超标","敖隐"); map.put("大手","马超"); Set<Entry<String,String>> entries = map.entrySet(); for(Map.Entry<String,String> entry : entries){ String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + ":" + value); } Iterator<Map.Entry<String, String>> it = entries.iterator(); while(it.hasNext()) { String key = it.next().getKey(); String value = map.get(key); System.out.println(key + ":" + value); } entries.forEach(new Consumer<Entry<String, String>>() { @Override public void accept(Map.Entry<String ,String> entry) { String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + ":" + value); } }); entries.forEach(entry-> { String key = entry.getKey(); String value = entry.getValue(); System.out.println(key + ":" + value); }); } }——3、Lambda表达式
Map里面的方法:
Default void forEach(BiConsumer<? super K, ? super V> action) 结合Lambda遍历Map集合
其中BiConsumer<T,U>是一个函数式接口
package MapDemo; import java.util.Map; import java.util.HashMap; import java.util.Set; import java.util.function.BiConsumer; public class MapDemo4 { public static void main(String[] args){ Map<String,String> map = new HashMap<>(); map.put("杀哥","我最讨厌的就是事后道歉~杀~杀~杀~"); map.put("坤哥","鸡你太美~"); map.put("唐三","复活吧!俺的爱人~"); map.put("萧炎","侥幸而已~"); map.forEach(new BiConsumer<String, String>() { @Override public void accept(String key,String value) { System.out.println(key + ":" + value); } }); map.forEach((key, value) -> System.out.println(key + ":" + value)); } }9、Map的实现类
(1)HashMap
———1、HashMap特点和原理
HashMap是Map的实现类,直接用Map里面的方法就可以了
HashMap的特点
HashMap是Map里面的一个实现类。
没有额外需要学习的特有方法,直接使用Map里面的方法就可以了。
特点都是由键决定的:无序、不重复、无索引
HashMap跟HashSet底层原理是一模一样的,都是哈希表结构
注:哈希表JDK8之前由数组和链表组成,JDK8及以后是由数组+链表+红黑树。
HashMap的底层原理
利用键计算哈希值,跟值无关。再计算出在数组中应存入的索引,如果索引处为null,则存入,如果该索引处已经有元素,则只比较键的属性值,一样则覆盖原有的键值对对象(put 方法的覆盖),不一样则添加新的键值对对象,JDK8以后新元素直接挂在老元素下面。
创建一个默认长度16,默认加载因子为0.75的数组,数组名table
这里的加载因子就是HashMap的扩容时机,
情况一:即当数组里面存了16*0.75=12个元素后,数组扩容为原来的2倍=32。
情况二:JDK8以后,当链表长度大于8而且数组长度大于等于64,当前的链表自动转为红黑树,提高查找效率
根据键的哈希值跟数组的长度计算出应存入的位置(公式:int index=(数组长度-1)&哈希值;
判断当前位置是否为null,如果是null直接存入
如果位置不为null,表示有元素,则调用equals方法只比较键属性值
注意:这里如果集合中存储的是自定义对象,必须重写equals方法。
属性值一样:覆盖原有的键值对对象(put方法的特点)
不一样:存入数组,形成链表(因此HashMap保证了元素的唯一)
JDK8以前:新元素存入数组,老元素挂在新元素下面,形成链表
JDK8及以后:新元素直接挂在老元素下面,形成链表
注意:如果集合中键存储的是自定义对象,必须要重写hashCode和equals方法。
如果集合中值存储的是 自定义对象,则不需要重写HashCode和equals方法。
———2、练习
练习一:存储自定义对象
package MapDemo; import java.util.*; import java.util.Iterator; import java.util.function.BiConsumer; public class MapDemo5 { public static void main(String[] args) { HashMap<Student, String> hm = new HashMap<>(); Student s1 = new Student("瑶",19); Student s2 = new Student("马超",23); Student s3 = new Student("孙膑",19); Student s4 = new Student("瑶",19); hm.put(s1,"混子"); hm.put(s2,"大手"); hm.put(s3,"混子"); hm.put(s4,"老板"); Set<Student> keys = hm.keySet(); for (Student key : keys) { String value = hm.get(key); System.out.println(key + ":" + value); } Set<Map.Entry<Student,String>> entries = hm.entrySet(); Iterator<Map.Entry<Student,String>> it = entries.iterator(); while (it.hasNext()) { Map.Entry<Student, String> entry = it.next(); Student key = entry.getKey(); String value = entry.getValue(); System.out.println(key + ":" + value); } hm.forEach(new BiConsumer<Student, String>() { @Override public void accept(Student key, String value) { System.out.println(key + ":" + value); } }); hm.forEach((key, value) -> System.out.println(key + ":" + value)); } } class Student { private String name; private int age; public Student() {} public Student(String name, int age) { this.name = name; this.age = age; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int hashCode() { return Objects.hash(name,age); } @Override public boolean equals(Object obj) { if(this == obj)return true; if(obj == null || getClass() !=obj.getClass()) return false; Student student = (Student) obj; return age == student.age && Objects.equals(name, student.name); } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }练习二:利用Map集合进行统计
package MapDemo; import java.util.*; public class MapDemo6 { public static void main(String[] args) { String[] arr = {"A","B","C","D"}; ArrayList<String> list = new ArrayList<String>(); Random r = new Random(); for (int i = 0; i < 80; i++) { int index = r.nextInt(arr.length); list.add(arr[index]); } HashMap<String,Integer> hm = new HashMap<>(); for(String name : list) { if(hm.containsKey(name)) { int count = hm.get(name); count++; hm.put(name, count); }else { hm.put(name,1); } } System.out.println(hm); int max = 0; Set<Map.Entry<String, Integer>> entries = hm.entrySet(); for (Map.Entry<String, Integer> entry : entries) { int count = entry.getValue(); if(count > max) { max = count; } } System.out.println(max); for(Map.Entry<String, Integer> entry : entries) { int count = entry.getValue(); if(count == max) { System.out.println(entry.getKey()); } } } }———3、HashMap的子类LinkedHashMap
LinkedHashMap
由键决定:有序、不重复、无索引。(和值无关)
这里的有序指的是保证存储和取出的元素一致
原理:底层数据结构依然是哈希表,只是每个键值对元素有额外的多了一个双链表的机制记录存储的顺序
注:底层还是利用键计算的哈希值,然后equals比较键的属性值。(跟值无关)
package MapAndHashMap; import java.util.LinkedHashMap; public class LinkedHashMap1 { public static void main(String[] args) { LinkedHashMap<String, Integer> lhm = new LinkedHashMap<>(); lhm.put("c",1); lhm.put("a",3); lhm.put("a",4); lhm.put("b",3); System.out.println(lhm); } } 输出:[c=1, a=4, b=3](2)TreeMap
———1、TreeMap特点和底层原理
TreeMap
TreeMap跟TreeSet底层原理一样,都是红黑树结构的
由键决定特性:可排序、不重复、无索引
可排序:对键进行排序
注意:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
代码书写两种排序规则:
实现Comparable接口,重写里面的compareTo方法,指定比较规则。
创建集合时传递Comparator比较器对象,重写里面的compare方法指定比较规则。
如何选择两种排序规则:
默认使用第一种,但是如果第一种不能满足需求,则需要采用第二种排序方式。这里的满足需求是指比如我要比较Integer,默认源码是升序,我现在要降序,第一种也能用,但是不推荐修改源码,所以可以使用第二种。还有比如说比较字符串,源码默认使用ASCII字典序升序排序,我现在要降序,不可能去修改源码,所以用第二种。总结:非自定义类型,源码中默认比较规则,如果要修改该比较规则就用第二种,否则用第一种默认即可。
———2、TreeMap第一种比较方式
实现Comparable接口,重写里面的compareTo方法指定比较规则。
package TreeMap; import java.util.Map; import java.util.TreeMap; import java.util.Set; import java.util.function.BiConsumer; public class TreeMapDemo2 { public static void main(String[] args) { TreeMap<Student,String> tm = new TreeMap<>(); Student s1 = new Student("杀哥",19); Student s2 = new Student("萧炎",18); Student s3 = new Student("坤哥",27); Student s4 = new Student("哈哈姐",19); tm.put(s1,"我最讨厌的就是事后道歉,砰~,杀~杀~杀~"); tm.put(s2,"侥幸而已~"); tm.put(s3,"大家好,我是个人练习生蔡徐坤,鸡你太美~"); tm.put(s4,"仰天大笑出门去,我辈岂是读书人~哈哈哈~哈哈哈~"); Set<Student> keys = tm.keySet(); for(Student key : keys) { String value = tm.get(key); System.out.println(key + ":" + value); } Set<Map.Entry<Student,String>> entries = tm.entrySet(); for(Map.Entry<Student,String> entry : entries) { Student s = entry.getKey(); String value = entry.getValue(); System.out.println(s + ":" + value); } tm.forEach(new BiConsumer<Student,String>() { @Override public void accept(Student s,String value) { System.out.println(s + ":" + value); } }); tm.forEach((s,value)->System.out.println(s + ":" + value)); } } class Student implements Comparable<Student> { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public int compareTo(Student o) { int num=this.getAge() - o.getAge(); if(num==0) { return this.getName().compareTo(o.getName()); } return num; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } }———3、TreeMap第二种比较方式
创建集合时传递Comparator比较器对象,重写里面的compare方法指定比较规则。Comparator是函数式接口
package TreeMap; import java.util.Comparator; import java.util.TreeMap; public class TreeMapDemo1 { public static void main(String[] args) { TreeMap<Integer, String> tm = new TreeMap<>(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1-o2; } }); TreeMap<Integer, String> tmm = new TreeMap<>((o1, o2)-> o1-o2); tm.put(5,"粤利粤"); tm.put(2,"康帅傅"); tm.put(4,"九个核桃"); tm.put(3,"雷碧"); tm.put(1,"可恰可乐"); System.out.println(tm); } } 输出:{1=可恰可乐, 2=康帅傅, 3=雷碧, 4=九个核桃, 5=粤利粤}———4、TreeMap练习
统计思路:键存字符,值存出现次数。
统计题可以选择HashMap或者TreeMap.
如果题目中要求对结果进行排序,默认使用HashMap,因为HashMap速度快
如果题目中要求对结果进行排序,则使用TreeMap.
package TreeMap; import java.util.Map; import java.util.TreeMap; import java.util.Random; import java.util.function.BiConsumer; public class TreeDemo3 { public static void main(String[] args) { String s = "aababcabcdabcde"; TreeMap<Character, Integer> tm = new TreeMap<>(); for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if(tm.containsKey(c)){ int count = tm.get(c); count++; tm.put(c,count); }else { tm.put(c,1); } } System.out.println(tm); StringBuilder sb = new StringBuilder(); tm.forEach(new BiConsumer<Character, Integer>() { @Override public void accept(Character key, Integer value) { sb.append(key).append("(").append(value).append(")"); } }); tm.forEach((key, value)-> sb.append(key).append("(").append(value).append(")")); System.out.println(sb.toString()); //输出:a(5)b(4)c(3)d(2)e(1)a(5)b(4)c(3)d(2)e(1) } }