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 中的元素都需要重写 hashCode 和 equals 方法。

扩容与红黑树优化

  • 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(比较器对象)
相关推荐
企业数字化笔记20 分钟前
租户停用后数据和任务怎么处理?冻结、恢复与保留策略的实现
android·java·spring boot
CarIise26 分钟前
Spring MVC 请求参数绑定与常用注解
java·spring·mvc
一条泥憨鱼39 分钟前
AI Agent 安全实践:从 MCP 工具调用到 Java 权限校验
java·人工智能·安全
Nebula_g1 小时前
云禾外卖补充—项目整体后端运行逻辑-对流程的更深层次理解
java·开发语言·网络·数据库·状态模式·javase
萧瑟余晖1 小时前
Spring Security 6.x新特性与架构演进详解
java·spring·架构
\光辉岁月/1 小时前
1.Object类
java·面向对象·object类
想做小南娘,发现自己是女生喵1 小时前
第二周学习文档 Linux C 文件操作
linux·c语言·学习
鸽鸽程序猿1 小时前
【项目】【在线判题系统】公共功能
java·spring boot·maven
乔旭伟1 小时前
Linux 服务器环境搭建实战笔记:最小化安装、Docker Compose 编排与上线检查全流程
linux·服务器·笔记
潜心一志1 小时前
图像预处理—— 图像的变换与校正(理论)
学习·计算机视觉