Java Map 集合学习笔记

Map 概述

Map 和 Collection 没有继承关系。

Map 以键值对的方式存储数据,key 和 value 都是存储对象的内存地址。

key 起到主导作用,value 是 key 的一个附属品,所有的 key 是一个 Set 集合。

Map 接口常用方法

  • void clear()
  • boolean containsKey(Object key)
  • boolean containsValue(Object value)
  • boolean isEmpty()
  • V get(Object key) 通过 key 获取 value
  • Set<K> keySet() 获取 Map 集合所有的 key
  • V put(K key, V value) 向 Map 集合中添加键值对
  • V remove(Object key) 通过 key 删除键值对
  • int size() 获取集合中键值对个数
  • Collection<V> values() 获取 Map 集合中所有 value,返回一个 Collection
  • Set<Map.Entry<K,V>> entrySet() 将 Map 集合转换成 Set 集合。转换后 key 和 value 对应起来,形成 key = 对应 value 的形式。
    Entry 是 Map 的静态内部类。
java 复制代码
Map<Integer, String> map = new HashMap<>();
Set<Map.Entry<Integer, String>> set = map.entrySet(); // set 元素类型就是 Map.Entry<K,V>

遍历 Map 集合

第一种方式

  1. 通过 keySet() 方法获取所有的 key,所有的 key 是一个 Set 集合。
  2. 遍历 key(可以使用迭代器或者 foreach),通过 key 获取 value(通过 get 方法)。

第二种方式

  1. 通过 entrySet() 方法将 Map 全部转换成 Set。
  2. 通过迭代器或者 foreach 遍历 Set 集合。
java 复制代码
Set<Map.Entry<Integer, String>> set = map.entrySet();
Iterator<Map.Entry<Integer, String>> it = set.iterator();
while (it.hasNext()) {
    Map.Entry<Integer, String> node = it.next();
    Integer key = node.getKey();
    String value = node.getValue();
}

for (Map.Entry<Integer, String> node : set) {
    Integer key = node.getKey();
    String value = node.getValue();
}

HashMap 集合

底层数据结构

  1. HashMap 集合底层是哈希表的数据结构。
  2. 哈希表是数组和单向链表的结合体(一维数组,数组中每一个元素是一个单链表),能充分发挥数组和单向链表的优点。

底层源码

java 复制代码
public class HashMap {
    Node<K,V> table;
    static class Node {
        final int hash;       // 哈希值(是 key 的 hashCode() 方法的执行结果,通过哈希算法计算出数组下标)
        final K key;
        final V value;
        Node<K,V> next;       // 下一个节点的存储地址
    }
}

深入 put(k,v) 方法

实现原理:

  1. 先将 k,v 封装到 Node 对象中。
  2. 底层调用 hashCode() 方法得出 hash 值,通过哈希算法将 hash 值转换成数组下标。
    • 下标位置没有元素的话,将 Node 添加到这个位置。
    • 如果对应下标有链表,就拿着 k 和链表中每一个 k 进行 equals 比较,如果都返回 false 则添加到链表末尾;如果有返回 true 的,那么这个节点的 value 被覆盖。

深入 get(k) 方法

原理类似 put(k,v):

调用 key 的 hashCode() 方法得出 hash 值,然后通过哈希算法得出数组下标。

  • 如果该下标下没有元素则返回 null。
  • 否则拿着 k 和该下标的链表进行 equals 比较,有返回 true 的就返回 value,没有的话就返回 null。

重写 hashCode() 和 equals()

HashMap 集合中的 hashCode()equals() 方法会被调用,所以需要对这两个方法进行重写。

由于放在 HashMap 中的 key 部分就是放在 HashSet 集合中了,所以 HashSet 集合也需要重写 hashCode()equals()

hashCode 使用不当会导致 HashMap 无法发挥性能:

  • 如果 hashCode 的返回值都不一样,HashMap 就会被用成一维数组。
  • 如果返回值都一样,就会被用成单向链表。
    要想充分发挥 HashMap 的性能,这些元素最好散列分布均匀。

放在 HashMap 和 HashSet 中的元素都需要重写 hashCodeequals 方法。

扩容与红黑树优化

  • HashMap 中的所用容量占总容量 0.75 的时候就会开始扩容。
  • 在 JDK 8 之后,如果 HashMap 中单向链表的元素超过 8 个,单向链表的数据结构就会变成红黑树;而元素个数低于 6 时会将红黑树再改回单向链表。这样做可以提高检索效率。

HashMap 和 Hashtable 的区别

  • HashMap 允许 key 和 value 部分为 null,但只能有一个 key 为 null。
  • Hashtable 不允许 key 部分和 value 部分为空,会报空指针异常。
    由于 Hashtable 是线程安全的,其下方法都带 synchronized 关键字,效率低,所以不常用。

Properties 属性类

Properties 是一个 Map 集合,继承 Hashtable,Properties 的 key 和 value 都是 String 类型。

Properties 被称为属性类对象,是线程安全的。

  • 存:setProperty
  • 取:getProperty
java 复制代码
Properties pro = new Properties();
pro.setProperty("111", "abandon");
pro.getProperty("111");

HashSet 集合

  • 无序不可重复。
java 复制代码
Set s1 = new HashSet();

TreeSet 集合

底层实现

TreeSet 集合底层实际是一个 TreeMap,TreeMap 集合底层是一个二叉树。

放到 TreeSet 集合中的元素,等同于放到 TreeMap 集合 key 部分了。

TreeMap 集合中的元素无序不可重复,但会自动排序。

但是对于自定义类型的元素来说,TreeSet 不能排序,因为没有指定比较规则,会抛出异常。

自平衡二叉树

  • 左小右大的方式存放。
  • 遍历二叉树的方式:
    • 前序遍历:根左右
    • 中序遍历:左根右
    • 后序遍历:左右根
  • TreeSet 和 TreeMap 采用中序遍历,Iterator 也是中序遍历。

元素排序的两种方式

放到 TreeSet 或者 TreeMap 集合 key 部分的元素要想做到排序有两种方式:

  1. 实现 Comparable 接口
  2. 在构造集合的时候传递一个比较器 Comparator
第一种方式:实现 Comparable 接口

要进行比较就要让自定义类型实现 Comparable 接口,同时实现接口中的 compareTo 方法。

TreeSet 中的 equals 可以不重写,需要在这个方法中写出比较的规则。

compareTo 源代码:

java 复制代码
public int compareTo(T o) {
    return 0;
}

例如,有一个 Stu 类,需要根据年龄大小进行排序放到 TreeSet:

java 复制代码
class Stu implements Comparable<Stu> {
    int age;
    public Stu() { }
    public Stu(int age) {
        this.age = age;
    }
    public int compareTo(Stu s) { // 调用的时候就是 s1.compareTo(s),也就是 this(已放置的) 和 s(需要放置的) 比较
        return s.age - this.age;  // 需要放置的 s 大返回正数,s 小返回负数,相等返回 0,然后调用者再根据返回值进行排序
    }
}
第二种方式:使用比较器 Comparator

实现比较器接口:

java 复制代码
class Stu {
    int age;
    public Stu() { }
    public Stu(int age) {
        this.age = age;
    }
}

class StuComparator implements Comparator<Stu> {
    public int compare(Stu s1, Stu s2) {
        return s1.age - s2.age;
    }
}

// TreeSet<Stu> stus = new TreeSet<>(); 这个时候不能这么构造了
TreeSet<Stu> stus = new TreeSet<>(new StuComparator()); // 需要传递比较器进去

或者使用匿名内部类,直接 new 接口:

java 复制代码
TreeSet<Stu> stus = new TreeSet<>(new Comparator<Stu>() {
    public int compare(Stu s1, Stu s2) {
        return s1.age - s2.age;
    }
});

两种方式的选择

  • 当比较规则单一的时候,建议使用 Comparable 接口。
  • 如果有多个规则并需要频繁切换,建议使用 Comparator 接口。

集合工具类 Collections

java.util.Collections 是集合工具类,注意与 java.util.Collection(集合接口)区分。

java 复制代码
List l1 = new ArrayList();
// 变成线程安全的
Collections.synchronizedList(l1);
// 此时 l1 就是线程安全的了
l1.add(100);
l1.add(200);
// 排序
Collections.sort(l1);

// 对自定义类型的元素排序,例如 Stu
List<Stu> l2 = new ArrayList<>();
// 两种方式:
// 1. 实现 Comparable 接口
// 2. 用比较器对象

// Set 集合如何排序?可以将 Set 转换为 List
Set<Stu> set = new HashSet<>();
List<Stu> list = new ArrayList<>(set);
Collections.sort(list);
// 或者用比较器对象:Collections.sort(比较器对象)
相关推荐
一只小菜鸡..11 小时前
南京大学 操作系统 (JYY) 学习笔记:进程、系统调用与状态机管理
笔记·学习
DeviceHub11 小时前
硬件工程师选型笔记:ALPS RK12L1230C0K 与 Tonevee RK12L1230C0K 电位器 PIN TO PIN 评估指南
笔记
小Ti客栈11 小时前
Spring Boot 集成 Springdoc-OpenAPI 与 Knife4j实现接口文档与可视化调试
java·spring boot·后端
whyTeaFo12 小时前
GAMES101: Lecture 13: Ray Tracing 1 (Whitted-Style Ray Tracing) ppt笔记
笔记
Ai拆代码的曹操12 小时前
Spring 事务 REQUIRES_NEW 嵌套调用:连接池翻倍的秘密
java·后端·spring
动恰客流统计12 小时前
ReID边缘计算视觉统计:餐饮店客流增长的数字化破局路径
java·大数据·运维·人工智能
Ivanqhz13 小时前
Rust &‘static str浅析
java·前端·javascript·rust
天国梦14 小时前
2026英语教学系统选型实战:AI如何让备课效率提升42%?天学网技术落地全解析
人工智能·学习
维克兜率天15 小时前
【维克】大数定律与中心极限定理:为什么长期均值终将回归?
经验分享·学习·金融·概率论
中微极客15 小时前
降维算法75倍加速:从PCA到稀疏字典学习的工程实践
人工智能·学习·算法