JavaSE基础知识-Map集合-HashMap

一、Map 接口体系结构

Map 接口采用键值对存储模式,其核心实现类的体系结构如下:

Map 接口
├─ HashMap(哈希表实现,JDK8+为数组+链表+红黑树)
├─ Hashtable(哈希表实现,线程安全)
│  └─ Properties(用于配置文件处理)
└─ SortedMap 接口(键有序)
   └─ TreeMap(红黑树实现,按键排序)

核心区别:Map 接口与 Collection 接口无继承关系,Collection 存储单一元素,Map 存储键值对;Map 的键(Key)具有无序不可重复特性,值(Value)可重复。

二、HashMap 深度解析

1. 底层存储结构

JDK8 及以后的 HashMap 采用数组 + 链表 + 红黑树的复合结构:

  • 数组:作为哈希表的主体,每个元素称为 "桶(Bucket)"(数组下标)
  • 链表:解决哈希冲突,当多个键映射到同一桶(数组下标)时形成链表
  • 红黑树:当链表长度超过 8 且数组容量≥64 时,链表转为红黑树(检索效率从 O (n) 提升至 O (log n));当红黑树节点数≤6 时,转回链表

2. 关键参数与扩容机制

  • 初始化容量:默认 16(建议指定为 2 的倍数,保证哈希分布均匀)
  • 加载因子:默认 0.75(元素数量达容量的 75% 时触发扩容)
  • 扩容机制:容量翻倍(原容量 ×2)

3. 核心特性

  • 线程安全:非线程安全,多线程环境需用Collections.synchronizedMapConcurrentHashMap
  • 键值限制:key 和 value 均可为 null(key 为 null 时仅允许一个)
  • 去重逻辑:相同 key 会覆盖旧 value(依赖 key 的equalshashCode方法)

三、HashMap的put(key,value)实现机制

当我们向 HashMap中添加新元素时,HashMap会按照以下步骤执行:

  1. 计算新元素key的 hashCode 值,确定其在 HashMap 中的存储位置(数组下标)
  2. 如果该位置没有元素,则认为该key不重复,直接添加该元素
  3. 若该位置存在元素则:
    1. 获取该位置的链表头节点(或红黑树的根节点)
    2. 遍历链表(或红黑树),对每个节点执行:
      • 计算当前元素key的 hash 值是否与新元素key的 hash 相等
      • 如果 hash 相等(不相等遍历下一个节点),再调用 equals()比较两个元素内容
        • 如果 equals 返回 true,覆盖该元素键值对的value(终止遍历)
        • 如果 equals 返回 false,继续遍历下一个节点
    3. 遍历完所有节点后
      • 如果未找到相等元素,将新元素插入链表尾部(或红黑树中)
      • 如果链表长度超过阈值(默认 8)且数组容量 ≥ 64,将链表转换为红黑树

这里需要特别注意的是,hashCode()和 equals()方法的实现必须遵循以下原则:

  • 如果两个对象equals()相等,则它们的 hashCode()值必须相同
  • 如果两个对象的 hashCode()值相同,它们equals()不一定相等(哈希冲突)

 不重写的后果:

  • equals()未重写:即使key相同,集合也会认为是不同元素,导致相同key的键值对元素重复存储。
  • hashCode()未重写:即使key相同,哈希值也可能不同(导致key相同的键值对元素被存入不同的数组下标而使equals()比较失效而无法达到键值对元素key不可重复目的,我们应保证key相同时存入相同的数组下标以便后续进行equals()判定。因此需要根据对象的属性特征值来重写hashCode()方法来计算哈希值)。

四、Map 接口常用方法

方法签名功能描述
V put(K key, V value)添加键值对,返回被覆盖的旧值(无则为 null)
V get(Object key)根据 key 获取 value(无则返回 null)
V remove(Object key)根据 key 删除键值对,返回被删除的 value
boolean containsKey(Object key)判断是否包含指定 key
boolean containsValue(Object v)判断是否包含指定 value
Set<K> keySet()返回所有 key 的 Set 集合
Collection<V> values()返回所有 value 的 Collection 集合
Set<Map.Entry<K,V>> entrySet()返回键值对集合(推荐用于遍历)

五、Map 遍历方式

以 HashMap 为例,演示三种常用遍历方式:

public class Main {
    public static void main(String[] args) {
        Map<Integer,String> map = new HashMap();
        map.put(1,"tom");
        map.put(2,"jack");
        map.put(3,"Alex");
        printWay1(map);
        printWay2(map);
        printWay3(map);
    }
    //keySet遍历:需两次哈希查询(keySet一次,get一次)
    public static void printWay1(Map map){
        Set<Integer> keySet = map.keySet();
        for (Integer key : keySet) {
            System.out.print(key + "=" + map.get(key) + " ");
        }
    }
    //entrySet遍历:一次获取键值对,效率更高
    public static void printWay2(Map map){
        Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
        for (Map.Entry<Integer, String> entry : entrySet) {
            System.out.print(entry.getKey() + "=" + entry.getValue() + " ");
        }
    }
    // Java 8 forEach 遍历
    public static void printWay3(Map map){
        map.forEach((key, value) -> System.out.print(key + "=" + value + " "));
    }
}

效率对比entrySet > forEach > keySetkeySet需额外调用get方法,多一次哈希查询)。

六、Hashtable 特性解析

Hashtable 是早期的哈希表实现,与 HashMap 的核心差异如下:

特性HashMapHashtable
线程安全非线程安全线程安全(方法加synchronized
初始化容量1611
扩容机制原容量 ×2原容量 ×2+1
键值 null 限制允许 key/value 为 null不允许(抛NullPointerException
存储结构数组 + 链表 + 红黑树(JDK8+)数组 + 链表

注意:Hashtable 因效率低已被淘汰,线程安全场景优先使用ConcurrentHashMap

七、Map 实现类选择

场景需求推荐实现类
一般场景(无序、高效)HashMap
线程安全场景ConcurrentHashMap
需按键排序TreeMap
处理配置文件Properties
遗留项目维护Hashtable(不推荐新用)

八、注意事项

  1. 初始化容量优化:创建 HashMap 时,根据预估元素数量设置初始容量(如new HashMap<>(100)),减少扩容次数

  2. 避免在遍历中修改 Map:增强 for 循环中修改 Map 结构(增删元素)会抛ConcurrentModificationException,需用迭代器的remove方法

  3. 红黑树转换阈值:JDK8 中,当链表长度 > 8 且数组容量≥64 时转红黑树,节点数 < 6 时转回链表,平衡检索效率

  4. null 值处理:HashMap 允许 key 为 null(仅一个),Hashtable 不允许,TreeMap 需注意compareTo对 null 的处理

九、总结

实际开发中,需根据业务场景选择合适的实现类,关注 key 的equalshashCode重写,以及遍历方式的效率差异。后续可结合源码深入学习哈希表和红黑树的实现细节,进一步提升 Java 集合框架的掌握程度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值