一. Map和Set的概念
1.什么是Map和Set ?
Map和set是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。
Map和Set在Java标准库中的四种对应的类:TreeMap,TreeSet和HashMap,HashSet,其在实现过程中具体体现为 二叉搜索树 和 哈希表 !
我们可以回顾一下我们已经掌握的常见的搜索方式有:
- 直接遍历,时间复杂度为O(N),元素如果比较多效率会非常慢;
- 二分查找,时间复杂度为,但搜索前必须要求序列是有序的。
上述排序比较适合静态类型的查找,即一般不会对区间进行插入和删除操作了,而现实中的查找比如:
- 根据姓名查询考试成绩;
- 通讯录,即根据姓名查询联系方式;
- 不重复集合,即需要先搜索关键字是否已经在集合中。
而且我们可能在查找时需要进行一些插入和删除的操作,即实现动态查找,那上述两种方式就不太适合了,本文介绍的Map和Set是一种适合动态查找的集合容器。
2.模型定义
一般把搜索的数据称为关键字(Key),关键字对应的称为值(Value),我们将其称之为Key-value的键值对,所以模型会有两种:
- 纯 key 模型,比如:
有一个英文词典,快速查找一个单词是否在词典中;
快速查找某个名字在不在通讯录中。 - Key-Value 模型,比如:
统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:<单词,单词出
现的次数>;
梁山好汉的江湖绰号:每个好汉都有自己的江湖绰号。
而Map中存储的就是key-value的键值对,Set中只存储了Key。这里我们需要注意的是:
- 关键字Key是不允许出现重复的;
- 键值对key-value是根据key来找value,知道key值可以确定唯一的value。
所以在选择使用Map和Set哪种数据结构时,如果我们只是需要判断key是否存在,使用Set数据结构即可;如果是需要根据key的值来查找value的内容,就需要使用Map数据结构!
二.Map和Set的使用
Map 和 Set 的用法大同小异,主要的差别是这两种结构的底层实现是完全不同的!
Map的使用
1.关于Map的说明
Map是一个接口类,该类没有继承自Collection,该类中存储的是<K,V>结构的键值对,并且K一定是唯一的,不能重复。
2.关于Map.Entry<K, V>的说明
Map.Entry<K, V> 是Map内部实现的用来存放<key, value>键值对映射关系的内部类,该内部类中主要提供了<key, value>的获取,value的设置以及Key的比较方式。
注意:Map.Entry<K,V>并没有提供设置Key的方法。3.Map的常用方法说明
注意:
- Map是一个接口,不能直接实例化对象,如果要实例化对象只能实例化其实现类TreeMap或者
HashMap; - Map中存放键值对的Key是唯一的,value是可以重复的;
- 在Map中插入键值对时,key不能为空,否则就会抛NullPointerException异常,但是value可以
为空; - Map中的Key可以全部分离出来,存储到Set中来进行访问(因为Key不能重复);
- Map中的value可以全部分离出来,存储在Collection的任何一个子集合中(value可能有重复);
- Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然
后再来进行重新插入; - TreeMap和HashMap的区别:
代码演示:
public class testMap {
public static void main(String[] args) {
Map<String ,String> map = new HashMap<>();
// 1. 使用 put 方法插入键值对
map.put("及时雨", "宋江");
map.put("黑旋风", "李逵");
map.put("行者", "武松");
map.put("及时雨", "宋公明");
System.out.println(map);
}
}
运行结果:
根据运行结果能发现:
- Map 内部的元素之间的先后顺序和插入顺序关系不大~
- 当 put 的时候发现 key 已经存在, 此时就会覆盖原有的 value~
public class testMap {
public static void main(String[] args) {
Map<String ,String> map = new HashMap<>();
// 1. 使用 put 方法插入键值对
map.put("及时雨", "宋江");
map.put("黑旋风", "李逵");
map.put("行者", "武松");
map.put("及时雨", "宋公明");
System.out.println(map);
//2.使用get方法,根据key值获取value
String value1 = map.get("行者");
String value2 = map.get("小李广");
System.out.println(value1 +" "+ value2);
}
}
运行结果:
根据运行结果能发现:
- 如果key值不存在,get方法返回的是null。
public class testMap {
public static void main(String[] args) {
Map<String ,String> map = new HashMap<>();
// 1. 使用 put 方法插入键值对
map.put("及时雨", "宋江");
map.put("黑旋风", "李逵");
map.put("行者", "武松");
map.put("及时雨", "宋公明");
map.put("小李广", "花荣本荣");
System.out.println(map);
//3.使用getOrDefault方法,根据key值获取value,如果key不存在,返回默认值
String value3 = map.getOrDefault("小李广","花荣(默认值)");
String value4 = map.getOrDefault("豹子头"," 林冲(默认值)");
System.out.println(value3+" "+value4);
}
}
运行结果:
根据运行结果能发现:
- 我们还可以使用 getOrDefault 来根据 key 获取 value,如果 key 不存在, getOrDefault
返回一个默认值。
public class testMap {
public static void main(String[] args) {
Map<String ,String> map = new HashMap<>();
// 1. 使用 put 方法插入键值对
map.put("及时雨", "宋江");
map.put("黑旋风", "李逵");
map.put("行者", "武松");
map.put("及时雨", "宋公明");
map.put("小李广", "花荣本荣");
System.out.println(map);
// 4. 使用 isEmpty 判定空
System.out.println("判断Map是否为空?:"+map.isEmpty());
// 5. 使用 size 获取到键值对的个数
System.out.println("获取Map的大小:"+map.size());
// 6. 使用 clear 清空所有的键值对
map.clear();
System.out.println("使用clear()后判断Map是否为空?:"+map.isEmpty());
System.out.println("使用clear()后获取Map的大小:"+map.size());
}
}
运行结果:
根据运行结果能发现:
- 使用 size ()函数,获取到的Map的大小是键值对的个数!
map.entrySet( ): 这个函数的作用是,把Map这样的键值对结构进行了转换,转换成了一个Set,Set里面的每一个元素都是一个Entey(相当于Set的key),每一个Entry里面又包含了key和value~~
public class testMap {
public static void main(String[] args) {
Map<String ,String> map = new HashMap<>();
// 1. 使用 put 方法插入键值对
map.put("及时雨", "宋江");
map.put("黑旋风", "李逵");
map.put("行者", "武松");
map.put("及时雨", "宋公明");
map.put("小李广", "花荣本荣");
System.out.println(map);
//7. 遍历 Map (Map 设计出来不是为了遍历!!)
// 遍历 Map 是比较复杂的, 需要把 Map 转换成 Set 再遍历
System.out.println("遍历Map,将键值对打印输出:");
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
//8. 还可以单独的获取到所有的 key 和所有的 value
System.out.println("打印输出所有的key:");
for (String key : map.keySet()) {
System.out.print(key+" ");
}
System.out.println("\n"+"打印输出所有的value:");
for (String value : map.values()) {
System.out.print(value+" ");
}
}
}
运行结果:
注意:
- keySet( ) 方法是把所有的key值单独存储在Set结构中,而values( )方法是把所有的value值放到Collection结构中,这么,这么做的原因是:key值是不允许重复的,value值是可以重复的。
- 需要强调的是entrySet( ),keySet( ),values( )方法是非常低效的方法,我们谨慎使用,尤其是在Map本身元素非常多的情况下~~
Set的使用
1.关于Set的说明
Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key。
2.Set的常用方法说明
注意:
- Set是继承自Collection的一个接口类;
- Set中只存储了key,并且要求key一定要唯一;
- Set的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的;
- Set最大的功能就是对集合中的元素进行去重;
- 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在
HashSet的基础上维护了一个双向链表来记录元素的插入次序; - Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入;
- Set中不能插入值为null 的key。;
- TreeSet和HashSet的区别:
代码演示
public class testSet {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
// 1. 使用 add 插入元素
set.add("C");
set.add("C++");
set.add("C++");
set.add("Java");
set.add("Python");
System.out.println(set);
//2. 使用 contains 方法判定元素是否存在 [重要]
boolean ret1 = set.contains("java");
boolean ret2 = set.contains("Java");
System.out.println("判断是否包含java:"+ret1);
System.out.println("判断是否包含Java:"+ret1);
// 3. 使用 remove 删除元素
set.remove("Java");
System.out.println("删除Java后的Set:"+set);
// 4. 使用 isEmpty 来判定空
System.out.println("判断Set是否为空:"+set.isEmpty());
// 5. 使用 size 获取元素个数
System.out.println("获取Set中元素个数:"+set.size());
// 6. 使用 clear 清空元素
set.clear();
System.out.println("使用clear()后判断是否为空:"+set.isEmpty());
System.out.println("使用clear()后获取Set中元素个数:"+set.size());
}
}
运行结果:
根据运行结果能发现:
- 如果插入重复的 key, 实际只保存一份,借助 Set 的这个特性, 就可以用于 “去重”。
- Set结构中的元素可以直接增删查,但是没办法直接修改。
public class testSet {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
// 1. 使用 add 插入元素
set.add("C");
set.add("C++");
set.add("C++");
set.add("Java");
set.add("Python");
System.out.println(set);
// 7. 遍历
// a) 直接使用 for-each 遍历 [语法糖]
System.out.println("使用for-each循环遍历:");
for (String key : set) {
System.out.print(key+" ");
}
// b) 使用迭代器来遍历
Iterator<String> it = set.iterator();
System.out.println("\n"+"使用迭代器循环遍历:");
while (it.hasNext()) {
System.out.print(it.next()+" ");
}
}
}
运行结果:
根据运行结果能发现:
- 如果这个类能支持迭代器就可以 for-each。
至此,Map和Set的入门知识就已经写完了,期待一下接下来的搜索树和哈希表吧!!!