一. Map集合体系
1. Map集合的概述
- Map集合也称键值对集合
- Map集合的特点是由键决定的
- Map和List不同的是,Map存储的是key-value的映射关系,并且它不保证顺序,同时一个key只能关联一个value,始终牢记:Map中不存在重复的key,因为放入相同的key,只会把原有的key-value中对应的value给替换掉。
package com.gch.d5_map;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
目标:认识Map体系的特点:按照键无序,不重复,无索引。值不做要求
*/
public class MapDemo1 {
public static void main(String[] args) {
// 1.创建一个Map集合对象
// HashMap:元素按照键是无序,不重复,无索引,值不做要求(与Map体系一致)
Map<String, Integer> maps = new HashMap<>(); // 一行经典代码
maps.put("鸿星尔克",3);
maps.put("Java",1);
maps.put("枸杞",100);
maps.put("Java",100); // 它会覆盖前面的数据
maps.put(null,null);
System.out.println(maps); // {null=null, Java=100, 枸杞=100, 鸿星尔克=3}
// LinkedHashMap:元素是按照键是有序,不重复,无索引,值不做要求。
Map<String, Integer> maps2 = new LinkedHashMap<>();
maps2.put("鸿星尔克",3);
maps2.put("Java",1);
maps2.put("枸杞",100);
maps2.put("Java",100); // 它会覆盖前面的数据
maps2.put(null,null);
System.out.println(maps2); // {鸿星尔克=3, Java=100, 枸杞=100, null=null}
}
}
2. Map集合体系特点
3. Map集合常用API
package com.gch.d6_map_api;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
目标:Map集合的常用API(重点中的重点)
- public V put(K key, V value): 把指定的键与指定的值添加到Map集合中。
- public V remove(Object key): 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。
- public V get(Object key) 根据指定的键,在Map集合中获取对应的值。
- public Set<K> keySet(): 获取Map集合中所有的键,存储到Set集合中。
- public Set<Map.Entry<K,V>> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。
- public boolean containKey(Object key):判断该集合中是否有此键。
- public boolean containValue(Object value):判断该集合中是否有此值。
*/
public class MapDemo {
public static void main(String[] args) {
// 1.添加元素: 无序,不重复,无索引。
Map<String , Integer> maps = new HashMap<>();
maps.put("iphoneX",10);
maps.put("娃娃",20);
maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",100);
maps.put("生活用品",10);
maps.put("手表",10);
// {huawei=100, 手表=10, 生活用品=10, iphoneX=100, 娃娃=20}
System.out.println(maps);
// 2.清空集合
// maps.clear();
// System.out.println(maps);
// 3.判断集合是否为空,为空返回true ,反之!
System.out.println(maps.isEmpty());
// 4.根据键获取对应值:public V get(Object key)
Integer key = maps.get("huawei");
System.out.println(key);
System.out.println(maps.get("生活用品")); // 10
System.out.println(maps.get("生活用品2")); // null
// 5.根据键删除整个元素。(删除键会返回键的值)
System.out.println(maps.remove("iphoneX")); // 100
System.out.println(maps); // {huawei=100, 手表=10, 生活用品=10, 娃娃=20}
// 6.判断是否包含某个键 ,包含返回true ,反之
System.out.println(maps.containsKey("娃娃")); // true
System.out.println(maps.containsKey("娃娃2")); // false
System.out.println(maps.containsKey("iphoneX")); // false
// 7.判断是否包含某个值。
System.out.println(maps.containsValue(100)); // true
System.out.println(maps.containsValue(10)); // true
System.out.println(maps.containsValue(22)); // false
// {huawei=100, 手表=10, 生活用品=10, 娃娃=20}
// 8.获取全部键的集合:public Set<K> keySet()
// 因为Map集合的键本身是无序,不重复,无索引的,与Set集合一样
Set<String> keys = maps.keySet();
System.out.println(keys); // [huawei, 手表, 生活用品, 娃娃]
System.out.println("------------------------------");
// 9.获取全部值的集合:Collection<V> values();
Collection<Integer> values = maps.values();
System.out.println(values); // [100, 10, 10, 20]
// 10.集合的大小
System.out.println(maps.size()); // 4
// 11.合并其他Map集合。(拓展)
Map<String , Integer> map1 = new HashMap<>();
map1.put("java1", 1);
map1.put("java2", 100);
Map<String , Integer> map2 = new HashMap<>();
map2.put("java2", 1);
map2.put("java3", 100);
map1.putAll(map2); // 把集合map2的元素拷贝一份到map1中去
System.out.println(map1); // {java3=100, java2=1, java1=1}
System.out.println(map2); // {java3=100, java2=1}
}
}
4. Map集合的遍历方式一:键找值
package com.gch.d7_map_traversal;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
目标:Map集合的遍历方式一:键找值
Map集合的遍历方式有:3种。
(1)“键找值”的方式遍历:先获取Map集合全部的键,再根据遍历键找值。
(2)“键值对”的方式遍历:难度较大。
(3)JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)
a.“键找值”的方式遍历Map集合。
1.先获取Map集合的全部键的Set集合。
2.遍历键的Set集合,然后通过键找值。
小结:
代码简单,需要记住!
*/
public class MapDemo01 {
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps); // {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
// 1、键找值:第一步:先拿到集合的全部键。
Set<String> keys = maps.keySet();
// 2、第二步:遍历每个键,根据键提取值
for (String key : keys) {
int value = maps.get(key);
System.out.println(key + "===>" + value);
}
}
}
5. Map集合的遍历方式二:键值对
package com.gch.d7_map_traversal;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
目标:Map集合的遍历方式。
Map集合的遍历方式有:3种。
(1)“键找值”的方式遍历:先获取Map集合全部的键,再根据键找值。
(2)“键值对”的方式遍历:难度较大。
(3)JDK 1.8开始之后的新技术:Lambda表达式。
b.“键值对”的方式遍历:
1.把Map集合转换成一个Set集合:Set<Map.Entry<K, V>> entrySet();
2.此时键值对元素的类型就确定了,类型是键值对实体类型:Map.Entry<K, V>
3.接下来就可以用foreach遍历这个Set集合,类型用Map.Entry<K, V>
*/
public class MapDemo02 {
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
/**
maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
👇
使用foreach遍历map集合.发现Map集合的键值对元素直接是没有类型的。所以不可以直接foreach遍历集合。
👇
可以通过调用Map的方法 entrySet把Map集合转换成Set集合形式 maps.entrySet();
👇
Set<Map.Entry<String,Integer>> entries = maps.entrySet();
[(huawei=1000), (手表=10), (生活用品=10), (iphoneX=100), (娃娃=30)]
entry
👇
此时可以使用foreach遍历
*/
// 1、把Map集合转换成Set集合
Set<Map.Entry<String, Integer>> entries = maps.entrySet();
// 2、开始遍历
for(Map.Entry<String, Integer> entry : entries){
String key = entry.getKey();
int value = entry.getValue();
System.out.println(key + "====>" + value);
}
}
}
6. Map集合的遍历方式三:lambda表达式
package com.gch.d7_map_traversal;
import java.util.HashMap;
import java.util.Map;
/**
目标:Map集合的遍历方式。
Map集合的遍历方式有:3种。
(1)“键找值”的方式遍历:先获取Map集合全部的键,再根据键找值。
(2)“键值对”的方式遍历:难度较大。
(3)JDK 1.8开始之后的新技术:Lambda表达式。
c.JDK 1.8开始之后的新技术:Lambda表达式。(暂时了解)
*/
public class MapDemo03 {
public static void main(String[] args) {
Map<String , Integer> maps = new HashMap<>();
// 1.添加元素: 无序,不重复,无索引。
maps.put("娃娃",30);
maps.put("iphoneX",100);// Map集合后面重复的键对应的元素会覆盖前面重复的整个元素!
maps.put("huawei",1000);
maps.put("生活用品",10);
maps.put("手表",10);
System.out.println(maps);
// maps = {huawei=1000, 手表=10, 生活用品=10, iphoneX=100, 娃娃=30}
// maps.forEach(new BiConsumer<String, Integer>() {
// @Override
// public void accept(String key, Integer value) {
// System.out.println(key + "--->" + value);
// }
// });
maps.forEach((k, v) -> System.out.println(k + "--->" + v));
}
}
7. Map集合的实现类HashMap
- 哈希表是一种对于增删改查数据性能都较好的结构。
- 哈希表的组成:
- JDK8之前的,底层使用数组+链表组成
- JDK8开始后,底层采用数组+链表+红黑树组成。
- 了解哈希表之前需要了解哈希值的概念。
- 哈希值:是JDK根据对象的地址,按照某种规则算出来的int类型的数值。
- Object类的API:public int hashCode():返回对象的哈希值
对象的哈希值特点:
- 同一个对象多次调用hashCode()方法返回的哈希值是相同的。
- 默认情况下,不同对象的哈希值是不同的。
- 每个对象都有自己的哈希值,因为每个对象都有自己的地址。
- 哈希算法:根据元素的哈希值跟数组的长度求余计算出应存入的位置(哈希算法)
HashMap(Key-Value):Key只允许一个为null(如果Key有多个null,Value会进行数据覆盖),Value可以有多个null。
package com.gch.hashmap.test;
import java.util.HashMap;
import java.util.Map;
/**
* @author A.G.H
*/
public class HashMapTest {
public static void main(String[] args) {
Map<String,Integer> map = new HashMap<>();
map.put(null,1);
map.put(null,3);
map.put(null,null);
System.out.println(map); // {null=null}
}
}
- HashMap是一种基于哈希表实现的数据结构,它可以快速查找和插入元素,JDK1.8开始HashMap底层使用数组+链表+红黑树存储元素,每个元素包含一个键和一个值。
- 当添加元素时,先计算键的哈希值,然后结合哈希算法,根据键的哈希值对数组长度求余算出应该存入数组的位置,即对应的数组下标,如果下标对应的链表或红黑树为空,则将元素插入到该位置,否则遍历链表或红黑树,查找是否存在相同的键,如果存在,则更新对应的值;否则,将元素插入到链表或者红黑树的末尾。
- 当查找元素时,先计算键的哈希值,然后结合哈希算法根据键的哈希值对数组长度求余算出应该存入数组的位置,即对应的数组下标,如果该下标对应的链表或红黑树为空,则元素不存在,否则,遍历链表或红黑树,查找是否存在相同的键,如果存在,则返回对应的值;否则,元素不存在。
- HashMap支持快速查找和插入(添加元素)操作,但是在遍历元素时,需要遍历整个链表或者红黑树,因此效率较低。
- HashMap适用于需要快速查找和插入元素的场景,如缓存和索引。
- HashMap是非线程安全的。
HashMap是如何解决哈希冲突的?
- 数组的每一个位置我们一般在算法里面叫做桶了,通过这个put方法去往HashMap去放数据的时候,HashMap会根据Key的这个Hash值去进行这个取模的运算,通过取模结果的运算去决定我最后放在哪个桶中或者具体的放到数组的哪个位置上面去,但是这种设计就会存在哈希冲突的问题,就是两个不同Hash值得Key它有一定几率会存入到同一个数组下标上面去,这样子就会造成一个桶中要入多个数据,所以这个HashMap它是采用链式寻址的方式来解决这个Hash冲突的问题的,对于存在冲突的Key,HashMap会把这个Key组成一个单向链表,后面的元素就会以链加到链表尾部的方式,这个一般我们叫做尾插法,就避免这个链表长度过长的问题,JDK1.8版本还做了优化,如果链表过长的话实际查询效率会降低,因此当链表长度超过8会自动转为红黑树,提升了查询的性能。
package com.gch.d9_map_impl;
import java.util.Objects;
public class Student {
private String name;
private int age;
private char sex;
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
/**
* 只要两个对象内容一样,结果一定是true
* @param o
* @return
*/
@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 && sex == student.sex && Objects.equals(name, student.name);
}
/**
两个对象的内容一样,哈希值就一样
入参一样,则哈希值相同
*/
@Override
public int hashCode() {
return Objects.hash(name, age, sex);
}
public Student(String name, int age, char sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
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;
}
public char getSex() {
return sex;
}
public void setSex(char sex) {
this.sex = sex;
}
}
package com.gch.d9_map_impl;
import java.util.HashMap;
import java.util.Map;
public class HashMapDemo1 {
public static void main(String[] args) {
// Map集合是根据键去除重复元素的
// 如果键存储的自定义对象,需要重写hashCode()和equals方法。
Map<Student, String> maps = new HashMap<>();
Student s1 = new Student("小李",18,'男');
Student s2 = new Student("小李",18,'男');
Student s3 = new Student("小夜",21,'女');
maps.put(s1, "杭州");
maps.put(s2, "广州");
maps.put(s3, "上海");
System.out.println(maps);
//{Student{name='小夜', age=21, sex=女}=上海, Student{name='小李', age=18, sex=男}=广州}
}
}
8. Map集合的实现类LinkedHashMap
package com.gch.d9_map_impl;
import java.util.LinkedHashMap;
import java.util.Map;
public class LinkedHashMapDemo2 {
public static void main(String[] args) {
// LinkedHashMap:键是有序,不重复,无索引
Map<String, Integer> maps = new LinkedHashMap<>();
maps.put("1",1);
maps.put("2",2);
maps.put("1",10);
maps.put("3",3);
System.out.println(maps); // {1=10, 2=2, 3=3}
}
}
9. Map集合的实现类TreeMap
package com.gch.d9_map_impl;
// 方式一:类实现Comparable接口(泛型接口),重写比较规则
public class Apple implements Comparable<Apple> {
private String name;
private String color;
private double price; // 价格
private int weight; // 重量
/**
* 方式一:类自定义比较规则
* o1.compareTo(o2)
* @param o the object to be compared.
* @return
*/
@Override
public int compareTo(Apple o) {
// 按照重量大小进行升序比较的
// return this.weight - o.weight; // 会去掉重重复的元素
return this.weight - o.weight >= 0 ? 1 : -1; // 保留重量重复的元素,一旦有相等的,认为第一个大一点
}
@Override
public String toString() {
return "Apple{" +
"name='" + name + '\'' +
", color='" + color + '\'' +
", price=" + price +
", weight=" + 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;
}
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;
}
}
package com.gch.d9_map_impl;
import java.util.Comparator;
import java.util.Map;
import java.util.TreeMap;
public class TreeMapDemo3 {
public static void main(String[] args) {
// TreeMap集合:由键决定特性:可排序,不重复,无索引
// 可排序:按照键数据的大小默认升序排序,只能对键排序!
// TreeMap集合是一定要排序的,可以默认排序,也可以将键按照指定的规则进行排序
// TreeSet底层就是基础TreeMap设计的
Map<Integer, String> maps1 = new TreeMap<>();
maps1.put(1, "12");
maps1.put(3, "17");
maps1.put(0, "5");
System.out.println(maps1); // {0=5, 1=12, 3=17}
Map<Apple,String> maps2 = new TreeMap<>(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o2.getWeight() - o1.getWeight() >= 0 ? 1 : -1; // 按照重量进行降序排序且不排除重复元素
}
});
Apple a1 = new Apple("红富士","红色",9.9,500);
Apple a2 = new Apple("青苹果","绿色",15.9,300);
Apple a3 = new Apple("绿苹果","青色",29.9,400);
Apple a4 = new Apple("黄苹果","黄色",9.8,500);
maps2.put(a1, "广东");
maps2.put(a2, "深圳");
maps2.put(a3, "西安");
maps2.put(a4, "洛南");
System.out.println(maps2);
}
}
二. Map集合案例-统计投票人数
package com.gch.d8_map_test;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
public class MapTest2 {
public static void main(String[] args) {
// 1.定义一个数组存储4个景点
String[] selects = {"A", "B", "C", "D"};
// 2.定义for循环让80个学生随机选择这四个景点之一并且拼接起来
StringBuilder sb = new StringBuilder();
Random r = new Random();
for(int i = 0;i < 80;i++){
sb.append(selects[r.nextInt(selects.length)]);
}
// 3.将拼接好的转为String类型
String s = sb.toString();
// 4.定义Map集合存储统计好的景点人数
Map<Character, Integer> maps = new HashMap<>();
// 5.遍历统计好的景点人数
for(int i = 0;i < s.length();i++){
// 6.提取当前被选择的景点
char ch = s.charAt(i);
// 7.看Map集合中是否出现过该景点
if(maps.containsKey(ch)){
// 出现过该景点,让其值+1
maps.put(ch, maps.get(ch) + 1);
}else{
// 该景点第一次出现
maps.put(ch, 1);
}
}
// 8.输出Map集合展示
System.out.println(maps); // {A=16, B=21, C=24, D=19}
}
}
三. 集合的嵌套
package com.gch.d10_map;
import java.util.*;
/**
Map集合案例:统计投票人数
*/
public class MapTest1 {
public static void main(String[] args) {
// 1.要求程序记录每个学生选择的情况
// 使用一个Map集合存储 集合嵌套
Map<String, List<String>> datas = new HashMap<>();
// 2.把学生选择的数据存入进去
List<String> selects1 = new ArrayList<>();
Collections.addAll(selects1, "A","D");
datas.put("富康",selects1);
List<String> selects2 = new ArrayList<>();
Collections.addAll(selects2, "B","C","D");
datas.put("新韩",selects2);
List<String> selects3 = new ArrayList<>();
Collections.addAll(selects3, "A","B","C","D");
datas.put("户田",selects3);
System.out.println(datas); // {富康=[A, D], 新韩=[B, C, D], 户田=[A, B, C, D]}
// 3.统计每个景点选择的人数
Map<String, Integer> infos = new HashMap<>();
// 4.提取所有人选择的景点信息
Collection<List<String>> values = datas.values();
System.out.println(values); // [[A, D], [B, C, D], [A, B, C, D]]
for (List<String> value : values) {
for (String s : value) {
if(infos.containsKey(s)){
// 包含该景点,让该值+1
infos.put(s, infos.get(s) + 1);
}else{
// 该景点第一次被选择
infos.put(s, 1);
}
}
}
// 5.输出该集合展示被选择的信息
System.out.println(infos); // {A=2, B=2, C=2, D=3}
}
}