写在前面:
每一个不曾起舞的日子,都是对生命的辜负。
希望看到这里的每一个人都能努力学习,不负韶华,成就更好的自己。
以下仅是个人学习过程中的一些想法与感悟,Java知识博大精深,作为初学者,个人能力有限,哪里写的不够清楚、明白,还请各位不吝指正,欢迎交流与讨论。如果有朋友因此了解了一些知识或对Java有了更深层次的理解,从而进行更进一步的学习,那么这篇文章的意义也就达到了。
目录
6.7Map集合的实现类HashMap、LinkedHashMap、TreeMap
集合(Set、Collections、Map、集合嵌套)
1.Set系列集合
1.1Set系列集系概述
Set系列集合特点
无序:存取顺序不一致
不重复:可以去除重复
无索引:没有带索引的方法,所以不能使用普通for循环遍历,也不能通过索引来获取元素。
Set集合实现类特点
HashSet:无序、不重复、无索引。
LinkedHashSet:有序、不重复、无索引(并不是真的无索引,而是对外没有提供索引相关API)。
TreeSet:排序(默认自动升序排序)、不重复、无索引。
Set集合的功能上基本上与Collection的API一致。
1.2HashSet元素无序的底层原理:哈希表
HashSet底层原理
HashSet集合底层采取哈希表存储的数据。
哈希表是一种对于增删改查数据性能都较好的结构。
哈希表的组成
JDK8之前的,底层使用数组+链表组成
JDK8开始后,底层采用数组+链表+红黑树组成。
在了解哈希表之前需要先理解哈希值的概念。
哈希值
是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
Object类的API
publicinthashCode():返回对象的哈希值
对象的哈希值特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的
默认情况下,不同对象的哈希值是不同的。
哈希表的详细流程
①创建一个默认长度16,默认加载因为0.75的数组,数组名table。
②根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)。
③判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素,则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
JDK7新元素占老元素位置,指向老元素,JDK8中新元素挂在老元素下面。
④当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍。
JDK1.8版本开始HashSet原理解析
底层结构:哈希表(数组、链表、红黑树的结合体)
当挂在元素下面的数据过多时,查询性能降低,从JDK8开始后,当链表长度超过8的时候,自动转换为红黑树,进一步提高了操作数据的性能。
案例:Set集合去重复
需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合,要求:学生对象的成员变量值相同,我们就认为是同一个对象
分析:
①定义学生类,创建HashSet集合对象,创建学生对象。
②把学生添加到集合。
③在学生类中重写两个方法,hashCode()和equals(),自动生成即可。
④遍历集合(增强for)。
示例代码如下:
Student类
public class Student {
private String name;
private int age;
private char sex;
public Student() {
}
public Student(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
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;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
/**
* 两个对象中每个字段内容相同即返回true
*
* @param o
* @return
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == nul|| getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
sex == student.sex &&
Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, sex);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
}
测试类
public class SetDemo2 {
public static void main(String[] args) {
/*
需求:创建一个存储学生对象的集合,存储多个学生对象,使用程序实现在控制台遍历该集合
要求:学生对象的成员变量值相同,我们就认为是同一个对象
*/
Set<Student> sets = new HashSet<>();
sets.add(new Student("张三", 18, '男'));
sets.add(new Student("李四", 20, '男'));
sets.add(new Student("李四", 20, '男'));
System.out.println(sets); // [Student{name='李四', age=20, sex=男}, Student{name='张三', age=18, sex=男}]
// Set集合去重复原理:先判断哈希值,再判断equals
// 若没有重写hasCode()与equals()方法,打印结果为[Student{name='李四', age=20, sex=男}, Student{name='张三', age=18, sex=男}, Student{name='李四', age=20, sex=男}]
// 没有去重复,两个对象地址不同,哈希值就不同,存储位置也就不同
}
}
自动生成重写hasCode()与equals()方法步骤如下。
①如下图所示,在Student类中空白处点击【鼠标右键】,选择【生成】。
②如下图所示,在弹出的页面中选择【equals()和hasCode()】。
③在弹出的页面中依次选择【下一个】,最后选择【完成】,如下图所示,即可自动生成重写的equals()与hasCode()方法。
1.3实现类:LinkedHashSet
LinkedHashSet集合概述和特点
有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致。
原理:底层数据结构依然是哈希表,只是每个元素又额外的多了一个双链表记录存储顺序。
1.4实现类:TreeSet
TreeSet集合概述和特点
不重复、无索引、可排序。
可排序:按照元素的大小默认升序(从小到大)排序。
TreeSet集合底层是基于红黑树的数据结构实现排序的,增删改查性能都较好。
注意:TreeSet集合是一定要排序的,可以将元素按照指定的规则进行排序。
TreeSet集合默认的规则
对于数值类型:Integer , Double,官方默认按照大小进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet无法直接排序。
注:想要使用TreeSet存储自定义类型,需要制定排序规则。
自定义排序规则
TreeSet集合存储对象的的时候有2种方式可以设计自定义比较规则。
方式一:让自定义类(如学生类)实现Comparable接口重写里面的compareTo方法来制定比较规则。
方式二:TreeSet集合有参数构造器,可以设置Comparator接口对应的比较器对象,来制定比较规则。
关于返回值的规则:
如果认为第一个元素大于第二个元素返回正整数即可。
如果认为第一个元素小于第二个元素返回负整数即可。
如果认为第一个元素等于第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
示例代码如下:
Apple类
public class Apple implements Comparable<Apple> {
private String name;
private String color;
private double price;
private int weight;
public Apple() {
}
public Apple(String name, String color, double price, int weight) {
this.name = name;
this.color = color;
this.price = price;
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
/**
* 方式一:类自定义比较规则
*
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
// 按照重量从小到大排序
// return this.weight - o.weight; // "金元帅"与"金冠"的"weight"属性都为500,TreeSet集合在排序时认为二者重复,会删去一个
return this.weight - o.weight >= 0 ? 1 : -1; // 去掉二者相等返回0的情况,保证"weight"相等的数据都会得到保留
}
}
测试类
public class SetDemo3 {
public static void main(String[] args) {
// 存储数值类型:Integer为例
Set<Integer> sets = new TreeSet<>(); // 不重复 无索引 可排序
sets.add(10);
sets.add(23);
sets.add(7);
System.out.println(sets); // [7, 10, 23]
// 存储字符串类型:String
Set<String> sets2 = new TreeSet<>();
sets2.add("Java");
sets2.add("java");
sets2.add("李四");
sets2.add("HTML");
sets2.add("Zed");
System.out.println(sets2); // [HTML, Java, Zed, java, 李四]
// 存储自定义类型:Student为例
Set<Apple> sets3 = new TreeSet<>();
sets3.add(new Apple("红富士", "红色", 9.9, 500));
sets3.add(new Apple("青苹果", "青色", 12.5, 300));
sets3.add(new Apple("金元帅", "金色", 20, 600));
sets3.add(new Apple("金冠", "金色", 25.5, 500));
// 如果没有制定排序规则直接打印集合,则无法排序,会报错
System.out.println(sets3);
// [Apple{name='青苹果', color='青色', price=12.5, weight=300}, Apple{name='红富士', color='红色', price=9.9, weight=500},
// Apple{name='金冠', color='金色', price=25.5, weight=500}, Apple{name='金元帅', color='金色', price=20.0, weight=600}]
System.out.println("-----------");
/* Set<Apple> apples = new TreeSet<>(new Comparator<Apple>() {
// 方式二:集合自带比较器对象进行规则制定
// 注:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
@Override
public int compare(Apple o1, Apple o2) {
// return o1.getWeight() - o2.getWeight(); // "金元帅"与"金冠"的"weight"属性都为500,TreeSet集合在排序时认为二者重复,会删去一个
return o1.getWeight() - o2.getWeight() >= 0 ? 1 : -1; // 去掉二者相等返回0的情况,保证"weight"相等的数据都会得到保留
}
});*/
// lambda表达式简化
Set<Apple> apples = new TreeSet<>((o1, o2) -> o1.getWeight() - o2.getWeight() >= 0 ? 1 : -1);
apples.add(new Apple("红富士", "红色", 9.9, 500));
apples.add(new Apple("青苹果", "青色", 12.5, 300));
apples.add(new Apple("金元帅", "金色", 20, 600));
apples.add(new Apple("金冠", "金色", 25.5, 500));
System.out.println(apples);
// [Apple{name='青苹果', color='青色', price=12.5, weight=300}, Apple{name='红富士', color='红色', price=9.9, weight=500},
// Apple{name='金冠', color='金色', price=25.5, weight=500}, Apple{name='金元帅', color='金色', price=20.0, weight=600}]
}
}
注:如果TreeSet集合存储的对象有实现比较规则,集合也自带比较器,默认使用集合自带的比较器排序。
2.Collection体系的特点、使用场景总结
1.如果希望元素可以重复,又有索引,索引查询要快?
用ArrayList集合,基于数组的。(用的最多)
2.如果希望元素可以重复,又有索引,增删首尾操作快?
用LinkedList集合,基于链表的。
3.如果希望增删改查都快,但是元素不重复、无序、无索引。
用HashSet集合,基于哈希表的。
4.如果希望增删改查都快,但是元素不重复、有序、无索引。
用LinkedHashSet集合,基于哈希表和双链表。
5.如果要对对象进行排序。
用TreeSet集合,基于红黑树。后续也可以用List集合实现排序。
3.补充知识:可变参数
可变参数概述
可变参数用在形参中可以接收多个数据。
可变参数的格式:数据类型...参数名称
可变参数的作用
传输参数非常灵活,方便。可以不传输参数,可以传输1个或者多个,也可以传输一个数组
可变参数在方法内部本质上就是一个数组。
可变参数的注意事项:
1.一个形参列表中可变参数只能有一个。
2.可变参数必须放在形参列表的最后面。
示例代码如下:
public class MethodDemo {
public static void main(String[] args) {
// 1.不传参数
sum();
// 2.传输1个参数
sum(10);
// 3.传输多个参数
sum(10, 20, 50);
// 4.传输数组
sum(new int[]{10, 20, 30});
}
public static void sum(int... nums) {
// 可变参数在方法内部实际上就是一个数组
System.out.println("元素个数:" + nums.length);
System.out.println("元素内容:" + Arrays.toString(nums));
System.out.println("-----------");
}
}
程序运行结果如下:
元素个数:0
元素内容:[]
-----------
元素个数:1
元素内容:[10]
-----------
元素个数:3
元素内容:[10, 20, 50]
-----------
元素个数:3
元素内容:[10, 20, 30]
-----------
4.补充知识:集合工具类Collections
Collections集合工具类概述
java.utils.Collections:是集合工具类
作用:Collections并不属于集合,是用来操作集合的工具类。
Collections常用的API
方法名 | 说明 |
public static <T> boolean addAll(Collection<? super T> c, T... elements) | 给集合对象批量添加元素 |
public static void shuffle(List<?> list) | 打乱List集合元素的顺序 |
Collections排序相关API
使用范围:只能对于List集合的排序。
注:Shift + F6 一键修改所有出现的该参数名称
具有值特性的类型对象排序
方法名 | 说明 |
public static <T> void sort(List<T> list) | 将集合中(具有值特性的)元素按照默认规则(从小到大)排序 |
注意:本方式不可以直接对自定义类型的List集合排序,除非自定义类型实现了比较规则Comparable接口。
自定义类型对象排序
方法名 | 说明 |
public static <T> void sort(List<T> list,Comparator<? super T> c) | 将集合中元素按照指定规则排序 |
在自定义类型中实现比较规则Comparable接口 |
示例代码如下:
Apple类
public class Apple implements Comparable<Apple> {
private String name;
private String color;
private double price;
private int weight;
public Apple() {
}
public Apple(String name, String color, double price, int weight) {
this.name = name;
this.color = color;
this.price = price;
this.weight = weight;
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", price=" + price +
", weight=" + weight +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
/**
* 方式一:类自定义比较规则
*
* @param o
* @return
*/
@Override
public int compareTo(Apple o) {
// 按照重量从小到大排序
return this.weight - o.weight; // List集合允许重复,不会删去重复元素,即相等也无妨
}
}
测试类
public class CollectionsDemo1 {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
// 1. public static <T> boolean addAll(Collection<? super T> c, T... elements) 给集合对象批量添加元素
// names.add("张三");
// names.add("李四");
// names.add("王五");
// 添加数据比较麻烦,使用addAll方法批量添加数据
Collections.addAll(names, "张三", "李四", "王五");
System.out.println(names); // [张三, 李四, 王五]
// 2. public static void shuffle(List<?> list) 打乱List集合元素的顺序
Collections.shuffle(names);
System.out.println(names); // [李四, 王五, 张三]
// 3. public static <T> void sort(List<T> list) 将集合中(具有值特性的)元素按照默认规则(从小到大)排序
List<Integer> list = new ArrayList<>();
Collections.addAll(list, 2, 5, 19, 10, 3);
Collections.sort(list);
System.out.println(list); // [2, 3, 5, 10, 19]
// 自定义类型对象排序
// ①在自定义类型中实现比较规则Comparable接口
List<Apple> apples = new ArrayList<>();
apples.add(new Apple("红富士", "红色", 9.9, 500));
apples.add(new Apple("青苹果", "青色", 12.5, 300));
apples.add(new Apple("金元帅", "金色", 20, 600));
apples.add(new Apple("金冠", "金色", 25.5, 500));
Collections.sort(apples);
System.out.println(apples);
// [Apple{name='青苹果', color='青色', price=12.5, weight=300}, Apple{name='红富士', color='红色', price=9.9, weight=500},
// Apple{name='金冠', color='金色', price=25.5, weight=500}, Apple{name='金元帅', color='金色', price=20.0, weight=600}]
// ②通过重写比较器对象中的比较方法,自定义比较规则
// 4. public static <T> void sort(List<T> list,Comparator<? super T> c) 将集合中元素按照指定规则排序
// Collections.sort(apples, new Comparator<Apple>() {
// @Override
// public int compare(Apple o1, Apple o2) {
// // 根据价格升序排序
// return Double.compare(o1.getPrice(), o2.getPrice()); // public static int compare(double d1, double d2)方法比较两个double类型数据,返回1或-1
// }
// });
// 使用lambda表达式简化匿名内部类的写法
Collections.sort(apples, (o1, o2) -> Double.compare(o1.getPrice(), o2.getPrice())); // public static int compare(double d1, double d2)方法比较两个double类型数据,返回1或-1
System.out.println(apples); // 重写比较器对象中的比较方法方式优先级高于自定义类型中实现比较规则Comparable接口,优先匹配重写比较器对象中的比较方法方式
// [Apple{name='红富士', color='红色', price=9.9, weight=500}, Apple{name='青苹果', color='青色', price=12.5, weight=300},
// Apple{name='金元帅', color='金色', price=20.0, weight=600}, Apple{name='金冠', color='金色', price=25.5, weight=500}]
}
}
注:重写比较器对象中的比较方法方式优先级高于自定义类型中实现比较规则Comparable接口,优先匹配重写比较器对象中的比较方法方式。
5.Collection体系的综合案例
案例—斗地主游戏
需求:在启动游戏房间的时候,应该提前准备好54张牌,完成洗牌、发牌、牌排序、逻辑。
分析:
①:当系统启动的同时需要准备好数据,可以使用静态代码块来完成。
②:洗牌就是打乱牌的顺序。
③:定义三个玩家、依次发出51张牌。
④:给玩家的牌进行排序(拓展)。
⑤:输出每个玩家的牌数据。
示例代码如下:
Card类
public class Card {
private String size; // 点数
private String color; // 花色
private int index; // 扑克牌权值大小
public Card() {
}
public Card(String size, String color, int index) {
this.size = size;
this.color = color;
this.index = index;
}
@Override
public String toString() {
return color + size;
}
// getter、setter
}
测试类
public class GameDemo {
// 定义一个静态的集合存储54张牌
public static List<Card> allCards = new ArrayList<>();
static int index; // 记录扑克牌权值大小
// 定义静态代码块初始化牌的数据
static {
// 初始化数据:点数确定、类型确定,使用数组
String[] sizes = {"3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "A", "2"};
// 初始化数据:花色数目确定、类型确定,使用数组
String[] colors = {"♥", "♦", "♣", "♠"};
for (String color : colors) {
index = 0; // 表示sizes数组中的"3"权值最小,仅表示大小关系,不一定是0,其他数目也可以
for (String size : sizes) {
index++; // 每向后查询一次,扑克牌权值大小+1,代表具有更大的效力
// 封装成一个牌对象
Card card = new Card(size, color, index);
// 将牌对象封装到集合容器中
allCards.add(card);
}
}
// 此时index代表数字中效力最大的"2"的权值,大王>小王>2
// 将大、小王牌对象封装到集合容器中
allCards.add(new Card("", "小王", ++index));
allCards.add(new Card("", "大王", ++index));
System.out.println("新牌:" + allCards);
}
public static void main(String[] args) {
// 洗牌—打乱扑克牌顺序
Collections.shuffle(allCards);
System.out.println("洗牌后:" + allCards);
// 定义3个ArrayList集合容器,代表3位玩家手中的扑克牌
List<Card> cardList1 = new ArrayList<>();
List<Card> cardList2 = new ArrayList<>();
List<Card> cardList3 = new ArrayList<>();
// 发牌—在牌堆中分别给3位玩家发17张牌,剩余3张作为底牌
for (int i = 0; i < allCards.size() - 3; i++) {
Card card = allCards.get(i);
// 轮循:第n张牌发给第1位玩家;第n + 1张牌发给第2位玩家;第n + 3张牌发给第3位玩家
// 通过取余的方式实现
switch (i % 3) {
case 0:
cardList1.add(card);
break;
case 1:
cardList2.add(card);
break;
case 2:
cardList3.add(card);
break;
default:
System.out.println("系统错误!");
}
}
// 拿到3张底牌
// 通过 List<E> subList(int fromIndex, int toIndex)方法(左闭右开)把最后3张牌截取为子集合
List<Card> lastThreeCards = allCards.subList(allCards.size() - 3, allCards.size());
// 模拟抢地主
Scanner sc = new Scanner(System.in);
System.out.println("请输入1、2、3第几位玩家是地主:");
int landlord = sc.nextInt();
// 为地主玩家分配底牌
switch (landlord) {
case 1:
cardList1.addAll(lastThreeCards);
break;
case 2:
cardList2.addAll(lastThreeCards);
break;
case 3:
cardList3.addAll(lastThreeCards);
break;
default:
System.out.println("系统错误!");
}
// 给玩家手牌排序
sortCards(cardList1);
sortCards(cardList2);
sortCards(cardList3);
// 将玩家手牌输出
System.out.println("玩家1:" + cardList1);
System.out.println("玩家2:" + cardList2);
System.out.println("玩家3:" + cardList3);
System.out.println("底牌:" + lastThreeCards);
}
/**
* create by: 全聚德在逃烤鸭、
* description: 扑克牌排序
* create time: 2022/5/6 0006 16:23
*
* @param cardList
* @return void
*/
private static void sortCards(List<Card> cardList) {
// Collections工具类的 public static <T> void sort(List<T> list, Comparator<? super T> c)方法与List类中的default void sort(Comparator<? super E> c)方法完全相同
Collections.sort(cardList, (o1, o2) -> o2.getIndex() - o1.getIndex()); // 降序排列
}
}
注:Collections工具类的 public static <T> void sort(List<T> list, Comparator<? super T> c)方法与List类中的default void sort(Comparator<? super E> c)方法完全相同
6.Map集合体系
6.1Map集合的概述
Map集合概述和使用
Collection是单列集合体系,Map集合是一种双列集合,每个元素包含两个数据。
Map集合的每个元素的格式:key=value(键值对元素)。
Map集合也被称为“键值对集合”。
Map集合整体格式:
Collection集合的格式:[元素1,元素2,元素3..]
Map集合的完整格式:{key1=value1 , key2=value2 , key3=value3 , ...}
6.2Map集合体系特点
Map集合体系如下图所示。
使用最多的Map集合是HashMap,重点掌握HashMap , LinkedHashMap , TreeMap。其他的后续理解。
Map集合体系特点
Map集合的特点都是由键决定的。
Map集合的键是无序,不重复,无索引,值不做要求(可以重复)。
Map集合后面重复的键对应的值会覆盖前面重复键的值。
Map集合的键和值均可以为null。
Map集合实现类特点
HashMap:元素按照键是无序,不重复,无索引,值不做要求。(与Map体系一致)
LinkedHashMap:元素按照键是有序,不重复,无索引,值不做要求。
TreeMap:元素按照建是排序,不重复,无索引的,值不做要求。
示例代码如下:
public static void main(String[] args) {
Map<String, Integer> maps = new HashMap<>();
maps.put("Java", 1);
maps.put("Nike", 5);
maps.put("BENZ", 3);
maps.put("Java", 2); // 键"Java"中的值"2"会覆盖之前的值"1"
maps.put("Dell", 3);
maps.put(null, null); // 键和值均支持null
System.out.println(maps); // {Nike=5, BENZ=3, null=null, Java=2, Dell=3}
}
6.3Map集合常用API
Map集合
Map是双列集合的祖宗接口,它的功能是全部双列集合都可以继承使用的。
Map集合常用API
方法名 | 说明 |
V put(K key,V value) | 根据键添加元素值,返回该键对应的原值, 若没有原值(添加新元素)则返回null |
V get(Object key) | 根据键获取对应值 |
V remove(Object key) | 根据键删除键值对元素, 返回被删除键对应的值 |
void clear() | 清空集合 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
Set<K> keySet() | 获取全部键的集合 |
Collection<V> values() | 获取全部值的集合,不会删去重复的值 |
void putAll(Map<? extends K, ? extends V> m) | 拷贝其他map集合,后面重复的键对应的值会覆盖前面重复键的值 |
示例代码如下:
public static void main(String[] args) {
Map<String, Integer> maps = new HashMap<>();
maps.put("iphoneX", 10);
maps.put("娃娃", 20);
maps.put("iphoneX", 100); // 键"iphoneX"中的值"100"会覆盖之前的值"10"
maps.put("HUAWEI", 100);
maps.put("生活用品", 10);
maps.put("手表", 10);
// {手表=10, 生活用品=10, iphoneX=100, 娃娃=20, HUAWEI=100}
System.out.println(maps);
// 1. void clear() 清空集合
// maps.clear();
// System.out.println(maps); // {}
// 2. boolean isEmpty() 判断集合是否为空
// boolean isEmpty = maps.isEmpty();
// System.out.println(isEmpty); // true
// 3. V get(Object key) 根据键获取对应值
Integer value = maps.get("HUAWEI");
System.out.println(value); // 100
System.out.println(maps.get("Nike")); // nu 若没有该键,取出的值为"null"
// 4. V remove(Object key) 根据键删除键值对元素,返回被删除键对应的值
Integer value2 = maps.remove("iphoneX");
System.out.println(value2); // 100
// 5. boolean containsKey(Object key) 判断集合是否包含指定的键
boolean isContainsKey = maps.containsKey("手表");
System.out.println(isContainsKey); // true
System.out.println(maps.containsKey("iphoneX")); // false
// 6. boolean containsValue(Object value) 判断集合是否包含指定的值
boolean isContainsValue = maps.containsValue(100);
System.out.println(isContainsValue); // true
System.out.println(maps.containsValue(50)); // false
// 7. Set<K> keySet() 获取全部键的集合
Set<String> keys = maps.keySet();
System.out.println(keys); // [手表, 生活用品, 娃娃, HUAWEI]
// 8. Collection<V> values() 获取全部值的集合
Collection<Integer> values = maps.values();
System.out.println(values); // [10, 10, 20, 100] 不会删去重复的值
// 9. int size() 集合的长度,也就是集合中键值对的个数
System.out.println(maps.size()); // 4
// 10. V put(K key,V value) 根据键添加元素值,返回该键对应的原值,若没有原值(添加新元素)则返回null
Integer putValue = maps.put("HTML", 1);
Integer putValue2 = maps.put("娃娃", 1);
System.out.println(putValue); // null
System.out.println(putValue2); // 20
System.out.println(maps); // {手表=10, 生活用品=10, HTML=1, 娃娃=1, HUAWEI=100}
// 11. void putAll(Map<? extends K, ? extends V> m) 拷贝其他map集合
Map<String, Integer> maps1 = new HashMap<>();
maps1.put("Java", 1);
maps1.put("HTML", 1);
Map<String, Integer> maps2 = new HashMap<>();
maps2.put("HTML", 2);
maps2.put("MySQL", 3);
maps1.putAll(maps2); // 键"HTML"中的值"2"会覆盖原值"1"
System.out.println(maps1); // {Java=1, MySQL=3, HTML=2}
}
6.4Map集合的遍历方式一:键找值
Map集合的遍历方式一:键找值
先获取Map集合的全部键的Set集合。
遍历键的Set集合,然后通过键提取对应值。
示例代码如下:
public static void main(String[] args) {
Map<String, Integer> maps = new HashMap<>();
maps.put("iphoneX", 10);
maps.put("娃娃", 20);
maps.put("HUAWEI", 100);
maps.put("生活用品", 10);
maps.put("手表", 10);
System.out.println(maps);
// 1.拿到集合的全部键
Set<String> keys = maps.keySet();
// 2.遍历每一个键,根据键提取值
for (String key : keys) {
int value = maps.get(key);
System.out.println(key + "-->" + value);
}
}
程序运行结果如下:
{手表=10, 生活用品=10, iphoneX=10, 娃娃=20, HUAWEI=100}
手表-->10
生活用品-->10
iphoneX-->10
娃娃-->20
HUAWEI-->100
6.5Map集合的遍历方式二:键值对
Map集合的遍历方式二:键值对
先把Map集合转换成Set集合,Set集合中每个元素都是键值对实体类型。
遍历Set集合,然后提取键以及提取值。
键值对涉及到的API
方法名 | 说明 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
K getKey() | 获得键 |
V getValue() | 获取值 |
示例代码如下:
public static void main(String[] args) {
Map<String, Integer> maps = new HashMap<>();
maps.put("iphoneX", 10);
maps.put("娃娃", 20);
maps.put("iphoneX", 100); // 键"iphoneX"中的值"100"会覆盖之前的值"10"
maps.put("HUAWEI", 100);
maps.put("生活用品", 10);
maps.put("手表", 10);
// {手表=10, 生活用品=10, iphoneX=100, 娃娃=20, HUAWEI=100}
System.out.println(maps);
// 1.将map集合转换为Set集合
Set<Map.Entry<String, Integer>> entries = maps.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
int value = entry.getValue();
System.out.println(key + "-->" + value);
}
}
程序运行结果如下:
{手表=10, 生活用品=10, iphoneX=100, 娃娃=20, HUAWEI=100}
手表-->10
生活用品-->10
iphoneX-->100
娃娃-->20
HUAWEI-->100
6.6Map集合的遍历方式三:lambda表达式
Map集合的遍历方式三:Lambda
得益于JDK 8开始的新技术Lambda表达式,提供了一种更简单、更直接的遍历集合的方式。
Map结合Lambda遍历的API
方法名 | 说明 |
default void forEach(BiConsumer<? super K, ? super V> action) | 结合lambda遍历Map集合 |
示例代码如下:
public static void main(String[] args) {
Map<String, Integer> maps = new HashMap<>();
maps.put("iphoneX", 10);
maps.put("娃娃", 20);
maps.put("iphoneX", 100); // 键"iphoneX"中的值"100"会覆盖之前的值"10"
maps.put("HUAWEI", 100);
maps.put("生活用品", 10);
maps.put("手表", 10);
// {手表=10, 生活用品=10, iphoneX=100, 娃娃=20, HUAWEI=100}
System.out.println(maps);
// maps.forEach(new BiConsumer<String, Integer>() {
// @Override
// public void accept(String key, Integer value) {
// System.out.println(key + "-->" + value);
// }
// });
// lambda表达式简化匿名内部类
maps.forEach((key, value) -> System.out.println(key + "-->" + value));
}
Map集合案例:统计投票人数
需求:某个班级80名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生 只能选择一个景点,请统计出最终哪个景点想去的人数最多。
分析:
将80个学生选择的数据拿到程序中去。
定义Map集合用于存储最终统计的结果。
遍历80个学生选择的数据,看Map集合中是否存在,不存在存入“数据=1“,存在则其对应值+1。
示例代码如下:
public static void main(String[] args) {
// 1.拿到80个学生的选择数据
String[] selects = {"A", "B", "C", "D"};
StringBuilder sb = new StringBuilder();
Random r = new Random();
for (int i = 0; i < 80; i++) {
sb.append(selects[r.nextInt(selects.length)]);
}
System.out.println(sb);
// DACCBAACDAACBACDBCCABBAABAAADBDCBBDCCAACCADDAADCAADADACAADCDBCBCCACACBBBCBADDBCA
// 2.定义一个map集合记录最终统计结果
Map<Character, Integer> infos = new HashMap<>();
// 3.遍历学生选择数据
for (int i = 0; i < sb.length(); i++) {
// 4.提取当前选择字符
char ch = sb.charAt(i);
// 5.判断当前Map集合中是否存在这个键
if (infos.containsKey(ch)) { // 存在,说明之前出现过该景点选择
// 值+1
infos.put(ch, infos.get(ch) + 1);
} else { // 不存在,说明该景点选择是第一次出现
// 值=1
infos.put(ch, 1);
}
}
System.out.println(infos); // {A=27, B=16, C=22, D=15}
}
6.7Map集合的实现类HashMap、LinkedHashMap、TreeMap
6.7.1HashMap的特点和底层原理
实际上:Set系列集合的底层就是Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
由键决定:无序、不重复、无索引。HashMap底层是哈希表结构的。
依赖hashCode方法和equals方法保证键的唯一。
如果键要存储的是自定义对象,需要重写hashCode和equals方法。
基于哈希表。增删改查的性能都较好。
6.7.2LinkedHashMap集合概述和特点
由键决定:有序、不重复、无索引。
这里的有序指的是保证存储和取出的元素顺序一致。
原理:底层数据结构是依然哈希表,只是每个键值对元素又额外的多了一个双链表的机制记录存储的顺序。
6.7.3TreeMap集合概述和特点
由键决定特性:不重复、无索引、排序。
排序:按照键数据的大小默认升序(有小到大)排序,只能对键排序。
注意:TreeMap集合一定要排序,可以默认排序,也可以将键按照指定的规则进行排序。
TreeMap和TreeSet底层原理是一样的。
TreeMap集合自定义排序规则有2种:
类实现Comparable接口,重写比较规则。
集合自定义Comparator比较器对象,重写比较规则。
7.补充知识:集合的嵌套
案例—Map集合案例-统计投票人数
需求:某个班级多名学生,现在需要组成秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学 生可以选择多个景点,请统计出最终哪个景点想去的人数最多。
分析:
将80个学生选择的数据拿到程序中去,需要记住每个学生选择的情况。
定义Map集合用于存储最终统计的结果。
示例代码如下:
public static void main(String[] args) {
// 1.拿到80个学生的选择数据
// 使用Map集合存储
Map<String, List<String>> data = new HashMap<>();
// 2.将学生选择数据存储进去
List<String> selects1 = new ArrayList<>();
Collections.addAll(selects1, "A", "C");
data.put("张三", selects1);
List<String> selects2 = new ArrayList<>();
Collections.addAll(selects2, "B", "C", "D");
data.put("李四", selects2);
List<String> selects3 = new ArrayList<>();
Collections.addAll(selects3, "A", "D");
data.put("王五", selects3);
System.out.println(data); // {李四=[B, C, D], 张三=[A, C], 王五=[A, D]}
// 3.统计每个景点的选择人数
Map<String, Integer> maps = new HashMap<>();
// 4.获取所有人选择的景点的信息
Collection<List<String>> values = data.values();
// values = [[B, C, D], [A, C], [A, D]]
for (List<String> value : values) { // 遍历data集合中所有的值,每个值均为一个List集合
for (String str : value) { // 遍历某个List集合中所有的值,每个值均为一个景点选项
// 5.判断当前maps集合中是否存在这个键
if (maps.containsKey(str)) { // 存在,说明之前出现过该景点选择
// 值+1
maps.put(str, maps.get(str) + 1);
} else { // 不存在,说明该景点选择是第一次出现
// 值=1
maps.put(str, 1);
}
}
}
System.out.println(maps); // {A=2, B=1, C=2, D=2}
}
写在最后:
感谢读完!
纵然缓慢,驰而不息!加油!