【JAVA入门】Day31 - 双列集合 —— Map 系列
文章目录
- 【JAVA入门】Day31 - 双列集合 —— Map 系列
- 一、双列集合体系结构
- 二、Map 的遍历方式
- 2.1 键找值
- 2.2 键值对遍历
- 2.3 Lambda 表达式遍历键值对
- 三、HashMap
- 3.1 HashMap的创建
- 四、LinkedHashMap
- 五、TreeMap
单列集合每次添加元素时,只能添加一个元素;但双列集合每次添加元素时,是添加一对元素,双列集合每次添加的一对元素,我们称为键值对(Entry)。一个键和一个值是对应的。
要注意:键和值是一一对应的,每一个键只能找到自己对应的值。键不能重复,但是值是可以重复的。
键 + 值这个整体我们称之为“键值对对象”,在 Java 中叫做“ Entry 对象”。
一、双列集合体系结构
在双列集合中,Map 是顶级接口,它的所有功能可以被全部双列集合继承使用。
Map 集合定义了很多方法,如下表。
方法使用细节如下代码。
package Maps;
import java.util.HashMap;
import java.util.Map;
public class MapDemo1 {
public static void main(String[] args) {
//1.创建Map对象
//Map是接口,要创建实现类对象进行多态
Map<String, String> m = new HashMap<>();
//2.添加键值对
m.put("郭靖","黄蓉");
m.put("喜羊羊","美羊羊");
String value1 = m.put("开心超人","宅博士");
//会覆盖上一行数据,添加时如果键不存在,直接添加该键值对,然后返回none
// 如果键已存在,会覆盖原有键值对,然后将被覆盖的值返回
String value2 = m.put("开心超人","甜心超人");
//返回值测试
System.out.println(value1);
System.out.println(value2);
//打印集合
System.out.println(m); //{开心超人=宅博士, 郭靖=黄蓉, 喜羊羊=美羊羊}
//3.根据键删除键值对,返回被删除的值
/*String result = m.remove("郭靖");
System.out.println(m);
System.out.println(result);*/
//4.清空集合
//m.clear();
//System.out.println(m);
//5.判断是否包含
boolean keyResult = m.containsKey("郭靖");
boolean valueResult = m.containsValue("美羊羊");
System.out.println(keyResult);
System.out.println(valueResult);
//6.判断集合是否为空
boolean result = m.isEmpty();
System.out.println(result);
//7.size集合长度
int size = m.size();
System.out.println(size);
}
}
二、Map 的遍历方式
Map 集合有三种遍历方式,键找值、依次获取键值对、Lambda 表达式遍历。
2.1 键找值
可以创建一个键的单列集合,通过每一个元素调用 get 方法来找对应的值。
package Maps;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo2 {
public static void main(String[] args) {
//遍历方式1
//1.创建Map集合对象
Map<String, String> map = new HashMap<>();
//2.添加元素
map.put("喜羊羊","美羊羊");
map.put("大大怪","小小怪");
map.put("虹猫","蓝兔");
//3.通过键找值
//3.1 把键获取放入单列集合
Set<String> keys = map.keySet();
//3.2 遍历单列集合得到每一个键
for (String key : keys) {
//System.out.println(key);
//3.3 利用键获取每一个值
String value = map.get(key);
System.out.println(key + " = " + value);
}
}
}
2.2 键值对遍历
获取每一对键值对,然后调用 get 方法获取键和值。
package Maps;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class MapDemo3 {
public static void main(String[] args) {
//遍历方式2
//1.创建Map集合对象
Map<String, String> map = new HashMap<>();
//2.添加元素
map.put("喜羊羊","美羊羊");
map.put("大大怪","小小怪");
map.put("虹猫","蓝兔");
//3.通过键值对对象遍历
//3.1 通过一个方法获取所有键值对对象,返回一个set集合
Set<Map.Entry<String, String>> entries = map.entrySet();
//3.2 遍历entries集合找到所有键值对对象
for (Map.Entry<String, String> entry : entries) {
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + " = " + value);
}
}
}
2.3 Lambda 表达式遍历键值对
利用Lambda表达式遍历集合。
package Maps;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
public class MapDemo4 {
public static void main(String[] args) {
//遍历方式3
//1.创建Map集合对象
Map<String, String> map = new HashMap<>();
//2.添加元素
map.put("鲁迅","这句话是我说的");
map.put("曹操","不可能,绝对不可能");
map.put("刘备","接着奏乐,接着舞");
map.put("张飞","我一枪挑了大哥!");
//3.利用lambda表达式进行遍历
/* 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));
}
}
三、HashMap
HashMap 是 Map 的一个实现类。
HashMap 中无额外方法,直接使用 Map 中的方法即可。
HashMap 的特点由键决定:无序、不重复、无索引。
HashMap 和 HashSet 在底层都是哈希表实现的:数组 + 链表 + 红黑树。
3.1 HashMap的创建
在存入数据时,HashMap 是以键为准的,和值无关。它会利用键计算出哈希值,存入哈希表中。
如果碰到已经存放元素的位置,还是根据键的属性值来判断。
如果新元素的键和旧元素一模一样,直接覆盖之。
如果新元素的键和旧的不一样,直接挂在旧元素下面,形成链表。
当链表的长度超过8 & 数组长度>=64时,链表自动转成红黑树。
根据底层结构可知,HashMap 底层存储和值没有任何关系,其依赖 hashCode() 方法和 equals() 方法保证键的唯一,如果键存储的是自定义对象,一定要重写这两个方法;但如果值存储的是自定义对象,那就不需要重写,存储和值无关。
【练习1】存储自定义对象。
需求:键为学生,值为籍贯。同年龄,同姓名认为是同一个学生。
package Maps;
import java.util.HashMap;
import java.util.Set;
public class HashMapDemo1 {
public static void main(String[] args) {
//1.创建HashMap对象
HashMap<Student, String> hm = new HashMap<>();
//2.三个学生对象
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 24);
Student s3 = new Student("wangwu", 25);
Student s4 = new Student("wangwu", 25);
//3.添加元素,Student是键,值是籍贯
hm.put(s1, "江苏");
hm.put(s2, "浙江");
hm.put(s3, "胡建");
hm.put(s4, "山东"); //覆盖了wangwu
//4.遍历集合
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();
for (Map.Entry<Student, String> entry : entries) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
hm.forEach((key, value) -> System.out.println(key + " = " + value));
}
}
【练习2】利用 Map 集合统计投票人数。
需求:某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是((A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
package Maps;
import java.util.*;
public class HashMapDemo2 {
public static void main(String[] args) {
/*
景点 投票次数
*/
//存四个景点
String[] arr = {"A", "B", "C", "D"};
//利用随机数模拟80个同学投票,并存储投票结果
ArrayList<String> list = new ArrayList<>();
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中,覆盖原数据
hm.put(name, count);
}else{
//不存在
//表示当前景点是第一次出现
hm.put(name, 1);
}
}
System.out.println(hm);
//3.遍历求该集合中的最大值
int max = 0;
Set<Map.Entry<String, Integer>> entries = hm.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
if(entry.getValue() > max) {
max = entry.getValue();
}
}
//4.再次遍历,打印景点投票次数和最大值一样的景点
for (Map.Entry<String, Integer> entry : entries) {
if(entry.getValue() == max) {
System.out.println(entry.getKey());
}
}
}
}
四、LinkedHashMap
LinkedHashMap 是 HashMap 的子类,它的特性由键决定:有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致。它的底层和 LinkedHashSet 类似,每个键值对存入哈希表中,又额外添加了一条双链表来记录存储的顺序。
在遍历时,会根据这个双链表的头结点依次遍历,和元素的存入顺序达成了一致。
package Maps;
import java.util.LinkedHashMap;
public class LinkedHashMapDemo {
public static void main(String[] args) {
//1.创建集合
LinkedHashMap<String, Integer> lhm = new LinkedHashMap<>();
//2.添加元素
lhm.put("123", 1);
lhm.put("123", 2);
lhm.put("abc", 3);
lhm.put("cad", 4);
//3.打印看看是不是按存储顺序
System.out.println(lhm);
}
}
五、TreeMap
TreeMap 和 TreeSet 底层原理一样,都是红黑树结构的。
TreeMap 的特性由键决定:不重复、无索引、可排序。
TreeMap 的排序是对键的排序,默认按照键的从小到大排序(字符串就是字典顺序),也可以按照自己规定的排序规则排序,规定方式和 TreeSet 别无二致。
- 实现 Comparable 接口,指定比较规则。
- 创建集合时传入 Comparator 比较器对象,指定比较规则。
【练习】 需求1:键:整数表示id,值:字符串表示商品名称,要求:按照id的升序排列、按照id的降序排列。
package Maps;
import java.util.Comparator;
import java.util.TreeMap;
public class TreeMapDemo {
public static void main(String[] args) {
/*
需求1:键:整数表示id,值:字符串表示商品名称,要求:按照id的升序排列、按照id的降序排列
*/
//1.创建集合对象
TreeMap<Integer,String> tm = new TreeMap<>();
//2.添加元素
tm.put(1,"粤利粤");
tm.put(4,"雷碧");
tm.put(5,"可恰可乐");
tm.put(2,"康帅傅");
tm.put(3,"九个核桃");
//3.打印集合
//默认按照键的升序进行排列
System.out.println(tm);
//按照id降序排列
TreeMap<Integer,String> tm1 = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
tm1.put(1,"粤利粤");
tm1.put(4,"雷碧");
tm1.put(5,"可恰可乐");
tm1.put(2,"康帅傅");
tm1.put(3,"九个核桃");
System.out.println(tm1);
}
}
【练习】 需求2:键:学生对象,值:籍贯,要求:按照学生年龄的升序排列,年龄一样按照姓名的字母排列,同姓名年龄视为同一个人。
package Maps;
import java.util.Objects;
public class Student implements Comparable<Student> {
private String name;
private int age;
public Student() {
}
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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public int compareTo(Student o) {
//按年龄升序
int i = this.getAge() - o.getAge();
//年龄一样按姓名字母顺序
i = i == 0 ? this.getName().compareTo(o.getName()) : i;
//姓名年龄都一样视为同一个人,TreeSet要添加的元素已经存在,就不存了
return i;
}
}
Student 类实现 Comparable 接口,重写 compareTo() 方法。
package Maps;
import java.util.TreeMap;
public class TreeSetDemo5 {
public static void main(String[] args) {
//1.创建集合
TreeMap<Student,String> tm = new TreeMap<>();
//2.创建学生对象
Student s1 = new Student("zhangsan", 23);
Student s2 = new Student("lisi", 24);
Student s3 = new Student("wangwu", 25);
//3.添加学生姓名,籍贯键值对
tm.put(s1,"福建");
tm.put(s2,"湖南");
tm.put(s3,"江苏");
//4.重写了学生类的排序,这里直接打印即可
System.out.println(tm); //{Student{name='zhangsan', age=23}=福建, Student{name='lisi', age=24}=湖南, Student{name='wangwu', age=25}=江苏}
}
}
【练习3】统计"aababcabcdabcde"每个字符出现次数,并按照以下格式输出:
a(5)b(4)c(3)d(2)e(1)
package Maps;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;
import java.util.TreeMap;
import java.util.function.BiConsumer;
public class TreeMapDemo6 {
public static void main(String[] args) {
//1.定义字符串
String str = "aababcabcdabcde";
//2.创建集合
TreeMap<Character,Integer> tm = new TreeMap<>();
//3.遍历字符串得到里面的每一个字符
for(int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
//如果字符没在tm中出现过,就说明其是第一次出现,就put c, 1,否则取出其count,然后++,再覆盖
if(tm.containsKey(c)) {
int count = tm.get(c);
count++;
tm.put(c, count);
}else{
tm.put(c, 1);
}
}
//System.out.println(tm);
//4.利用StringBuilder添加字符串按照指定格式拼接
StringBuilder sb = new StringBuilder();
tm.forEach((key,value) -> {
sb.append(key).append("(").append(value).append(")");
});
System.out.println(sb);
5.利用StringJoiner添加字符串按照指定格式拼接
StringJoiner sj = new StringJoiner("");
//sj只能add字符串,所以要把key和value后面都加一个"",把它变成字符串形式
Set<Map.Entry<Character, Integer>> entries = tm.entrySet();
for (Map.Entry<Character, Integer> entry : entries) {
sj.add(entry.getKey() + "").add("(").add(entry.getValue() + "").add(")");
}
System.out.println(sj);
}
}