【数据结构】Map与Set

前言

    前两篇文章我们研究了二叉搜索树与哈希表的结构与特点,他们二者是Map与Set这两个接口实现的底层结构,他们利用了搜索树与哈希表查找效率高这一特点,是一种专门用来进行搜索操作的容器或数据结构。本篇文章就让我们一起来梳理这两个接口的特性与用法吧

58d60bbf89ce4be698240c1474f4da73.png

在介绍这两个接口前先说明一下Key-value模型吧:

    一般把搜索的数据称为关键字(Key),和关键字对应的称为值(Value),将其称之为Key-value的键值对,所以模型会有两种:
1. key 模型,比如:
有一个英文词典,快速查找一个单词是否在词典中快速查找某个名字在不在通讯录中
2. Key-Value 模型,比如:
统计文件中每个单词出现的次数,统计结果是每个单词都有与其对应的次数:<单词,单词出现的次数>梁山好汉的江湖绰号:每个好汉都有自己的江湖绰号
Map 中存储的就是 key-value 的键值对, Set 中只存储了 Key

一、Map

3784d2b8c38f434db7f6ce60bc674457.png

Map 是一个接口类,该类没有继承自 Collection ,该类中存储的是 <K,V> 结构的键值对,并且 K 一定是唯一的,不 能重复
Map.Entry<K, V> 是 Map 内部实现的用来存放 <key, value> 键值对映射关系的内部类,该内部类中主要提供了<key, value>的获取,value的设置以及Key的比较方式。
注意:Map.Entry<K,V>并没有提供设置Key的方法
Map 的常用方法说明
aad74e44e2cc4ccca5b53e182933d846.png
注意:
1. Map 是一个接口,不能直接实例化对象,如果 要实例化对象只能实例化其实现类 TreeMap 或者 HashMap
2. 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删除掉,然后再来进行重新插入。
8446713306834a369d27a58cf936962a.png
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中,返回null
    m.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,返回旧value
    str = m.put("李逵", "铁牛");
// get(key): 返回key所对应的value
// 如果key存在,返回key所对应的value
// 如果key不存在,返回null
    System.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,否则返回false
    System.out.println(m.containsKey("林冲"));
    System.out.println(m.containsKey("史进"));
// containValue(value): 检测value是否包含在Map中,时间复杂度: O(N)
// 找到返回true,否则返回false
    System.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。
常见方法说明
36806fd5bfea4a3682513e02124fbb43.png
注意:
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可以。
7c56d054196046e2b5c58f840606d75b.png
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存在,返回false
    boolean 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,否则返回false
    System.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这两个接口由于出色的查找效率,为之后的一些算法题中在优化时间上发挥着重要的作用,不过由于创建需要开辟大量空间,是典型的空间换时间,在实际应用中应该根据需要选用。还是哪句话,没有最好的数据结构,只有最适合的数据结构。


那么本篇文章就到此为止了,如果觉得这篇文章对你有帮助的话,可以点一下关注和点赞来支持作者哦。作者还是一个萌新,如果有什么讲的不对的地方欢迎在评论区指出,希望能够和你们一起进步✊

08403404d82e4ca19e76b10b45ca5f28.png

  • 39
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值