Java 集合 HashMap VS LinkedHashMap VS TreeMap

更多 Java 集合类方面的文章,请参见文集《Java 集合类》


共同点:

  • 都是线程不安全的
  • Fail-fast behavior

基本区别:

  • 迭代顺序:

    • HashMap :遍历时没有特定的顺序,从前到后遍历链表散列(数组 + 链表)
    • LinkedHashMap :遍历时按照插入的顺序或者访问的顺序。
    • TreeMap :遍历时按照 key 升序(通过 key 的 compareTo 方法进行比较)。
  • put/remove/get 时间复杂度

    • HashMap :O(1)
    • LinkedHashMap :O(1)
    • TreeMap :O(lgN)
  • 是否允许空的 key 和 value

    • HashMap :允许空的 key 和 value
    • LinkedHashMap :允许空的 key 和 value
    • TreeMap :只允许空的 value,key 不能为空
  • 实现方式

    • HashMap : buckets 链表散列
    • LinkedHashMap :double-linked buckets 双向链表散列
    • TreeMap :红黑树,迭代时做树的中序遍历

使用 HashMap:

Map<String, String> m = new HashMap<String, String>();
m.put("A", "AA");
m.put("C", "CC");
m.put("B", "BB");

m.forEach((k, v) -> System.out.print(k + " "));

打印:A B C

使用 LinkedHashMap:

Map<String, String> m = new LinkedHashMap<String, String>();
m.put("A", "AA");
m.put("C", "CC");
m.put("B", "BB");

m.forEach((k, v) -> System.out.print(k + " "));

打印:A C B

使用 TreeMap:

Map<String, String> m = new TreeMap<String, String>();
m.put("A", "AA");
m.put("C", "CC");
m.put("B", "BB");

m.forEach((k, v) -> System.out.print(k + " "));

打印:A B C

继承结构

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

public class LinkedHashMap<K,V>
    extends HashMap<K,V>
    implements Map<K,V>

public class TreeMap<K,V>
    extends AbstractMap<K,V>
    implements NavigableMap<K,V>, Cloneable, java.io.Serializable

LinkedHashMap 的实现

成员变量

LinkedHashMap 采用的 hash 算法和 HashMap 相同,但是它重新定义了数组中保存的元素 Entry,该 Entry 除了保存当前对象的引用外,还保存了其上一个元素 before 和下一个元素 after 的引用,从而在哈希表的基础上又构成了双向链接列表。

/**
 * 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);
    }
}

初始化

在 LinkedHashMap 的构造方法中,实际调用了父类 HashMap 的相关构造方法来构造一个底层存放的 table 数组,但额外可以增加 accessOrder 这个参数,如果不设置,默认为 false,代表按照插入顺序进行迭代;当然可以显式设置为 true,代表以访问顺序进行迭代。

/**
 * 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;
}

public LinkedHashMap(int initialCapacity,
                     float loadFactor,
                     boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

存储

LinkedHashMap 并未重写父类 HashMap 的 put 方法,而是重写了父类 HashMap 的 put 方法调用的子方法void recordAccess(HashMap m) ,void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。

读取

LinkedHashMap 重写了父类 HashMap 的 get 方法,实际在调用父类 getEntry() 方法取得查找的元素后,再判断当排序模式 accessOrder 为 true 时,记录访问顺序,将最新访问的元素添加到双向链表的表头,并从原来的位置删除。由于的链表的增加、删除操作是常量级的,故并不会带来性能的损失。

排序模式

LinkedHashMap 定义了排序模式 accessOrder。

  • 该属性为 boolean 型变量,对于访问顺序,为 true;
  • 对于插入顺序,则为 false;
  • 一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。

该哈希映射的迭代顺序就是最后访问其条目的顺序,这种映射很适合构建 LRU 缓存。参考 Java 集合 LinkedHashMap 与 LRU cache
LinkedHashMap 提供了 removeEldestEntry(Map.Entry<K,V> eldest) 方法。
该方法可以提供在每次添加新条目时移除最旧条目的实现程序,默认返回 false,这样,此映射的行为将类似于正常映射,即永远不能移除最旧的元素。

总结

其实 LinkedHashMap 几乎和 HashMap 一样:从技术上来说,不同的是它定义了一个 Entry<K,V> header,这个 header 不是放在 Table 里,它是额外独立出来的。
LinkedHashMap 通过继承 hashMap 中的 Entry<K,V>,并添加两个属性 Entry<K,V> before,after,和 header 结合起来组成一个双向链表,来实现按插入顺序或访问顺序排序。


引用:
LinkedHashMap 的实现原理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容