Java之LinkedHashMap

本文主要探讨了Java中的LinkedHashMap,它是HashMap的子类,实现插入和访问顺序一致的效果。文章详细讲解了LinkedHashMap的底层数据结构,特别是其构造方法、put方法的内部逻辑,包括recordAccess、addEntry和createEntry等关键步骤。此外,还分析了Iterator的工作原理以及containsValue方法的行为。在遍历LinkedHashMap时需要注意的问题也进行了说明,最后总结了LinkedHashMap的特点及其在特定情况下的行为表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、底层数据结构

根据类名定义public class LinkedHashMap<K,V> extends HashMap<K,V>, LinkedHashMap是继承自HashMap,
它实现了元素插入顺序与访问顺序一致

二、部分方法源码

1.构造方法
 public LinkedHashMap() {
        super();
        accessOrder = false; // 默认情况下为false
}

可以看到构造方法调用的是父类HashMap的方法, 但多了一个参数accessOrder.

/**
     * The iteration ordering method for this linked hash map: <tt>true</tt>
     * for access-order, <tt>false</tt> for insertion-order.
     *
     * @serial
     */
    private final boolean accessOrder;

该属性与集合存储的元素顺序有关.
false:访问顺序与插入顺序一致
true:集合元素可能会因为插入(put)、访问(get)修改在集合中的位置

2.put方法

LinkedHashMap调用父类的put方法, 并且重写了recordAccesaddEntrycreateEntry方法, 这些方法在put方法中会被调用.

2.1 recordAccess——集合元素位置变化

何时会调用?

(1) accessOrder == true
(2) 插入元素时存在相同的key or 调用get方法

2.1.1 key已经存在

//  HashMap put方法部分源码
...
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this); <—— 在这里
                return oldValue;
            }
        }
...

recordAccess——

// recordAccess源码

void recordAccess(HashMap<K,V> m) {
            LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
            if (lm.accessOrder) {
                lm.modCount++;
                remove(); // 删除当前节点(修改引用)
                addBefore(lm.header); // 添加元素到末尾
            }
        }

例子——

默认状态下, accessOrder = false

// 栗子1
Map<String, String> map = new LinkedHashMap<>(16, 0.75f); // 不设置accessOrder
map.put("ah", "f");
map.put("1", "a");
map.put("ah", "g");

遍历打印:
ah=g
1=a

accessOrdertrue时, 会删除节点, 并修改元素的位置到当前列表末尾

// 栗子2
Map<String, String> map = new LinkedHashMap<>(16, 0.75f, true); // 显式设置accessOrder
map.put("ah", "f");
map.put("1", "a");
map.put("ah", "g");

遍历打印:
1=a
ah=g

2.1.2 当调用get方法

public V get(Object key) {
        Entry<K,V> e = (Entry<K,V>)getEntry(key); // 调用父类方法
        if (e == null)
            return null;
        e.recordAccess(this); // 修改元素顺序
        return e.value;
    }
// 栗子1
 Map<String, String> map = new LinkedHashMap<>(16, 0.75f, true);
 
map.put("ah", "f");
map.put("1", "a");
map.put("2", "b");
map.get("ah");

遍历打印:
1=a
2=b
ah=f
2.2 addEntry & createEntry

addEntry ——

void addEntry(int hash, K key, V value, int bucketIndex) {
        super.addEntry(hash, key, value, bucketIndex); // 调用父类addEntry

        // Remove eldest entry if instructed
        Entry<K,V> eldest = header.after;
        if (removeEldestEntry(eldest)) { // 只会返回false
            removeEntryForKey(eldest.key);
        }
    }

createEntry——

void createEntry(int hash, K key, V value, int bucketIndex) {
        HashMap.Entry<K,V> old = table[bucketIndex];
        Entry<K,V> e = new Entry<>(hash, key, value, old);
        table[bucketIndex] = e;
        e.addBefore(header); <——关键
        size++;
    }

除了关键的代码, 其实其它的与HashMap并没有什么差别.

// 典型的双向链表插入代码
 private void addBefore(Entry<K,V> existingEntry) {
            after  = existingEntry;
            before = existingEntry.before;
            before.after = this;
            after.before = this;
        }
3.Iterator

直接看看Iterator是如何取数据的。

 public Map.Entry<K,V> next() { return nextEntry(); }

nextEntry()——

Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            if (nextEntry == header)
                throw new NoSuchElementException();

            Entry<K,V> e = lastReturned = nextEntry;<——关键属性
            nextEntry = e.after;
            return e;
        }

nextEntry属性在何时被初始化?

在调用Iterator()时已经初始化

 private abstract class LinkedHashIterator<T> implements Iterator<T> {
        Entry<K,V> nextEntry    = header.after; <——得到第一个元素
        Entry<K,V> lastReturned = null;
...

header在何时被初始化?

// 构造方法调用
void init() {
        header = new Entry<>(-1, null, null, null);
        header.before = header.after = header;
    }

为什么header.after会指向第一个元素?

First, 构造方法调用init方法后链表的结构:
在这里插入图片描述

Second, 添加第一个元素(看上面提到的插入链表的代码)
在这里插入图片描述
Next, 添加第二个元素
在这里插入图片描述

4. containsValue(Object value)
public boolean containsValue(Object value) {
        // Overridden to take advantage of faster iterator
        if (value==null) {
            for (Entry e = header.after; e != header; e = e.after)
                if (e.value==null)
                    return true;
        } else {
            for (Entry e = header.after; e != header; e = e.after)
                if (value.equals(e.value))
                    return true;
        }
        return false;
    }

对照上图看.

三、遍历

通常情况下有2个方案:
1.根据keySet()方法获取key集合, 遍历key, 根据get(Object key)方法获取key-value值
2.根据entrySet()获取集合, 直接遍历

But, 当accessOrder=true时, 调用get()方法会引发集合元素顺序的修改, 引发异常ConcurrentModificationException

四、总结

1.LinkedHashMap通过链表来保证插入顺序与访问顺序一致
2.当accessOrder=true时, 可能会造成元素顺序的修改, 只影响当前元素在集合中的顺序.

### Java 中遍历 LinkedHashMap 的方法 在 Java 中,`LinkedHashMap` 是 `HashMap` 的子类,它保留了插入顺序或访问顺序。因此,在遍历 `LinkedHashMap` 时,可以按照其内部维护的顺序来获取键值对。以下是几种常见的遍历方式。 #### 方法一:使用增强型 for 循环 通过 `entrySet()` 方法可以获得 `LinkedHashMap` 中所有的键值对集合,然后使用增强型 for 循环逐一访问这些键值对: ```java import java.util.LinkedHashMap; import java.util.Map; public class IterateLinkedHashMapExample { public static void main(String[] args) { LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put(11, "AB"); linkedHashMap.put(2, "CD"); linkedHashMap.put(33, "EF"); System.out.println("Using Enhanced For Loop:"); for (Map.Entry<Integer, String> entry : linkedHashMap.entrySet()) { System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); } } } ``` 此方法简单直观,适用于大多数场景[^1]。 --- #### 方法二:使用 Iterator 进行迭代 如果需要更灵活地控制迭代过程(例如中途停止迭代),可以通过 `Iterator` 来实现: ```java import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; public class IterateWithIteratorExample { public static void main(String[] args) { LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put(11, "AB"); linkedHashMap.put(2, "CD"); linkedHashMap.put(33, "EF"); System.out.println("Using Iterator:"); Iterator<Map.Entry<Integer, String>> iterator = linkedHashMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry<Integer, String> entry = iterator.next(); System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); } } } ``` 这种方法提供了更多的灵活性,尤其是在需要条件判断的情况下[^2]。 --- #### 方法三:使用 Lambda 表达式和 forEach() 自 Java 8 起,引入了函数式编程特性,允许使用 `forEach()` 和 Lambda 表达式简化代码结构: ```java import java.util.LinkedHashMap; import java.util.Map; public class IterateWithLambdaExample { public static void main(String[] args) { LinkedHashMap<Integer, String> linkedHashMap = new LinkedHashMap<>(); linkedHashMap.put(11, "AB"); linkedHashMap.put(2, "CD"); linkedHashMap.put(33, "EF"); System.out.println("Using Lambda Expression and forEach():"); linkedHashMap.forEach((key, value) -> { System.out.println("Key: " + key + ", Value: " + value); }); } } ``` 这种方式更加简洁明了,尤其适合现代开发环境中的需求[^1]。 --- ### 总结 以上三种方法都可以用于遍历 `LinkedHashMap`,具体选择取决于实际应用场景和个人偏好。对于简单的遍历操作,推荐使用增强型 for 循环;而对于需要更多控制权的情况,则可以选择 `Iterator` 或者结合 Java 8 特性的 `forEach()` 方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值