Map接口详解


Map

Map(映射)是一种用于存储键值对的数据结构。在 Java 中,Map 是一个接口,它定义了一组方法来操作和处理键值对。

Map 接口的常见实现类包括 HashMapTreeMapLinkedHashMap 等。

1.以下是一些常用的 Map 接口的方法:

1.put(key, value): 将指定的键值对存储到 Map 中。

2.get(key): 根据键获取对应的值。

3.containsKey(key): 判断 Map 中是否包含指定的键。

4.containsValue(value): 判断 Map 中是否包含指定的值。

5.remove(key): 根据键移除对应的键值对。

6.size(): 返回 Map 中键值对的数量。

7.keySet(): 返回包含所有键的集合。

8.values(): 返回包含所有值的集合。

9.entrySet(): 返回包含所有键值对的集合。

2.Map 的特点包括:

  • 键不允许重复,每个键只能对应一个值。
  • 可以通过键快速查找值,具有高效的查找能力。
  • 键和值可以是任意的对象,但在使用时需要正确实现 hashCode() 和 equals() 方法以确保准确的存取。
  • 不保证元素的顺序,具体的顺序取决于具体的实现类。

使用 Map 可以方便地根据键来获取对应的值,常用于存储和检索映射关系的场景,如缓存、配置信息等。

3.HashMap

  1. 允许使用null键和null值,与HashSet一样,不保证映射的顺序。
  2. 所有的key构成的集合是Set:无序的、不可重复的。所以, key所在的类要重写:equals()和hashCode()
  3. 所有的value构成的集合是Collection:无序的、可以重复的。所以, value所在的类要重写: equals()
  4. 一个key-value构成一个entry
  5. 所有的entry构成的集合是Set:无序的、不可重复的
  6. HashMap 判断两个key 相等的标准是:两个 key 通过 equals() 方法返回 true,hashCode 值也相等。
  7. HashMap判断两个 value相等的标准是:两个 value 通过 equals() 方法返回 true。

3.1 JDK7以前的存储结构

请添加图片描述

JDK1.7前哈希桶里不会自动形成红黑树,如果出现哈希冲突,就会一直增加链表长度 。这样就会导致一个链表太长 遍历时间就会变长

3.2 JDK 8 存储结构

请添加图片描述

  • DK1.8HashMap新变化
    • HashMap map = new HashMap();//默认情况下,先不创建长度为16的数组
    • 当首次调用map.put()时,再创建长度为16的数组
    • 数组为Node类型,在jdk7中称为Entry类型
    • 形成链表结构时,新添加的key-value对在链表的尾部(七上八下)
    • 当数组指定索引位置的链表长度>8时,且map中的数组的长度> 64时,此索引位置上的所有key-value对使用红黑树进行存储。

源码中三个重要方法

  • resize:扩容
  • treeifyBin:树化
  • untreeify:链化

3.3 HashMap中添加的元素分配哈希桶中的过程

1.添加的元素先通过hashcode获得一个哈希值
2.用add往里面添加的时候,会通过扰动函数给这个元素赋予一个新的hashcode,这样可以降低哈希码冲突
3.通过新的hashcode哈希桶数量(bucketCount)取余(%)来确定这个元素的哈希桶位置
4.将元素存储到对应的哈希桶里

3.4 HashMap的容量为什么必须是2的n次幂

请添加图片描述
HashMap使用n-1 & hash得到槽位地址,这个运算n必须是2的n次幂,结论当容量是2的n次幂的时候(16,32…)
hash % n = (n-1) & hash,因为位运算性能高

如果初始容量不是2的n次幂,HashMap调用tableSizeFor自动转换成大于这个数最小的2的n次幂

4. LinkedHashMap

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

LinkedHashMap 是 Java 中的一种特殊类型的哈希表,它继承自 HashMap,同时保持了插入顺序。具体来说,LinkedHashMap 使用一个双向链表来维护键值对的顺序,这样可以保证迭代遍历时的顺序与插入顺序一致。

与普通的 HashMap 不同,LinkedHashMap 提供了以下特性:

  1. 迭代顺序:迭代 LinkedHashMap 时,元素的顺序与插入顺序相同。
  2. 访问顺序:可以通过构造函数或设置 accessOrder 参数为 true,使得 LinkedHashMap按照访问顺序进行排序。每次访问一个键值对,无论是读取还是写入操作,都会将该键值对移动到链表末尾,使其成为最近访问的元素。
  3. 有序性:LinkedHashMap 可以按照插入顺序或访问顺序排序,从而保证键值对的顺序性。

请添加图片描述

请添加图片描述

LinkedHashMap 是线程不安全的,如果需要在多线程环境下使用,请考虑使用线程安全的实现或进行适当的同步操作。

5. TreeMap

  • TreeSet使用TreeMap实现,只是value使用静态空对象,只是用key实现TreeSet
  • TreeMap存储 Key-Value 对时, 需要根据 key-value 对进行排序。TreeMap 可以保证所有的 Key-Value 对处于有序状态。
  • TreeSet底层使用红黑树结构存储数据
  • TreeMap 的 Key 的排序:
    • 自然排序: TreeMap 的所有的 Key 必须实现 Comparable 接口,而且所有的 Key 应该是同一个类的对象,否则将会抛出 ClasssCastException
    • 定制排序:创建 TreeMap 时,传入一个 Comparator 对象,该对象负责对TreeMap 中的所有 key 进行排序。此时不需要 Map 的 Key 实现Comparable 接口
  • TreeMap判断两个key相等的标准:两个key通过compareTo()方法或者compare()方法返回0,1,-1
    • 0:对象相等,添加失败
    • -1:比对象小,添加到左边
    • 1:比对象打,添加到右边

6. 自然排序

  • 类实现Comparable接口,进行排序
  • 若一个类实现了Comparable接口,就意味着该类支持排序。实现了Comparable接口的类的对象的列表或数组可以通过Collections.sort或Arrays.sort进行自动排序。
  • 此外,实现此接口的对象可以用作有序映射中的键或有序集合中的集合,无需指定比较器

user类实现 Comparable

public class User implements Comparable {

    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    //按照姓名从大到小排列,年龄从小到大排列
    @Override
    public int compareTo(Object o) {
        if(o instanceof User){
            User user = (User)o;
//            return -this.name.compareTo(user.name);
//            int compare = -this.name.compareTo(user.name);
            int compare = this.name.compareTo(user.name);
            if(compare != 0){
                return compare;
            }else{
                return Integer.compare(this.age,user.age);
            }
        }else{
            throw new RuntimeException("输入的类型不匹配");
        }

    }

}

测试类

public class TreeMapDemo1 {


    public static void main(String[] args) {
        User u1 = new User("ggg", 35);
        User u2 = new User("ggg", 35);
        User u3 = new User("ccc", 83);
        User u4 = new User("ccc", 30);
        User u5 = new User("ccc", 74);
        User u6 = new User("eee", 39);
        User u7 = new User("fff", 40);
        User u8 = new User("aaa", 40);
        User u9 = new User("bbb", 40);

        TreeMap map = new TreeMap();
        map.put(u1, "a");
        map.put(u2, "a");
        map.put(u3, "a");
        map.put(u4, "a");
        map.put(u5, "a");
        map.put(u6, "a");
        map.put(u7, "a");
        map.put(u8, "a");
        map.put(u9, "a");


        for (Object o : map.entrySet()) {
            System.out.println(o);
        }


    }
}

7. 定制排序

Comparator是比较接口,我们如果需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口),那么我们就可以建立一个“该类的比较器”来进行排序,这个“比较器”只需要实现Comparator接口即可。也就是说,我们可以通过实现Comparator来新建一个比较器,然后通过这个比较器对类进行排序

注意:

  • 1、若一个类要实现Comparator接口:它一定要实现compare(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数
  • 2、int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。
public class TreeMapDemo1 {


    public static void main(String[] args) {
        User u1 = new User("ggg", 35);
        User u2 = new User("ggg", 35);
        User u3 = new User("ccc", 83);
        User u4 = new User("ccc", 30);
        User u5 = new User("ccc", 74);
        User u6 = new User("eee", 39);
        User u7 = new User("fff", 40);
        User u8 = new User("aaa", 40);
        User u9 = new User("bbb", 40);

        Comparator com = new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                if(o1 instanceof User && o2 instanceof User){
                    User u1 = (User) o1;
                    User u2 = (User) o2;

                    return -Integer.compare(u1.getAge(), u2.getAge());

//                    int compare = u1.getName().compareTo(u2.getName());
//                    if(compare != 0){
//                        return compare;
//                    }else{
//                        return Integer.compare(u1.getAge(), u2.getAge());
//                    }
                }else{
                    throw new RuntimeException("输入的类型不匹配");
                }
            }
        };

        TreeMap map = new TreeMap(com);
        map.put(u1, "a");
        map.put(u2, "a");
        map.put(u3, "a");
        map.put(u4, "a");
        map.put(u5, "a");
        map.put(u6, "a");
        map.put(u7, "a");
        map.put(u8, "a");
        map.put(u9, "a");


        for (Object o : map.entrySet()) {
            System.out.println(o);
        }


    }
}

user类

public class User  {

    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

   


}

8. Comparable和Comparator区别比较

Comparable是排序接口,若一个类实现了Comparable接口,就意味着“该类支持排序”。而Comparator是比较器,我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。

Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。

两种方法各有优劣, 用Comparable 简单, 只要实现Comparable 接口的对象直接就成为一个可以比较的对象,但是需要修改源代码。 用Comparator 的好处是不需要修改源代码, 而是另外实现一个比较器, 当某个自定义的对象需要作比较的时候,把比较器和对象一起传递过去就可以比大小了, 并且在Comparator 里面用户可以自己实现复杂的可以通用的逻辑,使其可以匹配一些比较简单的对象,那样就可以节省很多重复劳动了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值