双列集合:Map集合及其实现类HashMap、TreeMap详解
概要:Map集合介绍及流程图
Map集合是一个接口,是一种用于存储键值对(key-value)映射关系的数据结构。键(key)在Map里面是唯一的,但是值(value)可以重复,一个key对应一个value。
以下是Map集合和其常见的实现类关系图,也是本篇博客的主要内容。Map集合中包含了其所有实现类通用的方法和三种遍历方式。
一、Map集合的主要方法和三种遍历方式
1、如何创建一个Map集合
由于Map集合是一个接口,我们无法直接创建接口的对象(实例化对象),所以Map集合的创建是用多态的方式创建其实现类的对象。而Map集合存储的是键值对对象,所以创建对象时有两个泛型(k,v),这里的创建实现类对象使用HashMap。
//Map(泛型,泛型) 对象名 = new 实现类<>();
Map<String, String> map = new HashMap<>();
2、Map集合的主要方法
(1)添加和修改元素:
V put(K key,V value)
添加、覆盖键值
细节:
如果Map集合里面没有这个键,直接添加。
如果Map集合已有这个键,覆盖原来的值(value),并返回被覆盖的值。
map.put("孙策","大乔");
map.put("周瑜","小乔");
map.put("吕布","貂蝉");
(2)获取元素:
boolean containsKey(Object key): //判断是否包含指定的键
boolean containsValue(Object value): //判断是否包含指定的值。
Set<K> keySet(): //返回包含所有键的Set集合。
Set<Map.Entry<K, V>> entrySet(): //返回包含所有键值对的Set集合,每个键值对表示为一个Map.Entry对象。
//实际场景
//containsKey
boolean keyResult = map.containsKey("周瑜");
System.out.println(keyResult);
//containsValue
boolean valueResult = map.containsValue("貂蝉");
System.out.println(valueResult);
(3)删除元素:
V remove(Object key): 根据键删除对应的键值对,并返回被删除的值。
void clear(): 清空Map中的所有键值对。
(4)判断和获取大小
int size(): 返回Map中键值对的数量。
boolean isEmpty(): 判断Map是否为空。
3、Map集合的三种遍历方式
注意:这里以代码形式呈现,每段代码针对每一步,基本上都有非常详细的注释。
3.1、键找值
代码如下:每种遍历方式中还有三种遍历集合的方法
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapErgodic1 {
public static void main(String[] args) {
//Map集合的第一种遍历方式
//键找值
Map<String, String> map = new HashMap<>();
map.put("孙策", "大乔");
map.put("周瑜", "小乔");
map.put("吕布", "貂蝉");
//通过键找值
//1.获取所有键,把这些键放到一个单列集合中(keySet方法)
Set<String> keys = map.keySet();
//2.遍历集合,通过键找到对应的值
//遍历方式1 -- 增强for
for (String key : keys) {
//通过键获取值(get方法)
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("----------------------");
//遍历方式2 -- 迭代器
Iterator<String> iterator = keys.iterator();
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println(key + "=" + map.get(key));
}
System.out.println("----------------------");
//遍历方式3 -- lambda表达式
//匿名内部类 -- lambda表达式
keys.forEach(key -> System.out.println(key + "=" + map.get(key)));
}
}
运行结果:
3.2、键值对
代码如下:每种遍历方式中还有三种遍历集合的方法
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class MapErgodic2 {
public static void main(String[] args) {
//Map集合的第二种遍历方式
//键值对
Map<String, String> map = new HashMap<>();
//键:外号
//值:姓名
map.put("标枪选手", "马超");
map.put("人物挂件", "明世隐");
map.put("御龙骑士", "尹志平");
//通过键值对对象进行遍历
//通过entrySet方法获取所有的键值对对象,返回一个Set集合
Set<Map.Entry<String, String>> entrySet = map.entrySet();
//遍历Set集合,集合中的每一个元素都是一个键值对对象
//通过getKey何getValue方法获取每一个键值对中的键和值
for (Map.Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println("----------------------");
//迭代器
Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println("----------------------");
//lambda
entrySet.forEach(entry -> System.out.println(entry.getKey() + "=" + entry.getValue()));
}
}
运行结果:
3.3、lambda表达式
代码如下:
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
public class MapErgodic3 {
public static void main(String[] args) {
//Map集合的第三遍历方式
//lambda表达式
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);
}
});
System.out.println("----------------------");
//利用lambda表达式进行遍历
map.forEach((key, value) -> System.out.println(key + "=" + value));
}
}
运行结果:
二、HashMap集合的应用
注意:同样,这里还是以代码形式呈现,每段代码针对每一步,都有非常详细的注释。
1、HashMap集合的特点
HashMap是Map集合中最常用的集合。HashMap是Map接口的一个实现类,即继承自Map接口。它提供了一种基于哈希表的快速查找、插入和删除元素的方式。
(1)快速访问:HashMap底层采用哈希表来实现,当我们使用get或put方法时,它会根据键的哈希值来快速定位对应的桶(Bucket),然后在桶内查找对应的键值对,因此访问速度非常快。
(2)无序性:HashMap中的元素是无序的,即元素插入的顺序和遍历的顺序不一定相同。
2、存储学生对象并遍历
需求:
创建一个HashMap集合,键是学生对象,值是籍贯
存储三个键值对元素,并遍历
要求:同姓名,同年龄认为是同一个学生
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class HashMap1 {
//核心点:HashMap如果存的的自定义对象,需要重写hashCode和equals方法
public static void main(String[] args) {
HashMap<Student, String> map = new HashMap<>();
Student stu1 = new Student("zhangsan", 23);
Student stu2 = new Student("lisi", 24);
Student stu3 = new Student("wangwu", 25);
//添加三个键值对元素
map.put(stu1, "福建");
map.put(stu2, "北京");
map.put(stu3, "上海");
//遍历集合:
//一、键找值
Set<Student> keys = map.keySet();
for (Student key : keys) {
//遍历得到的每个元素是键,通过map.get()方法,传过去键值,返回value值
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("----------------------");
//二、键值对
Set<Map.Entry<Student, String>> entries = map.entrySet();
for (Map.Entry<Student, String> entry : entries) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println("----------------------");
//三:lambda表达式
map.forEach((student, s) -> System.out.println(student + "=" + s));
}
}
Student类代码:
import java.util.Objects;
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
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 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 String toString() {
return "Student{" +"name='" + name + '\'' +", age=" + age +'}';
}
}
运行结果:
3、统计投票人数
需求:
某个班级80名学生,限制需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生
只能选择一个景点,请统计出最重哪个景点想去的人数最多。
import java.util.*;
public class HashMap2 {
public static void main(String[] args) {
//1、学生投票
//定义一个数组,存储提供的景点
String[] s = {"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(s.length); // [0-4)
list.add(s[index]);
}
//如果要统计的东西比较多,不方便使用计数器思想
//2、跟据list集合存储的投票情况,统计人数
HashMap<String, Integer> hashMap = new HashMap<>();
for (String name : list) {
//如果Map集合里已有这个键,则覆盖此键且值加一
if (hashMap.containsKey(name)) {
Integer count = hashMap.get(name);
hashMap.put(name, count + 1);
} else {
//如果Map集合里没有这个键,则添加这个键
hashMap.put(name, 1);
}
}
System.out.println(hashMap);
//3、遍历hashMap,找到人数最多的景点
int maxCount = 0;
Set<Map.Entry<String, Integer>> entries = hashMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
//用getValue获取值,每次与当前maxCount比较(if的改写)
maxCount = Math.max(maxCount, entry.getValue());
}
System.out.println(maxCount);
//4、判断哪个景点的次数和跟最大值一样,如果一样,打印出来
for (Map.Entry<String, Integer> entry : entries) {
if (maxCount == entry.getValue()) {
System.out.println(entry.getKey());
}
}
}
}
运行结果:
4、(LeetCode)两数之和
两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
import java.util.Arrays;
import java.util.HashMap;
public class LeetCode1 {
public int[] twoSum(int[] nums, int target) {
HashMap<Integer, Integer> map = new HashMap<>();
//查找哈希表内是否有能和当前数和为target的键
//如果没有,以当前元素为键,下标为值存入哈希表
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
} else {
map.put(nums[i], i);
}
}
return new int[0];
}
public static void main(String[] args) {
int[] twoSum = new LeetCode1().twoSum(new int[]{2, 7, 9, 11}, 9);
System.out.println(Arrays.toString(twoSum));
}
}
5、(LeetCode)多数元素
多数元素
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
public class LeetCode2 {
public int majorityElement(int[] nums) {
//创建一个Map集合,键为该元素值,值为出现次数
HashMap<Integer, Integer> hashMap = new HashMap<>();
//遍历数组
for (int num : nums) {
//如果当前元素存在,给键上对应值增一
//如果当前元素不存在,添加一个键
if (hashMap.containsKey(num)) {
int count = hashMap.get(num);
hashMap.put(num, count + 1);
} else {
hashMap.put(num, 1);
}
}
//遍历hashMap集合,找到出现值次数大于n/2的元素,返回他的键
Set<Map.Entry<Integer, Integer>> entries = hashMap.entrySet();
for (Map.Entry<Integer, Integer> entry : entries) {
int value = entry.getValue();
if (value > nums.length / 2) {
return entry.getKey();
}
}
return -1;
}
public static void main(String[] args) {
LeetCode2 lc = new LeetCode2();
int result = lc.majorityElement(new int[]{2, 2, 1, 1, 1, 2, 2});
System.out.println(result);
}
}
6、LinkHashMap集合
6.1、LinkHashMap集合的特点
LinkedHashMap它继承自HashMap,具有HashMap的所有特性,并且额外提供了按插入顺序或访问顺序进行遍历的功能。
(1)有序性:LinkedHashMap会保留元素的插入顺序,即元素插入的顺序和遍历的顺序一致。这是通过在HashMap的基础上维护一个双向链表来实现的。
(2)内部维护了双向链表:LinkedHashMap内部维护了一个双向链表,用于保存元素的插入顺序或访问顺序。这样可以在O(1)的时间复杂度下实现元素的插入和删除操作。
(3)HashMap和LinkHashMap的选择:
LinkedHashMap适用于需要保留插入顺序或按访问顺序遍历的场景。如果不需要保留顺序,直接使用HashMap即可。
6.2、证明其有序、不重复、无索引。
import java.util.LinkedHashMap;
public class LinkHashMap {
/*
LineHashMap:
有序、不重复、无索引。
原理:
底层数据结构依然是哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序
*/
public static void main(String[] args) {
//1、创建集合
LinkedHashMap<String,Integer> lhm = new LinkedHashMap<>();
//2、添加元素
lhm.put("aaa",123);
lhm.put("aaa",123);
lhm.put("bbb",456);
lhm.put("ccc",789);
//3、打印集合
System.out.println(lhm);
}
}
运行结果:
三、TreeMap集合的应用
注意:同样,这里还是以代码形式呈现,每段代码针对每一步,都有非常详细的注释。
1、TreeMap集合的特点
TreeMap同样继承自Map集合,并且基于红黑树(Red-Black Tree)实现。与HashMap不同,TreeMap中的元素是按照键的自然顺序或者自定义比较器进行排序的。
TreeMap:
TreeMap跟TreeSet底层原理一样,都是红黑树结构的。
由键决定特性:不重复、无索引、可排序。
可排序:对键进行排序。
注意:默认按照键的从小到大进行排序,也可以自己规定键的排序规则
代码书写两种排序规则:
1:实现Comparable接口,指定比较规则。
2:创建集合时传递Comparator比较器对象,指定比较规则。
2、TreeMap的基本应用1
需求1:
键:整数表示id
值:字符串表示商品名称
要求:按照id的升序排列、按照id的降序排列
import java.util.TreeMap;
public class TreeMap1 {
public static void main(String[] args) {
//创建集合时传递Comparator比较器对象,指定比较规则。
//Integer Double 默认情况下都是按照升序排列的
//String 按照字母ASCII码表中对应的数字进行升序排列
//即里面实现了Comparable接口
//1、创建集合对象 -- lambda表达式
TreeMap<Integer, String> treeMap = new TreeMap<>((o1, o2) -> o2 - o1);
/* TreeMap<Integer, String> treeMap1 = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});*/
//2、添加元素
treeMap.put(5, "可恰可乐");
treeMap.put(4, "雷碧");
treeMap.put(3, "九个核桃");
treeMap.put(2, "康帅傅");
treeMap.put(1, "粤利粤");
//3、打印集合
System.out.println(treeMap);
}
}
运行结果:
3、TreeMap集合的基本应用2
TreeMap基本应用
需求2:
键:学生对象
值:籍贯
要求:按照学生年龄的升序排序,年龄一样按照姓名的字母排序,同姓名同年龄视为同一个人。
import java.util.TreeMap;
public class TreeMap2 {
public static void main(String[] args) {
//1、创建集合
TreeMap<Student,String> treeMap = new TreeMap<>();
//2、创建三个学生对象
Student s1 = new Student("zhangsan",23);
Student s2 = new Student("lisi",24);
Student s3 = new Student("wangwu",25);
//3、添加集合元素
treeMap.put(s1,"江苏");
treeMap.put(s2,"福建");
treeMap.put(s3,"浙江");
//4、打印集合
System.out.println(treeMap);
}
}
Student类代码:重点!!!重写compareTo方法
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;
}
/**
* 获取
*
* @return name
*/
public String getName() {
return name;
}
/**
* 设置
*
* @param name
*/
public void setName(String name) {
this.name = name;
}
/**
* 获取
*
* @return age
*/
public int getAge() {
return age;
}
/**
* 设置
*
* @param age
*/
public void setAge(int age) {
this.age = age;
}
public String toString() {
return "Student{name = " + name + ", age = " + age + "}";
}
@Override
public int compareTo(Student o) {
//按照学生年龄的升序排序,年龄一样按照姓名的字母排序,同姓名同年龄视为同一个人。
//this:表示当前要添加的元素
//o:表示已经在红黑树中存在的元素
//返回值:
//负数:表示当前要添加的元素是小的,存左边
//整数:表示当前要添加的元素是大的,存右边
//0:表示当前要添加的元素已经存在,舍弃
//1:直接访问属性(this.)
//int i = this.age - o.age;
//i = i == 0 ? this.name.compareTo(o.name) : i;
//2:get方法获取
int i = this.getAge() - o.getAge();
i = i == 0 ? this.getName().compareTo(o.getName()) : i;
return i;
}
}
注意:
这里的键存储的是Student类对象,也就是自定义类,如果不对compareTo方法进行重写,Java就不知道TreeMap集合将以什么值比较来进行排序
运行结果:
4、统计个数
统计个数
需求:字符串 "aababcabcdabcde"
请统计字符串中每一个字符出现的次数,并按照以下格式输出
输出结果:
a(5) b(4) c(3) d(2) e(1)
import java.util.StringJoiner;
import java.util.TreeMap;
public class TreeMap3 {
public static void main(String[] args) {
/*
新的统计思想:利用Map集合进行统计
HashMap:如果题目中没有要求对结果进行排序,默认使用HashMap
TreeMap:如果题目中要求对结果进行排序,使用TreeMap
键:表示要统计的内容
值:表示次数
*/
//1、定义字符串
String str = "aababcabcdabcde";
//2、创建集合
TreeMap<Character, Integer> treeMap = new TreeMap<>();
//3、遍历字符串得到每一个字符
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
//如果集合中出现过这个字符,值(次数)加一
//如果集合中没出现过这个字符,添加字符(键)
if (treeMap.containsKey(c)) {
//值加一,通过treeMap.get()方法得到值,再加一
treeMap.put(c, treeMap.get(c) + 1);
} else {
treeMap.put(c, 1);
}
}
//4、遍历集合,并按照指定方式进行拼接
//a(5) b(4) c(3) d(2) e(1)
//StringBuilder
StringBuilder sb = new StringBuilder();
treeMap.forEach((key, value) -> sb.append(key).append("(").append(value).append(")"));
//StringJoiner
StringJoiner sj = new StringJoiner("", "", "");
treeMap.forEach((key, value) -> sj.add(key + "").add("(").add(value + "").add(")"));
System.out.println(sb);
System.out.println(sj);
}
}
运行结果: