前提:Map和Set的底层是一棵红黑树,红黑树的本质是一颗二叉搜索树.
概念及场景
Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。
1.直接遍历,时间复杂度为O(N),元素如果比较多效率会非常慢
2.二分查找,时间复杂度为O(log2N) ,但搜索前必须要求序列是有序的
上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如:
1.根据姓名查询考试成绩
2.通讯录,即根据姓名查询联系方式
3.不重复集合,即需要先搜索关键字是否已经在集合中
可能在查找时进行一些插入和删除的操作,即动态查找,那上述两种方式就不太适合了,本节介绍的Map和Set是一种适合动态查找的集合容器。
模型
一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以模型会有两种:
1.纯 key 模型,比如:
有一个英文词典,快速查找一个单词是否在词典中快速查找某个名字在不在通讯录中
2.Key-Value 模型,比如:
统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:<单词,单词出现的次数> 梁山好汉的江湖绰号:每个好汉都有自己的江湖绰号
而Map中存储的就是key-value的键值对,Set中只存储了Key。
Map的使用
关于Map的说明
Map是一个接口类,该类没有继承自Collection,该类中存储的是结构的键值对,并且K一定是唯一的,不 能重复。
关于Map.Entry
Map.Entry 是Map内部实现的用来存放键值对映射关系的内部类,该内部类中主要提供了
的获取,value的设置以及Key的比较方式。
注意:Map.Entry并没有提供设置Key的方法
Map的常用方法说明
注意:
1.Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap
2.Map中存放键值对的Key是唯一的,value是可以重复的
3.在Map中插入键值对时,key可以为空,value也可以为空,也可以不为空.
4.Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
5.Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
6.Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。
7.TreeMap和HashMap的区别
代码示例
public class MapDemo { public static void main(String[] args) { Map<String, Object> map = new HashMap<>(); //put的书写顺序不能决定打印的顺序 //put是根据一个函数存放的,叫做哈希函数 map.put("2", 4); map.put("3", 5); map.put("4", 6); //输出结果为{2=4, 3=5, 4=6} System.out.println(map); Map<String, Object> map1 = new HashMap<>(); map1.put("sb", 2); map1.put("sb", 3); //如果key存在,会使用value替换原来key所对应的value,返回新value //结果为{sb=3} System.out.println(map1); Map<String, Object> map2 = new HashMap<>(); map2.put("sb", 4); // get(key): 返回key所对应的value // 如果key存在,返回key所对应的value // 如果key不存在,返回null // 结果为4 System.out.println(map2.get("sb")); Map<String, Object> map3 = new HashMap<>(); //如果key不存在,返回一个默认值 int a = (int) map3.getOrDefault("sb", 9); //结果为9 System.out.println(a); Map<String, Object> map4 = new HashMap<>(); map4.put("2", 4); map4.put("3", 5); map4.put("4", 6); //set集合不能存放相同元素,keySet方法只获取key值,返回所有 key 的不重复集合 Set<String> set = map4.keySet(); //结果为[2, 3, 4] System.out.println(set); Map<String, Integer> map5 = new HashMap<>(); map5.put("2", 4); map5.put("3", 5); map5.put("4", 6); //entrySet是为了返回所有的 key-value 映射关系 Set<Map.Entry<String, Integer>> set1 = map5.entrySet(); /* 输出结果为 4 2 5 3 6 4 */ // 打印所有的键值对 for (Map.Entry<String, Integer> entry : set1) { System.out.println(entry.getValue() + " " + entry.getKey()); } Map<String, Integer> map6 = new HashMap<>(); map6.put("2", 4); map6.put("3", 5); map6.put("4", 6); //检测key是否包含在Map中,时间复杂度:O(logN) // 按照红黑树的性质来进行查找 // 找到返回true,否则返回false boolean b = map6.containsKey("2"); //结果为true System.out.println(b); Map<String, Integer> map7 = new HashMap<>(); map7.put("2", 4); map7.put("3", 5); map7.put("4", 6); // containValue(value): 检测value是否包含在Map中,时间复杂度: O(N) // 因为TreeMap是按照Key进行组织的,因此查找value时候就需要整体遍历 // 找到返回true,否则返回false boolean c = map6.containsValue(4); //结果为true System.out.println(c); } }
Set的说明
Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key,并且Set中不能存储相同的元素。所以我们经常会使用Set来进行去重.
常见方法说明
注意:
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.TreeSet和HashSet的区别
注意:时间复杂度为O(1)是因为Set的底层是Map,而HashSet的底层是HashMap,HashMap的时间复杂度为O(1).则HashSet的时间复杂度为O(1)
8.set的add方法中,当数字的范围大于65535后,数字的有序性就不能保证了,也就是说set中的add方法其实插入小于65535的数字的时候是有序的,在插入大于65535的数字后就变成无序数字了.并且需要注意的是,Set集合中所有插入的数字都是非重复的,即Set集合中不可能存储重复的数字.
代码示例
在这里我就说下Set中的元素转为数组返回的问题,也就是toArray方法:注意这里不能使用强转,写法按照下面代码的写法才可以发生转化.
public class MapDemo1 { public static void main(String[] args) { Set<Integer> set = new HashSet<>(); set.add(1); set.add(2); set.add(3); set.add(4); set.add(5); Integer[] a= set.toArray(new Integer[5]); for (int i = 0; i < a.length; i++) { System.out.print(a[i]+" "); } } }
面试题目
题目一
1的代码:
public static int findFirstReapeatNumber() { Random random = new Random(); ArrayList<Integer> list = new ArrayList<>(); for (int i = 0; i < 10_0000; i++) { list.add(random.nextInt(1_0000)); } HashSet<Integer> set = new HashSet<>(); for (int i = 0; i < list.size(); i++) { if (!set.contains(list.get(i))) { set.add(list.get(i)); } else { return list.get(i); } } return -1; }
2的代码:
因为要把十万个数据中的元素全部去重,就需要使用到Set集合,它就是自动去重的.
public static Set<Integer> deputil() { Random random = new Random(); ArrayList<Integer> list = new ArrayList<>(); for (int i = 0; i < 10_0000; i++) { list.add(random.nextInt(1_0000)); } HashSet<Integer> set = new HashSet<>(); for (int i = 0; i < list.size(); i++) { set.add(list.get(i)); } return set; }
3的代码:
public class MapDemo1 { public static void func() { Random random = new Random(); ArrayList<Integer> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(random.nextInt(10)); } System.out.println(list); HashMap<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < list.size(); i++) { Integer key = list.get(i); if (map.get(key) == null) { map.put(key, 1); } else { Integer value = map.get(key); map.put(key, value + 1); } } for (Map.Entry<Integer, Integer> map1 : map.entrySet()) { if (map1.getValue() > 1) { System.out.println(map1.getKey() + "=" + map1.getValue()); } } } public static void main(String[] args) { func(); } }
题目二
只出现一次的数字
题目三
复制带随机指针的链表
题目四
宝石与石头
题目五
旧键盘
题目六
前K个高频单词