网站建站多少钱,企业网站源代码下载,新网站的建设方案,深圳网站设计吧Java Map 详解#xff1a;原理、实现与使用场景
一、介绍
Map 是 Java 集合框架#xff08;java.util#xff09;中键值对#xff08;Key-Value#xff09; 形式的集合接口#xff0c;与 List/Set 并列#xff08;继承自 Collection 的父接口 Iterable#xff0c;但不…Java Map 详解原理、实现与使用场景一、介绍Map 是 Java 集合框架java.util中键值对Key-Value形式的集合接口与 List/Set 并列继承自Collection的父接口Iterable但不直接继承Collection。核心特征是键Key唯一且无序部分实现有序值Value可重复通过键快速查找值是日常开发中存储关联数据的核心工具。特性键值对映射每个 Key 对应唯一的 ValueKey 不允许重复若重复插入新 Value 会覆盖旧 ValueKey 不可变性作为 Key 的对象需重写hashCode()和equals()方法保证哈希一致性且建议使用不可变类型如 String、Integer避免修改后哈希值变化导致无法查找支持 null不同实现类对 null 的支持不同如 HashMap 允许 1 个 null Key 多个 null ValueHashtable 不允许 null无序性默认大部分 Map 实现如 HashMap不保证键值对的存储 / 遍历顺序有序实现需显式指定如 TreeMap、LinkedHashMap。二、Map 接口核心方法Map 定义了键值对操作的核心方法覆盖增删改查、遍历等场景方法作用V put(K key, V value)添加 / 替换键值对Key 不存在则新增存在则替换 Value返回旧 Value无则返回 nullV get(Object key)根据 Key 获取 ValueKey 不存在返回 nullV remove(Object key)删除指定 Key 的键值对返回被删除的 Value无则返回 nullboolean containsKey(Object key)判断是否包含指定 Keyboolean containsValue(Object value)判断是否包含指定 Value需遍历效率低int size()返回键值对数量boolean isEmpty()判断是否为空void clear()清空所有键值对SetK keySet()返回所有 Key 的 Set 集合视图修改会同步到原 MapCollectionV values()返回所有 Value 的 Collection 集合视图SetMap.EntryK,V entrySet()返回键值对Entry的 Set 集合遍历最优方式三、Map 主要实现类Java 提供了多款 Map 实现类适配不同的性能、有序性、并发场景核心如下1. HashMap最常用底层实现JDK 8 前 数组哈希桶 链表JDK 8 后 数组 链表 / 红黑树链表长度 ≥8 转红黑树≤6 转回链表。补充HashMap 的底层由「哈希桶数组」「链表 / 红黑树」组成核心设计目标是通过哈希算法将 Key 映射到数组下标实现 O (1) 级别的快速存取通过链表 / 红黑树解决「哈希冲突」不同 Key 哈希值相同的情况。哈希桶数组table默认初始长度 162 的幂每个下标对应一个「桶」桶内存储链表或红黑树链表当多个 Key 哈希到同一桶时先以链表形式存储红黑树当链表长度 ≥8 且数组长度 ≥64 时链表转为红黑树查询性能从 O (n) 优化为 O (log n)当红黑树节点数 ≤6 时转回链表减少红黑树维护开销。红黑树红黑树是带红 / 黑颜色标记的自平衡二叉查找树也是二叉树的一种不过有5条规则对树的高度进行限制增删改查的时间复杂度稳定在O(logn)补充一下那5条规则1每个节点只能为黑色或者红色2根节点必须为黑色3所有“空叶子节点”都是黑色4红色节点的父节点和子节点都必须为黑色你也可以这样理解不能连续俩红色节点5从任意节点到它自己所有叶子节点在红黑树中这个叶子节点就是指的空叶子节点的路径中黑色节点的数量完全相同规则是为了做什么保证树的结构不会退化成链表同时保证增删查改始终是 O (log n)补最长路径的长度 ≤ 2 × 最短路径的长度两者的差值最大为 最短路径的长度什么是桶桶就是 HashMap 哈希桶数组里的一个存储单元负责接收通过哈希计算分配过来的键值对节点解决哈希冲突的链表 / 红黑树也都是在桶内部形成的。简单举个例子把 HashMap 想象成一个带编号的快递柜这个快递柜的每个独立格子就是一个桶。快递柜 哈希桶数组table快递柜有很多个格子每个格子都有自己的编号比如 0、1、2…15对应 HashMap 数组的索引。桶 快递柜的单个格子每个格子桶的作用是存放 “属于自己” 的快递。在 HashMap 中通过哈希计算会把键值对节点分配到对应编号的桶里。哈希冲突 一个格子里放多个快递理想情况一个快递节点对应一个格子桶取件时直接按编号找速度飞快对应 HashMap 的 O (1) 存取。现实情况可能出现多个快递被分配到同一个格子不同 Key 计算出相同的数组索引这就是哈希冲突。这时HashMap 会在这个桶里用链表把这些节点串起来如果链表太长≥8就会变成红黑树提升查询效率。核心特点存取效率高平均 O (1) 时间复杂度基于哈希表快速定位无序存储顺序 ≠ 插入顺序允许 1 个 null Key、多个 null Value非线程安全多线程操作可能导致死循环、数据丢失初始容量 16负载因子 0.75扩容阈值 容量 × 负载因子默认 12扩容时容量翻倍且重新哈希。机制详解哈希计算与索引定位HashMap 高效存取的核心是「将 Key 映射到数组下标」分为两步1.Key 的哈希值计算为了减少哈希冲突HashMap 对 Key 的hashCode()做了二次哈希扰动函数staticfinalinthash(Objectkey){inth;// 核心逻辑key.hashCode() ^ (h 16)return(keynull)?0:(hkey.hashCode())^(h16);}作用将 Key 的哈希值高 16 位与低 16 位异或混合高低位特征减少哈希冲突尤其数组长度较小时仅低几位参与取模混合后能利用高位信息null Key 处理hash 值固定为 0因此 null Key 始终映射到数组索引 0。2.数组索引计算通过哈希值计算数组下标利用「位运算替代取模」提升性能仅当数组长度为 2 的幂时生效java// i 哈希值 (数组长度 - 1)intindexhash(table.length-1);示例数组长度 16二进制 10000table.length - 1 15二进制 01111哈希值与 15 按位与等价于hash % 16但位运算更快。为什么数组长度是 2 的幂→ 保证hash (length-1)等价于取模且位运算效率远高于取模→ 扩容时节点新索引要么不变要么 原索引 原数组长度简化扩容迁移逻辑。扩容机制条件首次put时table为 null初始化数组默认长度 16threshold16×0.7512后续put时若size threshold触发扩容链表长度≥8 但数组长度 64 时优先扩容而非树化。步骤// 1. 计算新容量原容量翻倍始终保持2的幂intnewCapoldCap1;// 如16→3232→64...// 2. 计算新扩容阈值newThr newCap × loadFactorintnewThroldThr1;// 3. 创建新数组长度newCapNodeK,V[]newTab(NodeK,V[])newNode[newCap];tablenewTab;// 4. 迁移原数组节点到新数组for(intj0;joldCap;j){NodeK,Ve;if((etable[j])!null){table[j]null;// 释放原数组引用帮助GC// 情况1桶内仅一个节点直接计算新索引并放入if(e.nextnull)newTab[e.hash(newCap-1)]e;// 情况2桶内是红黑树拆分红黑树并迁移elseif(einstanceofTreeNode)((TreeNodeK,V)e).split(this,newTab,j,oldCap);// 情况3桶内是链表拆分链表优化无需重新哈希仅判断高位else{// 链表拆分规则新索引 原索引 或 原索引 oldCapNodeK,VloHeadnull,loTailnull;// 原索引节点NodeK,VhiHeadnull,hiTailnull;// 原索引oldCap节点NodeK,Vnext;do{nexte.next;// 核心判断哈希值的第n位oldCap的二进制位是否为0if((e.hasholdCap)0){// 第n位为0 → 新索引原索引if(loTailnull)loHeade;elseloTail.nexte;loTaile;}else{// 第n位为1 → 新索引原索引oldCapif(hiTailnull)hiHeade;elsehiTail.nexte;hiTaile;}}while((enext)!null);// 放入新数组if(loTail!null){loTail.nextnull;newTab[j]loHead;}if(hiTail!null){hiTail.nextnull;newTab[joldCap]hiHead;}}}}使用示例MapString,IntegerhashMapnewHashMap();hashMap.put(Java,1);hashMap.put(Python,2);IntegervaluehashMap.get(Java);// 获取值1hashMap.put(Java,3);// 替换值旧值 1 被覆盖各操作逻辑1.存先定位桶 → 无冲突则新增节点 → 有冲突则按链表 / 红黑树处理 → 替换重复 Key 的 Value → 检查扩容。2.取定位桶 → 桶头匹配直接返回 → 红黑树 / 链表遍历匹配 → 未找到返回 null。3.删定位待删除节点 → 按链表 / 红黑树规则删除 → 更新 size 和 modCount。2. LinkedHashMap底层实现HashMap 双向链表维护插入 / 访问顺序。补充LinkedHashMap 完全复用 HashMap 的哈希桶数组、节点哈希 / 扩容 / 树化等逻辑仅通过「双向链表」改造节点结构、新增头尾指针实现有序性。哈希桶层和 HashMap 完全一致负责通过哈希快速定位节点解决哈希冲突链表 / 红黑树双向链表层所有节点通过before/after指针串联head指向最早节点tail指向最新节点专门维护遍历顺序节点复用LinkedHashMap 的 Entry 继承 HashMap.Node因此哈希桶里的节点同时属于「哈希链表 / 红黑树」和「双向链表」一个节点两个 “身份”。什么是节点在 LinkedHashMap以及 HashMap中节点Node/Entry是存储「键值对Key-Value」的最小单元 —— 就像快递包裹本身而桶是放包裹的格子、双向链表是串包裹的绳子。举个具体的例子比如你往 LinkedHashMap 里存put(手机, 1999)、put(耳机, 299)、put(充电器, 99)每个键值对对应一个「节点」节点 1Key 手机Value1999节点 2Key 耳机Value299节点 3Key 充电器Value99。这些节点会按哈希规则被分配到不同的桶比如手机→3 号格、耳机→5 号格、充电器→3 号格3 号桶里有两个节点手机 充电器用链表串起来5 号桶只有耳机节点。同时这三个节点会被双向链表串成head→手机→耳机→充电器→tail插入顺序如果调用get(手机)节点 1 会被挪到尾部链表变成head→耳机→充电器→手机→tail访问顺序。核心特点继承 HashMap 的所有特性额外保证「有序性」有序模式插入顺序默认遍历顺序 插入顺序访问顺序调用get()/put()后该键值对移至链表尾部可实现 LRU 缓存非线程安全性能略低于 HashMap维护链表开销。使用示例LRU 缓存// 初始容量16负载因子0.75访问顺序模式MapString,IntegerlruMapnewLinkedHashMap(16,0.75f,true);lruMap.put(a,1);lruMap.put(b,2);lruMap.get(a);// 访问后a 移至尾部// 遍历顺序b → a访问顺序for(Map.EntryString,Integerentry:lruMap.entrySet()){System.out.println(entry.getKey());}3. TreeMap底层实现红黑树自平衡的二叉查找树。核心特点有序默认按 Key 的自然顺序如 Integer 升序、String 字典序或自定义 Comparator 排序无 null Key会抛 NullPointerException允许 null Value存取效率 O (log n)红黑树查找 / 插入低于 HashMap非线程安全适合需要排序的场景如按 Key 范围查询。使用示例自定义排序// 按 Key 降序排序MapInteger,StringtreeMapnewTreeMap((k1,k2)-k2-k1);treeMap.put(3,c);treeMap.put(1,a);treeMap.put(2,b);// 遍历顺序3 → 2 → 1for(Integerkey:treeMap.keySet()){System.out.println(key);}4. Hashtable古老的线程安全实现底层实现数组 链表JDK 8 未优化红黑树。补充Hashtable 无红黑树优化全程仅数组 链表线程安全通过「方法级synchronized」实现是典型的 “简单粗暴” 的线程安全哈希表设计。哈希桶数组默认初始长度 11非 2 的幂区别于 HashMap每个下标对应一个桶桶内存储链表链表所有哈希冲突的节点Key 哈希到同一桶通过next指针串联无红黑树优化链表过长时查询性能退化至 O (n)线程安全所有核心方法put/get/remove都加synchronized锁锁粒度为整个 Hashtable 对象。核心特点线程安全方法级synchronized锁但锁粒度大性能差不允许 null Key/Value抛 NullPointerException初始容量 11负载因子 0.75扩容时容量 原容量 ×2 1基本被 ConcurrentHashMap 替代仅兼容旧代码时使用。机制详解哈希计算与扩容Key 的哈希计算无扰动优化Hashtable 的哈希计算逻辑简单无 HashMap 的 “高低位异或” 扰动优化且直接使用key.hashCode()不允许 null Key// 计算Key的哈希值简化版inthashkey.hashCode();// 计算数组索引取模而非位运算intindex(hash0x7FFFFFFF)%table.length;扩容机制区别于HashMapHashtable 扩容触发后新容量 原容量 × 2 1而非 HashMap 的翻倍// 1. 计算新容量原容量 ×2 1如 11→2323→47...intnewCapacitytable.length*21;// 2. 计算新扩容阈值newThr (int)(newCapacity × loadFactor)intnewThreshold(int)(newCapacity*loadFactor);// 3. 创建新数组长度newCapacityEntry?,?[]newTablenewEntry?,?[newCapacity];// 4. 迁移原数组节点到新数组重新计算索引拷贝链表for(inti0;itable.length;i){EntryK,Ve(EntryK,V)table[i];if(e!null){table[i]null;// 释放原数组引用do{EntryK,Vnexte.next;// 重新计算新索引取模intindex(e.hash0x7FFFFFFF)%newCapacity;e.next(EntryK,V)newTable[index];newTable[index]e;enext;}while(e!null);}}// 5. 替换数组和阈值tablenewTable;thresholdnewThreshold;差异总结容量增长规则2n1保证素数理论减少哈希冲突但无实际性能优势HashMap 是2n2 的幂位运算优化索引重计算每次扩容都需对所有节点重新取模无 HashMap 的 “高位判断” 优化扩容开销更大触发条件count ≥ thresholdHashMap 是size threshold更早触发扩容。5. ConcurrentHashMap并发安全底层实现JDK 8 前 分段锁Segment 数组 HashMapJDK 8 后 数组 链表 / 红黑树 CAS synchronized锁哈希桶粒度更细。补充哈希桶层与 HashMap 一致数组长度为 2 的幂链表≥8 转红黑树但数组 / 节点关键字段table/val/next用volatile保证多线程可见性并发控制层无竞争时用 CAS 操作新增节点无锁有竞争时对「单个哈希桶」加synchronized锁仅锁住冲突的桶其他桶可并行操作扩容时用ForwardingNode标记桶状态多线程协同扩容避免单线程扩容瓶颈节点特性Node节点的val/next为volatile保证修改后其他线程能立即感知。核心特点线程安全高并发JDK 8 后锁粒度为「单个哈希桶」并发性能远高于 Hashtable支持 1 个 null KeyJDK 8 后、多个 null Value存取效率接近 HashMap并发场景下迭代器是 “弱一致性”不会抛ConcurrentModificationException适合高并发读写的场景如分布式缓存、业务核心数据存储。扩容机制多线程协同当size 容量 × 负载因子默认初始容量 16负载因子 0.75阈值 12且当前数组table无其他扩容操作时触发。ConcurrentHashMap 扩容由「触发线程 其他访问线程」协同完成避免单线程扩容瓶颈触发条件size sizeCtl同 HashMap扩容流程创建nextTable容量翻倍遍历table桶将桶节点迁移到nextTable迁移时将原桶头节点替换为ForwardingNode标记扩容中其他线程访问该桶时检测到ForwardingNode会暂停当前操作协助扩容核心优势多线程并行迁移不同桶大幅提升扩容效率。使用示例MapString,StringconcurrentMapnewConcurrentHashMap();// 多线程安全写入newThread(()-concurrentMap.put(thread1,data1)).start();newThread(()-concurrentMap.put(thread2,data2)).start();// 安全读取System.out.println(concurrentMap.get(thread1));6. Properties配置文件专用底层实现继承 HashtableKey/Value 均为 String 类型。补充Properties 完全复用 Hashtable 的哈希表核心逻辑数组 链表、方法级 synchronized 锁、禁止 null 值仅在其基础上增加「Key/Value 强制为 String 类型」和「配置文件读写」的扩展方法。核心存储层完全复用 Hashtable 的table数组初始容量 11扩容规则 2n1、Entry 链表节点线程安全通过方法级synchronized实现扩展层类型约束Key/Value 实际使用时强制为 String虽底层仍存储 Object但setProperty()/getProperty()限定为 String配置继承通过defaults实现配置复用当前配置无 Key 时从默认配置集查找IO 能力load()/store()方法解析 / 生成.properties格式的配置文件键值对以keyvalue换行存储。核心特点专为读取配置文件.properties设计支持load()/store()方法读写文件线程安全继承 Hashtable 的 synchronized 方法常用场景读取系统配置、项目配置文件。操作原理配置文件加载load ()load()是 Properties 最核心的扩展方法用于从.properties文件 / 输入流解析键值对// 示例加载配置文件PropertiespropsnewProperties();props.load(newFileInputStream(config.properties));解析按行读取输入流忽略注释行以#/!开头和空行解析每行的keyvalue格式支持:/ 空格 作为分隔符去除首尾空格调用put(key, value)继承 Hashtable将键值对存入哈希表全程加synchronized锁保证线程安全。配置获取getProperty ()getProperty()是 String 专属的获取方法支持默认配置集查找Stringurlprops.getProperty(db.url);// 带默认值若Key不存在返回默认值jdbc:mysql://localhostStringurlprops.getProperty(db.url,jdbc:mysql://localhost);查找逻辑调用 Hashtable 的get(key)获取值强转为 String若值为 null 且defaults不为 null递归从defaults中查找若仍为 null返回指定的默认值或 null。配置存储store ()store()用于将配置写入文件 / 输出流生成标准.properties文件props.store(newFileOutputStream(config.properties),DB Config);写入逻辑先写入注释可选和当前时间戳遍历哈希表的 Entry 链表按keyvalue格式逐行写入全程加synchronized锁保证写入过程不被打断。基础操作put/removeProperties 未重写 Hashtable 的put()/remove()方法完全复用其逻辑setProperty(key, value)本质是调用put(key, value)仅限定参数为 String所有操作均加synchronized锁线程安全但并发性能差同 Hashtable。使用示例PropertiespropsnewProperties();// 读取配置文件props.load(newFileInputStream(config.properties));Stringurlprops.getProperty(db.url);// 获取配置值四、Map 常见使用场景场景推荐实现类日常开发、无排序、高读写性能HashMap需要有序插入 / 访问顺序、LRU 缓存LinkedHashMap需要按 Key 排序、范围查询TreeMap高并发读写、线程安全ConcurrentHashMap配置文件读写Properties旧代码兼容、低并发线程安全Hashtable五、Map 遍历方式性能对比Map 遍历的核心是操作entrySet()/keySet()/values()推荐优先级entrySet 遍历最优直接获取键值对无需二次查询性能最高for(Map.EntryString,Integerentry:map.entrySet()){Stringkeyentry.getKey();Integervalueentry.getValue();}迭代器遍历支持删除适合遍历中删除元素避免ConcurrentModificationExceptionIteratorMap.EntryString,Integeritmap.entrySet().iterator();while(it.hasNext()){Map.EntryString,Integerentryit.next();if(entry.getValue()2){it.remove();// 安全删除}}keySet get () 遍历低效需二次哈希查询性能最差不推荐for(Stringkey:map.keySet()){Integervaluemap.get(key);// 额外哈希查询}Lambda 遍历简洁JDK 8 支持代码简洁性能接近 entrySetmap.forEach((key,value)-System.out.println(key:value));六、注意事项Key 的哈希与相等性自定义对象作为 Key 时必须重写hashCode()和equals()保证相同对象哈希值相同不同对象哈希值尽量不同避免使用可变对象作为 Key如 ArrayList修改后哈希值变化会导致无法查找。HashMap 扩容优化已知元素数量时提前指定初始容量如new HashMap(100)避免频繁扩容扩容需重新哈希开销大负载因子默认 0.75 是性能与内存的平衡无需轻易修改。并发安全HashMap/LinkedHashMap/TreeMap 非线程安全多线程修改需手动加锁如Collections.synchronizedMap()或直接使用 ConcurrentHashMapConcurrentHashMap 不支持null KeyJDK 7/ 支持 1 个 null KeyJDK 8需注意空值处理。TreeMap 排序自定义对象作为 Key 时需实现Comparable接口或创建 TreeMap 时指定 Comparator否则抛ClassCastException。七、总结Map 是 Java 中存储键值对的核心集合核心实现类各有侧重HashMap性能均衡适合绝大多数无排序、非并发场景LinkedHashMap有序 HashMap 特性适合缓存场景TreeMap排序 范围查询适合有序键值对场景ConcurrentHashMap高并发安全适合核心业务的并发存储Properties配置文件专用简单易用。七、总结Map 是 Java 中存储键值对的核心集合核心实现类各有侧重HashMap性能均衡适合绝大多数无排序、非并发场景LinkedHashMap有序 HashMap 特性适合缓存场景TreeMap排序 范围查询适合有序键值对场景ConcurrentHashMap高并发安全适合核心业务的并发存储Properties配置文件专用简单易用。选择时需根据「有序性、并发要求、性能、业务场景」四大维度权衡同时注意 Key 的不可变性和遍历方式的性能优化。附表JDK 版本核心变化点涉及 Map 实现类详细说明JDK 1.0初始版本发布Hashtable1. 引入 Hashtable底层为「数组 链表」方法级synchronized保证线程安全2. 不支持 null Key/Value初始容量 11扩容规则2n13. 无红黑树、无扰动哈希、无并发优化。JDK 1.2集合框架重构HashMap、TreeMap1. 引入 HashMap替代 Hashtable 成为非线程安全哈希表首选- 底层「数组 链表」初始容量 162 的幂负载因子 0.75- 支持 1 个 null Key、多个 null Value- 实现扰动哈希高低位异或、位运算索引计算2. 引入 TreeMap底层红黑树支持 Key 自然排序 / 自定义排序无 null Key3. 引入 LinkedHashMapHashMap 子类双向链表维护插入 / 访问顺序。JDK 1.4配置增强Properties1. 基于 Hashtable 扩展强化.properties配置文件读写能力2. 新增loadFromXML()/storeToXML()方法支持 XML 格式配置。JDK 5.0泛型支持所有 Map 实现类1. 引入泛型MapK,V替代原始 Object 类型避免强制类型转换2. 优化 TreeMap 比较器支持Comparator? super K泛型约束。JDK 6.0性能微调HashMap、Hashtable1. 优化 HashMap 哈希算法减少哈希冲突概率2. 调整 Hashtable 扩容阈值计算逻辑提升低容量场景性能。JDK 7.0并发优化ConcurrentHashMap1. 重写 ConcurrentHashMap底层「分段锁Segment HashMap」- 分段锁粒度为 Segment默认 16 个并发度远高于 Hashtable- 不支持 null Key/Value避免并发场景空指针歧义2. HashMap 优化扩容逻辑减少扩容时的哈希冲突。JDK 8.0核心重构里程碑HashMap、ConcurrentHashMap、LinkedHashMap1. HashMap 重大升级- 链表长度 ≥8 且数组长度 ≥64 时链表转红黑树查询从 O (n)→O (log n)- 红黑树节点数 ≤6 时转回链表降低维护开销- 优化扩容机制多线程扩容但仍非线程安全- 简化扰动哈希逻辑key.hashCode() ^ (h 16)2. ConcurrentHashMap 重构- 废弃分段锁改用「CAS synchronized 锁单个哈希桶」并发粒度更细- 支持 1 个 null Key区别于 JDK 7兼容 HashMap 用法- 引入红黑树优化同 HashMap3. LinkedHashMap 优化- 强化 LRU 场景性能访问顺序模式下节点迁移效率提升4. 新增 Lambda 遍历forEach()方法简化遍历写法。JDK 9.0工厂方法增强所有 Map 实现类1. 引入不可变 Map 工厂方法-Map.of()最多 10 个键值对、Map.ofEntries()不限数量- 不可变 Map 不支持增删改无 null Key/Value性能更高。JDK 11.0性能与安全优化ConcurrentHashMap、HashMap1. 优化 ConcurrentHashMap 扩容逻辑多线程协同扩容效率提升2. 修复 HashMap 极端哈希值下的性能退化问题3. 增强不可变 Map 的序列化安全性。JDK 17.0长期支持版优化所有 Map 实现类1. 稳定化不可变 Map 实现修复多线程下的弱一致性问题2. 优化 TreeMap 红黑树旋转逻辑减少排序耗时3. 废弃 Hashtable 部分过时方法如elements()推荐 ConcurrentHashMap 替代。