目录
一、整体概述
- Map集合称为双列集合,格式:{key1=value1,key2=value2,key3=value3,…}
- 一次需要存一对数据做为一个元素Map集合的每个元素“key=value”称为一个键值对/键值对对象/一个Entry对象,Map集合也被叫做“键值对"
- 集合Map集合的所有键是不允许重复的,但值可以重复,键和值是一一对应的,每一个键只能找到自己对应的值
- 应用场景:需要存储一一对应的数据时,就可以考虑用Map集合来做
- Map集合体系特点:
-
- tip: Map系列集合的特点都是由键决定的,值只是一个附属品,值不做要求
- HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)
- LinkedHashMap (由键决定特点): 有序、不重复、无索引
- TreeMap (由键决定特点): 按照大小默认升序排序、不重复、无索引
public static void main(String[] args) {
//Map<String,Integer> map = new HashMap<>(); //经典代码 按照键 无序 不重复 无索引
Map<String,Integer> map = new LinkedHashMap<>(); //经典代码 按照键 有序 不重复 无索引
map.put("大佬",100);
map.put("手表",100);
map.put("手表",50);
map.put("樊振东",79);
map.put("手表",150);//重复的数据会覆盖前面的数据
System.out.println(map);//{大佬=100, 手表=150, 樊振东=79}
Map<Integer,String> map1 = new TreeMap<>(); //经典代码 按照键 有规定排序默认按升序 不重复 无索引
map1.put(25,"java");
map1.put(15,"樊振东");
map1.put(36,"牛魔王");
System.out.println(map1);//{15=樊振东, 25=java, 36=牛魔王}
}
二、Map常用方法
public static void main(String[] args) {
//Map<String,Integer> map = new HashMap<>(); //经典代码 按照键 无序 不重复 无索引
Map<String,Integer> map = new LinkedHashMap<>(); //经典代码 按照键 有序 不重复 无索引
map.put("大佬",100);
map.put("手表",100);
map.put("手表",50);
map.put("樊振东",79);
map.put("手表",150);//重复的数据会覆盖前面的数据
System.out.println(map);//{大佬=100, 手表=150, 樊振东=79}
//2. public int size() : 获取集合大小
System.out.println(map.size());
//public void clear(): 清空集合
//map.clear();
//3. public boolean isEmpty() : 判断集合是否为空
System.out.println(map.isEmpty());
//4.public V get(object key)根据键获取对应值
System.out.println(map.get("樊振东")); //79
System.out.println(map.get("牛马")); // 不存在 null
//5.public V remove(object key):根据键删除整个元素(删除键会返回键的值)
System.out.println(map.remove("手表"));
//6.public boolean containsKey(0bject key):判断是否包含某个键 ,包含返回true ,反之
System.out.println(map.containsKey("樊振东"));
//7.public boolean containsValue(object value): 判断是否包含某个值。
System.out.println(map.containsValue(79));
//8.public Set<K> keySet():获取Map集合的全部键
System.out.println(map.keySet()); //[大佬, 樊振东]
//9.public Collection<V> values();获取Map集合的全部值
Collection<Integer> values = map.values();
System.out.println(values); //[100, 79]
//10.把其他Map集合的数据倒入到自己集合中来。(拓展)
Map<String,Integer> map2 = new HashMap<>();
map2.put("j1",11);
map2.put("j2",88);
map2.put("j8",66);
Map<String,Integer> map3 = new HashMap<>();
map3.put("j2",55);
map3.put("j9",99);
map2.putAll(map3);
System.out.println(map2); //{j1=11, j2=55, j8=66, j9=99}
System.out.println(map3); //{j2=55, j9=99}
}
三、Map 集合的遍历方式
1. 键找值
(先获取Map集合全部的键,再通过遍历键来找值)
需要用到的方法:public Set<K> keySet() 获取所有键的集合;
public V get(Object key) 根据键获取其对应的值
public static void main(String[] args) {
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",165.9);
map.put("蜘蛛精",175.9);
map.put("紫霞",169.8);
map.put("孙悟空",180.5);
map.put("牛魔王",190.6);
//获取键
Set<String> keys = map.keySet();
System.out.println(keys);
//遍历键得到对应的值
for (String key : keys) {
double height = map.get(key);
System.out.println(key + "===>" + height);
}
}
2. 键值对
public static void main(String[] args) {
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",169.8);
map.put("紫霞",165.8);
map.put("至尊宝",169.5);
map.put("牛魔王",183.6);
System.out.println(map);
//1. 调用Map集合提供的entrySet方法,把Map集合转换成键值对类型的Set集合
Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) {
String key = entry.getKey();
double values = entry.getValue();
System.out.println(key + "===>" + values);
}
}
3. Lambda 表达式
1. 理解:new BiConsumer<String, Double>() 之后,在forEach里面,会把Map集合转换成键值对类型的Set集合,再拿到key和value值,调用accept方法,传入key和value值
map.forEach(new BiConsumer<String, Double>() {
@Override
public void accept(String k, Double v) {
System.out.println(k + "--->" + v);
}
});
简化写法与完整代码:
public static void main(String[] args) {
Map<String,Double> map = new HashMap<>();
map.put("蜘蛛精",169.8);
map.put("紫霞",165.8);
map.put("至尊宝",169.5);
map.put("牛魔王",183.6);
System.out.println(map);
map.forEach((k,v) -> {
System.out.println(k + "--->" + v);
});
}
四、Map集合案例——统计投票人数
/*
* 需求: 某个班级80名学生,现在需要组织秋游活动,班长提供了四个景点依次是(A、B、C、D),每个学生只能选择一个景点,请统计出最终哪个景点想去的人数最多。
* 分析: 将80个学生选择的数据拿到程序中去,[A,A,B,A,B,C,D,…]
一一对应,景点和选择人数,所以考虑用Map集合来存储最终结果
准备一个Map集合用于存储统计的结果,Map<String,Integer>,键是景点,值代表投票数量
遍历80个学生选择的景点,每遍历一个景点,就看Map集合中是否存在该景点,不存在存入“景点=1存在则其对应值+1
*/
public static void main(String[] args) {
//1. 把80个学生选择的景点数据拿到程序中来
List<String> data = new ArrayList<>();
String[] selects = {"A","B","C","D"};
Random r =new Random();
for (int i = 0; i < 80; i++) {
int ran = r.nextInt(4);
String num = selects[ran];
data.add(num);
}
System.out.println(data);
//开始统计每个景点的投票人数
//准备一个Map集合用于统计最终的结果
Map<String,Integer> result = new HashMap<>();
//开始遍历80个景点数据
for (String s : data) {
//询问Map集合里面是否存在该景点
if (result.containsKey(s)){
//说明这个景点之前统计过,其值加1,存入到Map集合里面去
int numbers = result.get(s) + 1;
result.put(s,numbers);
}else {
//这个景点第一次统计
result.put(s,1);
}
}
System.out.println(result);
}
五、Map 底层原理
1. HashMap :无序、不重复、无索引
- HashMap跟HashSet的底层原理是一摸一样的,都是基于哈希表实现的
(实际上原来的Set集合的底层就是基于Map实现的,只是Set只要键数据,不要值数据)
流程:首先创建一个长度为16的数组;把HashMap的键值作为一个Entry对象,然后利用键计算哈希值存入数组中;
如果有一个重复数据要存入,在JDK8之前会直接替换原来的数据,原来的数据存到新数据下面,JDK8之后,新数据会存到原来的数据下面;
但存入数据长度大于12时,会创建一个新数组(扩容);
当链表长度大于8,数组长度大于64,会转变成红黑树
- HashMap集合是一种增删改查数据,性能都较好的集合
- 但是它是无序,不能重复,没有索引支持的(由键决定特点)
- 如果键存储的是自定义类型的对象,可以通过重写hashcode和equals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的
- HashMap的键依赖hashcode方法和equals方法保证键的唯一
当你尝试将一个新的键值对添加到
HashMap
中时,HashMap
首先会调用键对象的hashCode()
方法来计算其哈希码,并根据这个哈希码确定键应该存储的位置。如果在该位置没有其他键,新的键值对就会被添加到
HashMap
中。如果该位置已经有其他键,
HashMap
会使用键对象的equals()
方法来检查新键是否与现有键相等。如果
equals()
返回true
,表示两个键相等,HashMap
将更新现有键的值(如果HashMap
允许键被覆盖)。如果
equals()
返回false
,表示两个键不相等,HashMap
将使用一种冲突解决策略(通常是链表或红黑树)来存储新的键值对。
public static void main(String[] args) {
Map<Student,String> map = new HashMap<>();
map.put(new Student("蜘蛛精",25,168.5),"盘丝洞");
map.put(new Student("蜘蛛精",25,168.5),"水帘洞");
map.put(new Student("至尊宝",29,145.5),"水帘洞");
map.put(new Student("牛魔王",19,175.5),"牛头山");
System.out.println(map);
}
此时如果没有重写HashCode和equals方法会导致两个蜘蛛精,重写方法如下:
//在Student类里面重写equals和HashCode方法
@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 && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
//姓名 年龄 身高来计算哈希值的
return Objects.hash(name, age, height);
}
2. LinkedHashMap : 有序、不重复、无索引
1. 基于哈希表,每个键值对元素多了一个双链表的机制记录元素顺序
2. map.put("键","值") 封装成一个Entry对象;创建一个长度为16的数组,根据键计算哈希值,存入数组;
3. 基于双链表实现有序
3. TreeMap: 默认升序排序,不重复,无索引
1. 原理:基于红黑树实现
2. 支持两种方式来指定排序规则(与之前的一样,代码简略了,只写了一种方式)
- 让类实现comparable接口,重写比较规则
- TreeMap集合有一个有参构造器,支持创建Comparator比较器对象,以便来指定比较规则
public static void main(String[] args) {
Map<Student,String> map = new TreeMap<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return Double.compare(o1.getHeight(),o2.getHeight());
}
});
//简化代码
//Map<Student,String> map = new TreeMap<>((o1, o2) -> Double.compare(o1.getHeight(),o2.getHeight()));
map.put(new Student("蜘蛛精",25,168.5),"盘丝洞");
map.put(new Student("蜘蛛精",25,168.5),"水帘洞");
map.put(new Student("至尊宝",29,145.5),"水帘洞");
map.put(new Student("牛魔王",19,175.5),"牛头山");
System.out.println(map);
}
六、集合的嵌套
1. 需求:
要求在程序中记住如下省份和其对应的城市信息,记录成功后,要求可以查询出湖北省的城市信息。
[江苏省=南京市,扬州市,苏州市,无锡市,常州市
湖北省=武汉市,孝感市,十堰市,宜昌市,鄂州市
河北省=石家庄市,唐山市,保定市,张家口市]
2. 分析
定义一个Map集合,键用表示省份名称,值表示城市名称,注意:城市会有多个。
根据“湖北省”这个键获取对应的值展示即可。
public static void main(String[] args) {
Map<String, List<String>> map = new HashMap<>();
List<String> cities1 = new ArrayList<>();
Collections.addAll(cities1,"南京市","扬州市","苏州市","无锡市","常州市");
map.put("江苏省",cities1);
List<String> cities2 = new ArrayList<>();
Collections.addAll(cities2,"武汉市","孝感市","十堰市","宜昌市","鄂州市");
map.put("湖北省",cities2);
List<String> cities3 = new ArrayList<>();
Collections.addAll(cities3,"石家庄市","唐山市","保定市","张家口市");
map.put("河北省",cities3);
System.out.println(map);
//通过键来获取值
List<String> cities = map.get("湖北省");
for (String city : cities) {
System.out.println(city);
}
//遍历全部信息
map.forEach((p,c) -> {
System.out.println(p + "--->" + c);
/* 江苏省--->[南京市, 扬州市, 苏州市, 无锡市, 常州市]
湖北省--->[武汉市, 孝感市, 十堰市, 宜昌市, 鄂州市]
河北省--->[石家庄市, 唐山市, 保定市, 张家口市]*/
});
}
记录,回顾,知新 Byebye!