前言
前两篇文章我们研究了二叉搜索树与哈希表的结构与特点,他们二者是Map与Set这两个接口实现的底层结构,他们利用了搜索树与哈希表查找效率高这一特点,是一种专门用来进行搜索操作的容器或数据结构。本篇文章就让我们一起来梳理这两个接口的特性与用法吧
在介绍这两个接口前先说明一下Key-value模型吧:
一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以模型会有两种:
1. 纯 key 模型,比如:
有一个英文词典,快速查找一个单词是否在词典中快速查找某个名字在不在通讯录中
2. Key-Value 模型,比如:
统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:<单词,单词出现的次数>梁山好汉的江湖绰号:每个好汉都有自己的江湖绰号
而 Map 中存储的就是 key-value 的键值对, Set 中只存储了 Key
一、Map
Map 是一个接口类,该类没有继承自 Collection ,该类中存储的是 <K,V> 结构的键值对,并且 K 一定是唯一的,不 能重复。
Map.Entry<K, V> 是 Map 内部实现的用来存放 <key, value> 键值对映射关系的内部类,该内部类中主要提供了<key, value>的获取,value的设置以及Key的比较方式。
方法 | 解释 |
K getKey() | 返回 entry 中的 key |
V getValue() | 返回 entry 中的 value |
V setValue(V value) | 将键值对中的value替换为指定value |
注意:Map.Entry<K,V> 并没有提供设置 Key 的方法
Map 的常用方法说明
注意:1. Map 是一个接口,不能直接实例化对象,如果 要实例化对象只能实例化其实现类 TreeMap 或者 HashMap2. Map 中存放键值对的 Key 是唯一的, value 是可以重复的3. 在 TreeMap 中插入键值对时, key 不能为空,否则就会抛 NullPointerException 异常,value可以为空。但是HashMap的key和value都可以为空。4. Map 中的 Key 可以全部分离出来,存储到 Set 中来进行访问(因为Key不能重复)。5. Map 中的 value 可以全部分离出来,存储在 Collection 的任何一个子集合中(value可能有重复)。6. Map中键值对的Key不能直接修改,value可以修改,如果要修改key,只能先将该key删除掉,然后再来进行重新插入。
HashMap的简单实现:
public class HashBuck2<K,V> {static class Node<K,V> {public K key;public V val;public Node<K,V> next;public Node(K key,V val) {this.key = key;this.val = val;}}public Node<K,V>[] array = (Node<K,V>[])new Node[10];//public Node<K,V>[] array = new Node<K,V>[10];public int usedSize;public double loadFactor = 0.75;public void put(K key,V val) {int hash = key.hashCode();int index = hash % array.length;Node<K,V> cur = array[index];//1. 遍历当前链表 是否存在当前值while (cur != null) {if(cur.key.equals(key)) {cur.val = val;return;}cur = cur.next;}//2. 说明 没有当前值,此时进行 头插Node<K,V> node = new Node<K,V>(key,val);node.next = array[index];array[index] = node;usedSize++;}public V get(K key) {int hash = key.hashCode();int index = hash % array.length;Node<K,V> cur = array[index];//1. 遍历当前链表 是否存在当前值while (cur != null) {if(cur.key.equals(key)) {return cur.val;}cur = cur.next;}return null;}
}
TreeMap使用示例:
import java.util.TreeMap;
import java.util.Map;
public static void TestMap(){Map<String, String> m = new TreeMap<>();
// put(key, value):插入key-value的键值对
// 如果key不存在,会将key-value的键值对插入到map中,返回nullm.put("林冲", "豹子头");m.put("鲁智深", "花和尚");m.put("武松", "行者");m.put("宋江", "及时雨");String str = m.put("李逵", "黑旋风");System.out.println(m.size());System.out.println(m);
// put(key,value): 注意key不能为空,但是value可以为空
// key如果为空,会抛出空指针异常
//m.put(null, "花名");str = m.put("无名", null);System.out.println(m.size());
// put(key, value):
// 如果key存在,会使用value替换原来key所对应的value,返回旧valuestr = m.put("李逵", "铁牛");
// get(key): 返回key所对应的value
// 如果key存在,返回key所对应的value
// 如果key不存在,返回nullSystem.out.println(m.get("鲁智深"));System.out.println(m.get("史进"));
//GetOrDefault(): 如果key存在,返回与key所对应的value,如果key不存在,返回一个默认值System.out.println(m.getOrDefault("李逵", "铁牛"));System.out.println(m.getOrDefault("史进", "九纹龙"));System.out.println(m.size());
//containKey(key):检测key是否包含在Map中,时间复杂度:O(logN)
// 按照红黑树的性质来进行查找
// 找到返回true,否则返回falseSystem.out.println(m.containsKey("林冲"));System.out.println(m.containsKey("史进"));
// containValue(value): 检测value是否包含在Map中,时间复杂度: O(N)
// 找到返回true,否则返回falseSystem.out.println(m.containsValue("豹子头"));System.out.println(m.containsValue("九纹龙"));
// 打印所有的key
// keySet是将map中的key防止在Set中返回的for(String s : m.keySet()){System.out.print(s + " ");}System.out.println();
// 打印所有的value
// values()是将map中的value放在collect的一个集合中返回的for(String s : m.values()){System.out.print(s + " ");}System.out.println();
// 打印所有的键值对
// entrySet(): 将Map中的键值对放在Set中返回了for(Map.Entry<String, String> entry : m.entrySet()){System.out.println(entry.getKey() + "--->" + entry.getValue());}System.out.println();
}
二、Set
Set与Map主要的不同有两点:Set是继承自Collection的接口类,Set中只存储了Key。
常见方法说明
注意:1. Set是继承自Collection的一个接口类2. Set中只存储了key,并且要求key一定要唯一3. TreeSet的底层是使用Map来实现的,其使用key与Object的一个默认对象作为键值对插入到Map中的4. Set最大的功能就是对集合中的元素进行去重5. 实现Set接口的常用类有TreeSet和HashSet,还有一个LinkedHashSet,LinkedHashSet是在HashSet的基础上维护了一个双向链表来记录元素的插入次序。6. Set中的Key不能修改,如果要修改,先将原来的删除掉,然后再重新插入7. TreeSet中不能插入null的key,HashSet可以。
TreeSet使用示例:
import java.util.TreeSet;
import java.util.Iterator;
import java.util.Set;
public static void TestSet(){Set<String> s = new TreeSet<>();// add(key): 如果key不存在,则插入,返回ture// 如果key存在,返回falseboolean isIn = s.add("apple");s.add("orange");s.add("peach");s.add("banana");System.out.println(s.size());System.out.println(s);isIn = s.add("apple");// add(key): key如果是空,抛出空指针异常//s.add(null);// contains(key): 如果key存在,返回true,否则返回falseSystem.out.println(s.contains("apple"));System.out.println(s.contains("watermelen"));// remove(key): key存在,删除成功返回true// key不存在,删除失败返回false// key为空,抛出空指针异常s.remove("apple");System.out.println(s);s.remove("watermelen");System.out.println(s);Iterator<String> it = s.iterator();while(it.hasNext()){System.out.print(it.next() + " ");}System.out.println();
}
总结
Map与Set这两个接口由于出色的查找效率,为之后的一些算法题中在优化时间上发挥着重要的作用,不过由于创建需要开辟大量空间,是典型的空间换时间,在实际应用中应该根据需要选用。还是哪句话,没有最好的数据结构,只有最适合的数据结构。
那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。作者还是一个萌新,如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊