引言
Map 和 Set 是一种专门用来进行搜索的容器或者数据结构,其搜索的效率与其具体的实例化子类有关。
一、Map 接口
Map是一个接口,该接口并没有继承(拓展)Collection接口。Map 接口中存储的是<K,V>结构的键值对,将键 Key 映射到值 Value 的对象。 此外,Map 中不能包含重复的键,每个键可以映射到最多一个值。
Map<K,V> //Key ——键 //Value ——值
1. Map 的常用方法
2. 使用 Map 时的说明
① Map 是一个接口,所以不能直接通过 new 来实例化对象,如果要实例化对象,那么只能实例化其实现类 TreeMap 或者 HashMap.
② Map 中存放键值对的 Key 是唯一的,Value 是可以重复的。而重复的 Key 对应的 Value 会在底层被更新。
③ Map中的 Key 可以全部分离出来,存储到Set中来进行访问(因为 Key 不能重复)。
④ Map 中的 Value 可以全部分离出来,存储在 Collection 的任何一个子集合中(Value 可能有重复)。
⑤ Map中键值对的 Key 不能直接修改,Value 可以修改,如果要修改 Key ,只能先将该 Key 删除掉,然后再来进行重新插入。
⑥ TreeMap 和 HashMap的区别:
3. Java 中 Map 的使用
/** * Map 接口 */ import java.util.HashMap; import java.util.Map; public class Test1 { public static void main(String[] args) { //Map<K,V> key,value Map<String, Integer> map = new HashMap<>(); map.put("hello", 3); map.put("world",4); map.put("nice",5); map.put("nice",666);//重复的 Key 对应的 Val 会更新 map.put(null, null); System.out.println(map); int ret = map.get("world");//获得当前 Key 在 map 中的次数 System.out.println(map.get("world")); System.out.println(map.get("world2")); System.out.println(map.getOrDefault("world3",-1)); map.remove("world"); System.out.println(map); } }
输出结果:
4. 注意
在上面我们使用 map.put( ) 方法的时候,我们会发现一个问题,我们 put 进去 map 的数据顺序是 [hello, world, nice, null ],但是输出的却是 [ null, world, hello, nice ],可以发现,我们从输出数据的顺序上来看,这很糟糕。
但是我在引言中提到,Map 是一种专门用来进行搜索的容器或者数据结构,其核心在于查找,并不是排序。也就是说,我们在使用 Map 和 Set 时,更多地是思考用来查找、统计数据,而对数据的顺序并不关心。
/** * Map 接口 */ import java.util.HashMap; import java.util.Map; import java.util.Set; public class Test2 { public static void main(String[] args) { Map<String, Integer> map = new HashMap<>(); map.put("hello", 3); map.put("world",4); map.put("nice",5); //此时 Set 集合中放的数据类型是: String //Set 这个集合中,存储的元素都是不重复的 Set<String> set = map.keySet(); System.out.println(set); //Set 集合中放的数据类型是: Map.Entry<String, Integer> //所以此时 Set 集合中放的就是 String 和 Integer 的集合体 Set<Map.Entry<String, Integer>> entrySet = map.entrySet(); for (Map.Entry<String, Integer> s : entrySet) { System.out.println(s.getKey()+" -> "+ s.getValue() ); } } }
输出结果:
二、Set 接口
Set 接口与 Map 接口主要有两点不同:
① Set 接口是继承了 (拓展) Collection 的接口
② Set 中只存储了 Key
Set<K> //K —— 键
1. Set 的常用方法
2. 使用 Set 时的说明
① Set 接口是继承了 (拓展) Collection 的接口。
② Set 中只存储了 Key ,并且要求 Key 一定要唯一。
③ Set 的底层是使用 Map 来实现的,其使用 Key 与 Object 的一个默认对象作为键值对插入到 Map 中的。
④ Set 最大的功能就是对集合中的元素进行去重。
⑤ 实现 Set 接口的常用类有 TreeSet 和 HashSet,还有一个 LinkedHashSet,LinkedHashSet 是在 HashSet 的基础上维护了一个双向链表来记录元素的插入次序。
⑥ Set 中的 Key 不能修改,如果要修改,先将原来的删除掉,然后再重新插入。
⑦ Set 中不能插入 null 的 Key 。
⑧ TreeMap 和 HashMap 的区别:
3. Java 中 Set 的使用
import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Test2 { public static void main(String[] args) { Set<String> set = new HashSet<>(); set.add("hello"); set.add("world"); set.add("nice"); set.add("nice"); System.out.println(set); Iterator<String> iterator = set.iterator(); while (iterator.hasNext()){ System.out.println(iterator.next()); } } }
输出结果:
4. 注意
在上面我们使用 set.add( ) 方法的时候,我们会发现,当添加重复的元素时,集合会自动帮我们去除重复的元素。此外,我们输出的元素会和 Map 一样,并不是按我们输入的顺序排序好的。因为我在引言中提到,同样地,Set 也只是一个用于搜索、查找的数据结构。
三、一些有关 Map 和 Set 的题目
1. 统计一组数据中的每个数据出现了多少次
/** * 统计一组数据中的每个数据出现了多少次 */ import java.util.HashMap; import java.util.Map; public class Test { public static void main(String[] args) { char[] arr = {'a','c','e','c','b','d','f','c','d'}; Map<Character,Integer> map = computer(arr); System.out.println(map); } public static Map<Character,Integer> computer(char[] arr){ Map<Character,Integer> map = new HashMap<>(); //如果在 map 中没有数组对应的 Key,那么就添加 1个 Key 进去 //如果在 map 中包含了和数组对应的 Key,且 Key 对应 count个 Val, //那么就添加 count + 1个 Key 进去 for (char x :arr) { if(map.containsKey(x)){ int count = map.get(x); map.put(x, count+1); }else { map.put(x,1); } } return map; } }
输出结果:
2. 将一组数据中多余的数据去重
/** * 将一组数据中多余的数据去重 */ import java.util.HashSet; import java.util.Set; public class Test { public static void main(String[] args) { int[] arr = {1,3,5,7,9,3,2,4,6,8,5,4,6}; Set<Integer> set = new HashSet<>(); for (int x : arr) { set.add(x); } System.out.println(set); } }
输出结果:
3. 找出第一个重复出现的数据
/** * 在一组数据中,找出第一个重复出现的数据 */ import java.util.HashSet; import java.util.Set; public class Test6 { public static void main(String[] args) { int[] arr = {1,3,5,7,9,3,2,4,6,8,5,4,6}; Set<Integer> set = new HashSet<>(); int i = 0; //如果在添加元素到 set 之前,就已经发现了 set 中包含某个数组的元素, //那么就 break for (i = 0; i < arr.length; i++) { if(set.contains(arr[i])){ break; } set.add(arr[i]); } System.out.println(arr[i]); //拿到的就是第一次出现的重复值 } }
4. 只出现一次的数字
方法一:
/** * 利用两两元素之间异或,最终得出的就是单独元素 */ class Solution { public int singleNumber(int[] nums) { int x = nums[0]; for(int i= 1; i<nums.length; i++){ x = x^nums[i]; } return x; } }
方法二:
/** * 利用集合 Set * 如果集合中有相同元素,就移除,反之,就添加 */ class Solution { public int singleNumber(int[] nums) { Set<Integer> set = new HashSet<>(); for(int i=0; i<nums.length; i++){ if(!set.contains(nums[i])){ set.add(nums[i]); }else{ set.remove(nums[i]); } } for(int i=0; i<nums.length; i++){ if(set.contains(nums[i])){ return nums[i]; } } return -1; } }
5. 复制带随机指针的链表
本题需要详细掌握 Map 的操作,与其对应的映射关系,才能将本题理解到位。
/** * 通过 Map 的映射关系 * 知道了 Key 的状态,实现 Value 的状态 * 即通过旧节点之间的关系,实现新节点之间的关系 */ class Solution { public Node copyRandomList(Node head) { if(head == null) return null; //Key - 旧节点,Value - 新节点 Map<Node,Node> map = new HashMap<>(); Node cur = head; //1. 创建新的节点 while(cur != null){ map.put(cur, new Node(cur.val)); cur = cur.next; } cur = head; //2. 连接各个节点 while(cur != null){ map.get(cur).next = map.get(cur.next); map.get(cur).random = map.get(cur.random); cur = cur.next; } return map.get(head); } }
6. 宝石与石头
方法一
/** * 方法一 * 1. 将 jewels 字符串中的字符放入集合 set 中 * 2. 查看集合 set 中是否包含 stones 字符数组中字符 * 3. 用 count 计数,最后返回 */ class Solution { public int numJewelsInStones(String jewels, String stones) { Set<Character> set = new HashSet<>(); for(char x:jewels.toCharArray()){ //1. set.add(x); } int count = 0; for(char j:stones.toCharArray()){ //2. if(set.contains(j)){ count++; } } return count; //3. } }
方法二
/** * 方法二 * 两层 for 循环 */ class Solution { public int numJewelsInStones(String jewels, String stones) { int count = 0; for(int i=0; i<jewels.length(); i++){ for(int j=0; j<stones.length();j++){ if( jewels.charAt(i) == stones.charAt(j) ){ count++; } } } return count; } }
7. 键盘坏了的键
import java.util.ArrayList; import java.util.HashSet; import java.util.Scanner; import java.util.Set; /** * 1. 将实际输入的文字 str2 放在集合 set 中 * 2. for 循环遍历应该输入的文字 str1, * 若集合 set 不包含某个字符,且线性表之前也未出现该字符,就输出打印 * 3. 将步骤 2 中不包含的字符放入顺序表中,以便下一次判断 */ public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); String str1 = scanner.nextLine(); //应该输入的文字 String str2 = scanner.nextLine(); //实际输入的文字 out(str1,str2); } public static void out(String str1, String str2){ Set<Character> set = new HashSet<>(); ArrayList<Character> list = new ArrayList<>(); for (char x:str2.toUpperCase().toCharArray()) { set.add(x); //1. } for(char x:str1.toUpperCase().toCharArray()){ //2. if(!set.contains(x) && !list.contains(x)){ System.out.print(x); } list.add(x); //3. } } }