【Java数据结构】Map和Set的小知识,你知道多少?

195 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

文章目录


一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情

动态查找的集合容器

Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。以前常见的搜索方式有:

  1. 直接遍历,时间复杂度为O(N),元素如果比较多效率会非常慢
  2. 二分查找,时间复杂度为O(logn) ,但搜索前必须要求序列是有序的
    上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如:
  3. 根据姓名查询考试成绩
  4. 通讯录,即根据姓名查询联系方式
  5. 不重复集合,即需要先搜索关键字是否已经在集合中

我们甚至可能在查找时进行一些插入和删除的操作,即动态查找,那上述两种方式就不太适合了,本节介绍的Map和Set是一种适合动态查找的集合容器

1.两种模型

一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以
模型会有两种:

  1. 纯 key 模型,比如:有一个英文词典,快速查找一个单词是否在词典中;快速查找某个名字在不在通讯录中。
  2. Key-Value 模型,比如:统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:<单词,单词出现的次数>;梁山好汉的江湖绰号:每个好汉都有自己的江湖绰号。

Map中存储的就是key-value的键值对,Set中只存储了Key。

2. Map接口

“映射表(也称为关联数组)的基本思想是它维护的是键-值(对)关联,因此你可以使用键来查找值。标准的Java类库中包含了Map的几种基本实现,包括:HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap。它们都有同样的基本接口Map,但是行为特性各不相同,这表现在效率、键值对的保存及呈现次序、对象的保存周期、映射表如何在多线程程序中工作和判定“键”等价的策略等方面。Map接口实现的数量应该可以让你感觉到这种工具的重要性。”——《Java编程思想》

Map是一个接口类,该类没有继承自Collection,该类中存储的是<K,V>结构的键值对,并且K一定是唯一的,不能重复。

Map.Entry<K, V> 是Map内部实现的用来存放<key, value>键值对映射关系的内部类,该内部类中主要提供了<key, value>的获取,value的设置以及Key的比较方式。

在这里插入图片描述映射不是类集,但可以获得映射的类集“视图”。

  1. 为了实现这种功能,可以使用entrySet方法,它返回一个包含了映射中元素的集合(Set)。
  2. 为了得到关键字的类集“视图”,可以使用keySet()方法。
  3. 为了得到值的类集“视图”,可以使用values()方法。

类集“视图”是将映射集成到框架内的手段

1.Map 的常用方法说明

在这里插入图片描述
在这里我们是用HashMap来介绍这些方法:

1.前三个方法的简单介绍:

public class TestDemo {
    public static void main(String[] args) {
        Map<String ,Integer> map = new HashMap<>();
        map.put("abc",3);
        map.put("dog",2);
        map.put("cat",4);
        System.out.println(map);
        int ret = map.get("abc");//通过key来获取对应的value值
        System.out.println(ret);
       // int ret1 = map.get("abc1");//通过key来获取对应的value值
       // System.out.println(ret1);//没有这个元素,将会空指针异常
        System.out.println(map.get("abc1"));//这样写的话,如果没有则会给一个默认值null
      System.out.println(map.getOrDefault("abc1",100));//如果没有,则给一个默认值100
      //remove()方法
		Integer ret2 = map.remove("dog");
        System.out.println(ret2);
        System.out.println(map);
    }
}

在这里插入图片描述在这里插入图片描述
2.Set keySet()方法:返回所有 key 的不重复集合

public static void main(String[] args) {
        Map<String ,Integer> map = new HashMap<>();
        map.put("abc",3);
        map.put("dog",2);
        map.put("cat",4);
        Set<String> set = map.keySet();
        System.out.println(set);
        }

在这里插入图片描述
【注意】Set这个集合当中存储的元素都是不重复的。在存储元素的时候,如果key值相同,则value值就会被覆盖。

3.Set<Map.Entry<K, V>> entrySet():返回所有的 key-value 映射关系(重点)

把key和value看作一个整体,最后放到Set里头。

我们用图来解释一下:

在这里插入图片描述

我们可以看一下调用这个方法后的打印结果:

在这里插入图片描述
一些总结:

  1. Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap。
  2. Map中存放键值对的Key是唯一的,value是可以重复的
  3. 在Map中插入键值对时,key不能为空,否则就会抛NullPointerException异常,但是value可以为空。(注意:hashmap是两个都可以为空)
  4. Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
  5. Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
  6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。

2.TreeMap和HashMap的区别

在这里插入图片描述

3.Set(集合)

Set的最大特点就是能去重。

在这里插入图片描述Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key。

1.Set的常见方法说明

在这里插入图片描述
下面是用TreeSet来演示上面的方法:

public static void main(String[] args) {
        Set<String> s = new TreeSet<>();
        // add(key): 如果key不存在,则插入,返回ture
        // 如果key存在,返回false
        boolean isIn = s.add("apple");
        s.add("orange");
        s.add("peach");
        s.add("banana");
        System.out.println(s.size());//打印结果:4
        System.out.println(s);//打印结果:[apple, banana, orange, peach]
        isIn = s.add("apple");
        // add(key): key如果是空,抛出空指针异常
        //s.add(null);
        // contains(key): 如果key存在,返回true,否则返回false
        System.out.println(s.contains("apple"));//打印结果:true
        System.out.println(s.contains("watermelen"));//打印结果:false
       // remove(key): key存在,删除成功返回true
      // key不存在,删除失败返回false
     // key为空,抛出空指针异常
        s.remove("apple");
        System.out.println(s);//打印结果:[banana, orange, peach]
        s.remove("watermelen");
        System.out.println(s);//打印结果:[banana, orange, peach]
      // 抛出空指针异常
      // s.remove(null);
        Iterator<String> it = s.iterator();
        while(it.hasNext()){
            System.out.print(it.next() + " ");//打印结果:banana orange peach
        }
        System.out.println();

    }

【注意事项】

  1. Set是继承自Collection的一个接口类
  2. Set中只存储了key,并且要求key一定要唯一
  3. Set的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的
  4. Set最大的功能就是对集合中的元素进行去重
  5. 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础
    上维护了一个双向链表来记录元素的插入次序。
  6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
  7. Set中不能插入null的key。

2.TreeSet和HashSet的区别

在这里插入图片描述

4.本次博客的重中之重

先看三个问题:

  1. 给定10w个数据,统计每个数据出现的次数
  2. 将10w个数据去重
  3. 从10w个数据中,找出第一个重复的数据

我们先写一个随机生成的10w个数组:


public static void main(String[] args) {
        int [] array  = new int[1_0000];
        Random random = new Random();
        for (int i = 0; i < array.length; i++) {
            array[i] = random.nextInt(1000);
        }
    }

第一题:给定10w个数据,统计每个数据出现的次数

public static Map<Integer,Integer> func1(int[] array){
        //key是关键字,value是出现的次数
        Map<Integer,Integer> map = new HashMap<>();
        //判断array中的元素,是否在map当中,如果不在就是1,否则在原来的基础+1
        for (int x:array) {
            if(map.get(x) == null){
                map.put(x,1);
            }else{
                int val = map.get(x);
                map.put(x,val+1);
            }
        }
        return map;
    }

第二题:将10w个数据去重

    //将10w个数据去重
    //直接把数据放到set中
    public static Set<Integer> func2(int[] array){
        HashSet<Integer> set = new HashSet<>();
        for(int x: array){
            set.add(x);
        }
        return set;
    }

第三题:从10w个数据中,找出第一个重复的数据

//从10w个数据中,找出第一个重复的数据
    //每次把元素放到set里,放之前检查一下set中是不是已存在
    public static int func3(int[] array){
        HashSet<Integer> set = new HashSet<>();
        for(int x: array){
            if(set.contains(x)){
                return x;
            }
            set.add(x);
        }
        return -1;
    }

有了上面三个例题的支持,我们做下面题目思路就会清晰很多:

  1. 只出现一次的数字
  2. 复制带随机指针的链表
  3. 宝石与石头
  4. 旧键盘
  5. 个高频单词

这五个题,我们在下次的博客再讲解。


最后

Map和Set的内容说多不多,说少不少,要熟练掌握还是得花费一些功夫的。我也在努力学习这方面内容中,并且希望能构建出属于自己的知识框架。