一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
文章目录
一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第3天,点击查看活动详情。
动态查找的集合容器
Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。以前常见的搜索方式有:
- 直接遍历,时间复杂度为O(N),元素如果比较多效率会非常慢
- 二分查找,时间复杂度为O(logn) ,但搜索前必须要求序列是有序的
上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如: - 根据姓名查询考试成绩
- 通讯录,即根据姓名查询联系方式
- 不重复集合,即需要先搜索关键字是否已经在集合中
我们甚至可能在查找时进行一些插入和删除的操作,即动态查找,那上述两种方式就不太适合了,本节介绍的Map和Set是一种适合动态查找的集合容器
1.两种模型
一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以
模型会有两种:
- 纯 key 模型,比如:有一个英文词典,快速查找一个单词是否在词典中;快速查找某个名字在不在通讯录中。
- 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的比较方式。
映射不是类集,但可以获得映射的类集“视图”。
- 为了实现这种功能,可以使用entrySet方法,它返回一个包含了映射中元素的集合(Set)。
- 为了得到关键字的类集“视图”,可以使用keySet()方法。
- 为了得到值的类集“视图”,可以使用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里头。
我们用图来解释一下:
我们可以看一下调用这个方法后的打印结果:
一些总结:
- Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者HashMap。
- Map中存放键值对的Key是唯一的,value是可以重复的
- 在Map中插入键值对时,key不能为空,否则就会抛NullPointerException异常,但是value可以为空。(注意:hashmap是两个都可以为空)
- Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复)。
- Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复)。
- 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();
}
【注意事项】
- Set是继承自Collection的一个接口类
- Set中只存储了key,并且要求key一定要唯一
- Set的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的
- Set最大的功能就是对集合中的元素进行去重
- 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础
上维护了一个双向链表来记录元素的插入次序。 - Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入
- Set中不能插入null的key。
2.TreeSet和HashSet的区别
4.本次博客的重中之重
先看三个问题:
- 给定10w个数据,统计每个数据出现的次数
- 将10w个数据去重
- 从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;
}
有了上面三个例题的支持,我们做下面题目思路就会清晰很多:
这五个题,我们在下次的博客再讲解。
最后
Map和Set的内容说多不多,说少不少,要熟练掌握还是得花费一些功夫的。我也在努力学习这方面内容中,并且希望能构建出属于自己的知识框架。