目录
前提: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,V>结构的键值对,并且K一定是唯一的,不 能重复。
关于Map.Entry
Map.Entry<K, V> 是Map内部实现的用来存放<key, value>键值对映射关系的内部类,该内部类中主要提供了
<key, value>的获取,value的设置以及Key的比较方式。
注意:Map.Entry<K,V>并没有提供设置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个高频单词