📜  Java HashMap中如何维护元素的插入顺序?(1)

📅  最后修改于: 2023-12-03 14:42:14.407000             🧑  作者: Mango

Java HashMap中如何维护元素的插入顺序?

介绍

Java中的HashMap是一种常用的数据结构,它能够高效地存储和检索键值对。但是,HashMap默认是不会维护插入顺序的,也就是说,随着元素的插入,元素在HashMap中的顺序可能会发生变化。这可能会导致一些问题,比如读取数据时顺序不一致等。所以很多情况下,我们需要在HashMap中维护元素的插入顺序。

解决方案
Java 8及以上版本

Java 8及以上版本提供了一个新的数据结构 LinkedHashMap,顾名思义,它是一个链表结构的哈希表,在HashMap的基础上额外维护了元素的插入顺序。因此,如果我们需要维护元素的插入顺序,只需要使用LinkedHashMap即可。示例代码如下:

Map<String, Integer> linkedHashMap = new LinkedHashMap<>();
linkedHashMap.put("apple", 1);
linkedHashMap.put("banana", 2);
linkedHashMap.put("orange", 3);

linkedHashMap.forEach((k, v) -> System.out.println(k + ":" + v));
// 输出:apple:1 banana:2 orange:3
Java 7及以下版本

对于Java 7及以下版本,我们可以自己实现一个类似于LinkedHashMap的数据结构,即一个哈希表和一个链表的结合体。在插入时,记录元素的插入位置,以便后续可以按照插入顺序访问元素。关键点是需要在插入、删除、遍历等操作时维护链表结构。示例代码如下:

import java.util.HashMap;

public class LinkedHashMap<K, V> extends HashMap<K, V> {

    private Entry<K, V> head;
    private Entry<K, V> tail;

    @Override
    public V put(K key, V value) {
        V oldValue = super.put(key, value);
        Entry<K, V> entry = new Entry<>(key, value);
        if (oldValue == null) {
            // 新增元素
            if (head == null) {
                head = entry;
                tail = entry;
            } else {
                tail.next = entry;
                entry.prev = tail;
                tail = entry;
            }
        } else {
            // 修改元素
            Entry<K, V> oldEntry = findEntry(key);
            oldEntry.value = value;
        }
        return oldValue;
    }

    @Override
    public void clear() {
        super.clear();
        head = null;
        tail = null;
    }

    @Override
    public boolean remove(Object key, Object value) {
        boolean removed = super.remove(key, value);
        if (removed) {
            removeEntry(key);
        }
        return removed;
    }

    @Override
    public V remove(Object key) {
        V value = super.remove(key);
        if (value != null) {
            removeEntry(key);
        }
        return value;
    }

    @Override
    public boolean remove(Object key, Object value, int hash, Object keyValHash) {
        boolean removed = super.remove(key, value, hash, keyValHash);
        if (removed) {
            removeEntry(key);
        }
        return removed;
    }

    @Override
    public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
        super.replaceAll(function);
        Entry<K, V> entry = head;
        while (entry != null) {
            entry.value = function.apply(entry.key, entry.value);
            entry = entry.next;
        }
    }

    @Override
    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
        V value = super.computeIfAbsent(key, mappingFunction);
        if (value != null) {
            Entry<K, V> entry = findEntry(key);
            if (entry.value == null) {
                entry.value = value;
            }
        }
        return value;
    }

    @Override
    public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        V value = super.computeIfPresent(key, remappingFunction);
        if (value != null) {
            Entry<K, V> entry = findEntry(key);
            if (entry.value != null) {
                entry.value = value;
            }
        }
        return value;
    }

    @Override
    public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
        V value = super.compute(key, remappingFunction);
        if (value != null) {
            Entry<K, V> entry = findEntry(key);
            if (entry.value != null) {
                entry.value = value;
            }
        }
        return value;
    }

    @Override
    public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
        V mergedValue = super.merge(key, value, remappingFunction);
        if (mergedValue != null) {
            Entry<K, V> entry = findEntry(key);
            if (entry.value != null) {
                entry.value = mergedValue;
            }
        }
        return mergedValue;
    }

    @Override
    public void forEach(BiConsumer<? super K, ? super V> action) {
        Entry<K, V> entry = head;
        while (entry != null) {
            action.accept(entry.key, entry.value);
            entry = entry.next;
        }
    }

    private Entry<K, V> findEntry(K key) {
        for (Entry<K, V> entry = head; entry != null; entry = entry.next) {
            if (Objects.equals(key, entry.key)) {
                return entry;
            }
        }
        return null;
    }

    private void removeEntry(Object key) {
        Entry<K, V> entry = findEntry((K) key);
        if (entry != null) {
            if (entry.prev == null) {
                head = entry.next;
            } else {
                entry.prev.next = entry.next;
            }
            if (entry.next == null) {
                tail = entry.prev;
            } else {
                entry.next.prev = entry.prev;
            }
            entry.prev = null;
            entry.next = null;
        }
    }

    private static class Entry<K, V> {
        final K key;
        V value;
        Entry<K, V> prev;
        Entry<K, V> next;

        Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }
}

使用时,与普通HashMap类似,直接创建一个LinkedHashMap实例即可。

总结

在Java中,如果需要维护HashMap中元素的插入顺序,可以使用Java 8及以上版本提供的LinkedHashMap,或者自己实现一个类似于LinkedHashMap的数据结构。选择哪种方案,需要根据实际情况进行权衡。