源码分析之Map(四)Map实现类特性对比
TreeMap
TreeMap是红黑树的java实现。相比HashMap来说,TreeMap多实现了一个接口NavigableMap,也就是这个接口,决定了TreeMap与HashMap的不同:HashMap的key是无序的,TreeMap的key是有序的。

源码展示:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable { /** * The comparator used to maintain order in this tree map, or null if it uses the natural ordering of its keys. */ private final Comparator<? super K> comparator; private transient Entry<K,V> root; //根节点 /** * The number of entries in the tree */ private transient int size = 0; /** * The number of structural modifications to the tree. */ private transient int modCount = 0; /** * Constructs a new, empty tree map, using the natural ordering of its keys.*/ public TreeMap() { comparator = null; } /** * Constructs a new, empty tree map, ordered according to the given comparator. */ public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } /** * Constructs a new tree map containing the same mappings as the given map, ordered according to the <em>natural ordering</em> of its keys.*/ public TreeMap(Map<? extends K, ? extends V> m) { comparator = null; putAll(m); } /** * Constructs a new tree map containing the same mappings and using the same ordering as the specified sorted map.This method runs in linear time.*/ public TreeMap(SortedMap<K, ? extends V> m) { comparator = m.comparator(); try { buildFromSorted(m.size(), m.entrySet().iterator(), null, null); } catch (java.io.IOException cannotHappen) { } catch (ClassNotFoundException cannotHappen) { } } }
红黑树相关操作可见 源码分析之Map(三)HashMap
LinkedHashMap
LinkedHashMap是HashMap的子类,所以也具备HashMap的诸多特性。不同的是,LinkedHashMap还维护了一个双向链表,以保证通过Iterator遍历时顺序与插入顺序一致。除此之外,它还支持Access Order,即按照元素被访问的顺序来排序源码展示:
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>{ /** * HashMap.Node subclass for normal LinkedHashMap entries. 存储元素的结构 */ static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } } /** * The head (eldest) of the doubly linked list. 双向链表的头节点 */ transient LinkedHashMap.Entry<K,V> head; /** * The tail (youngest) of the doubly linked list. 双向链表的尾节点 */ transient LinkedHashMap.Entry<K,V> tail; /** * The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. true为访问顺序 false为插入顺序 * * @serial */ final boolean accessOrder; /** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance with the specified initial capacity and load factor.*/ public LinkedHashMap(int initialCapacity, float loadFactor) { super(initialCapacity, loadFactor); accessOrder = false; } /** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance with the specified initial capacity and a default load factor (0.75).*/ public LinkedHashMap(int initialCapacity) { super(initialCapacity); accessOrder = false; } /** * Constructs an empty insertion-ordered <tt>LinkedHashMap</tt> instance with the default initial capacity (16) and load factor (0.75). */ public LinkedHashMap() { super(); accessOrder = false; } /** * Constructs an insertion-ordered <tt>LinkedHashMap</tt> instance with the same mappings as the specified map. */ public LinkedHashMap(Map<? extends K, ? extends V> m) { super(); accessOrder = false; putMapEntries(m, false); } /** * Constructs an empty <tt>LinkedHashMap</tt> instance with the specified initial capacity, load factor and ordering mode.*/ public LinkedHashMap(int initialCapacity,float loadFactor,boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } }
ConcurrentHashMap
核心设计思想是:读操作无锁(volatile 保证可见性),写操作锁粒度最小化(CAS + synchronized) 锁头节点
源码展示(JDK21):
final V putVal(K key, V value, boolean onlyIfAbsent) { // ========== 1. 参数校验 ========== if (key == null || value == null) throw new NullPointerException(); int hash = spread(key.hashCode()); // 二次哈希,减少碰撞 int binCount = 0; // 记录桶内节点数,用于判断是否转红黑树 // ========== 2. 自旋主循环(CAS 失败时不断重试) ========== for (Node<K,V>[] tab = table;;) { Node<K,V> f; int n, i, fh; // ========== 2.1 懒初始化:table 为空则初始化 ========== if (tab == null || (n = tab.length) == 0) tab = initTable(); // 见下方分析 // ========== 2.2 目标桶为空:CAS 直接插入 ========== else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { // 用 CAS 把新节点放到数组的 i 位置,期望值是 null if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // 成功则退出循环 // CAS 失败说明有其他线程抢先插入了,进入下一轮循环 } // ========== 2.3 目标桶正在扩容:当前线程去帮忙 ========== else if ((fh = f.hash) == MOVED) // MOVED = -1,ForwardingNode 的标志 tab = helpTransfer(tab, f); // 协助扩容,见下方分析 // ========== 2.4 目标桶有节点:synchronized 锁头节点 ========== else { V oldVal = null; synchronized (f) { // 锁住头节点 f if (tabAt(tab, i) == f) { // 双重检查:确认 f 还是头节点(防止被扩容改掉) // ========== 2.4.1 链表插入 ========== if (fh >= 0) { // fh >= 0 表示是普通链表节点 binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; // key 相同,覆盖 value if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; // 直接改,val 是 volatile break; } Node<K,V> pred = e; // 到达链表尾部,追加新节点 if ((e = e.next) == null) { pred.next = new Node<K,V>(hash, key, value, null); break; } } } // ========== 2.4.2 红黑树插入 ========== else if (f instanceof TreeBin) { Node<K,V> p; binCount = 2; // 调用红黑树的插入逻辑 if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key, value)) != null) { oldVal = p.val; if (!onlyIfAbsent) p.val = value; } } } } // ========== 3. 插入完成后的收尾 ========== if (binCount != 0) { // 链表长度 >= 8,尝试转红黑树 if (binCount >= TREEIFY_THRESHOLD) // TREEIFY_THRESHOLD = 8 treeifyBin(tab, i); if (oldVal != null) return oldVal; // 如果是覆盖,返回旧值 break; } } } // ========== 4. 更新 size(并发计数) ========== addCount(1L, binCount); // 见下方分析 return null; }
-
JDK 8 (CAS + synchronized)
-
实现:抛弃了
Segment数组,直接使用一个Node<K,V>[] table数组。Node是链表的节点。当链表过长时,会转换为 红黑树(TreeNode)。 -
锁粒度:
put操作时,会根据 hash 值定位到数组中的某一个具体的桶(bucket),然后只锁住这个桶的头节点。 -
优势:锁的粒度更细了。原来锁住一个Segment(可能包含几十上百个桶),现在只锁一个桶。并发冲突的概率大大降低。
-
-
CAS(Compare and Swap):在
put时,如果定位到的数组位置是空的,它会利用 CAS 操作,尝试将新的Node写入该位置。这一步是无锁的,性能很高。 -
synchronized:如果定位到的数组位置不为空(说明发生了 hash 冲突),说明可能有多个线程要操作同一个桶。此时,它会锁住这个桶的头节点(即
synchronized (f)),然后遍历链表或红黑树进行插入或更新。 -
特点:为什么改用
synchronized?因为 JDK 8 对synchronized做了大量优化(偏向锁、轻量级锁等),在某些场景下,synchronized的性能已经不输甚至优于ReentrantLock。而且这样简化了代码,减少了内存开销。 - 支持多线程并发扩容。当整个数组需要扩容时,每个线程负责迁移一部分桶(从后往前,通过步长分配),将旧数组的元素复制到新数组。这就大大提高了扩容的效率,避免了一次性全量迁移导致的长时间停顿。
特性对比
| HashMap | LinkedHashMap | TreeMap | concurrentHashMap | |
| 原理 | HashMap内部是基于哈希表实现的键值对存储。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。hashcode相同时,会储存在同一个bucket位置的LinkedList中,在一定条件下,LinkedList会转为红黑树 | LinkedHashMap是继承自HashMap,底层使用哈希表与双向链表来保存所有元素。顺序可以分为插入顺序和访问顺序,是根据成员变量accessOrder决定的。其访问顺序可以实现LRU | TreeMap实现了SotredMap接口,它是有序的集合。而且是一个红黑树结构,每个key-value都作为一个红黑树的节点。如果在调用TreeMap的构造函数时没有指定比较器,则根据key执行自然排序。 | ConcurrentHashMap相比HashMap而言,是多线程安全的,其底层数据与HashMap的数据结构相同。而HashTable虽然是线程安全的,方法都是用Synchronized修饰的,但争夺的都是同一个对象锁,在高并发的情况下,会产生效率低,等待时间长的问题。concurrentHashMap 很多地方使用了cas操作和分段加锁,加锁的最小单位是Hash桶,这使得ConcurrentHashMap效率大大提升. |
| 线程安全 | 否 | 否 | 否 | 是 |
| 初始容量 | 16 | 16 | 0 | 16 |
| 存储结构 | 数组+链表/红黑树 | 数组+链表/红黑树(HashMap子类) | 红黑树 | 数组+链表/红黑树 |
| 顺序规则 | 无序 | 访问顺序或插入顺序 | key的自然顺序或自定义顺序 | 无序 |
| 存储特点 | 最多一条记录的key为null | 最多一条记录的key为null | key的自然顺序时不能为null | 不能存null |
| 插入效率 | 高 | 高 | 较高(需要比较排序) | 较高 |
| 时间复杂度 | O(1) | O(n) | O(log(n)) | O(1) |

浙公网安备 33010602011771号