LinkedHashMap

LinkedHashMap内部採用了散列表和链表实现Map接口,并能够保证迭代的顺序,和HashMap不同,其内部维护一个指向全部元素的双向链表,其决定了遍历的顺序,一般是元素插入的顺序进行迭代,只是元素又一次插入顺序不会受到影响。

LinkedHashMap提供一个特殊的构造函数,实现了每次迭代返回近期使用的元素,这个特性能够用于构建LRU缓存。

此外removeEldestEntry(Map.Entry)方法能够被子类覆盖用于推断在加入元素的时候什么时候能够删除元素。

LinkedHashMap性能相同受到初始容量和装填因子的影响,对于基本操作(add,contains,remove)在常数时间内,其性能比HashMap略微低。因为须要额外代价维护链表;只是其遍历性能为O(size)高于HashMapO(capacity)。

LinkedHashMap实现

类定义

直接继承了HashMap

  1. public class LinkedHashMap<K,V>
  2. extends HashMap<K,V>
  3. implements Map<K,V>

成员

  1. private transient Entry<K,V> header; //用于遍历的双向链表表头
  2. /**
  3. * The iteration ordering method for this linked hash map:
  4. * true: for access-order, false: for insertion-order
  5. */
  6. private final boolean accessOrder;

Entry内部类继承了HashMap.Entry<K,V>类,添加了两个指针before和after用于维护遍历顺序,实际上Entry有三个指针父类本身有个next指针用于当发生元素冲突时指向的下一个元素。

由此能够看出用于遍历的双向链表直接加在Entry上面,这样有效节约了空间,实际仅仅比HashMap多了2*size个引用+1个头结点空间消耗。

before和after这两个引用在外部类调用put或remove时,调用其相关方法进行维护(recordAccess和recordRemoval等)。

  1. private static class Entry<K,V> extends HashMap.Entry<K,V> {
  2. Entry<K,V> before, after;
  3. Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
  4. super(hash, key, value, next);
  5. }
  6. private void remove() {
  7. before.after = after;
  8. after.before = before;
  9. }
  10. //existingEntry之前加入当前节点
  11. private void addBefore(Entry<K,V> existingEntry) {
  12. after = existingEntry;
  13. before = existingEntry.before;
  14. before.after = this;
  15. after.before = this;
  16. }
  17. //由父类HashMap的put方法调用,若是acessOrder。则加入到双向链表的头结点后面;本身get方法也会触发调用
  18. void recordAccess(HashMap<K,V> m) {
  19. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
  20. if (lm.accessOrder) {
  21. lm.modCount++;
  22. remove();
  23. addBefore(lm.header);
  24. }
  25. }
  26. //有元素删除时,调用该方法
  27. void recordRemoval(HashMap<K,V> m) {
  28. remove();
  29. }
  30. }

put方法是继承自父类HashMap,重写了当须要加入元素时候调用的addEntry方法,相同是在相应桶的链表头结点后面加入。加入完以后不是直接进行resize推断,而是推断是否要删除旧的元素,这种方法默认返回false,用户能够重写这种方法用于确定缓存的淘汰机制。

  1. void addEntry(int hash, K key, V value, int bucketIndex) {
  2. createEntry(hash, key, value, bucketIndex);
  3. // Remove eldest entry if instructed, else grow capacity if appropriate
  4. Entry<K,V> eldest = header.after;
  5. if (removeEldestEntry(eldest)) {
  6. removeEntryForKey(eldest.key);
  7. } else {
  8. if (size >= threshold)
  9. resize(2 * table.length);
  10. }
  11. }
  12. void createEntry(int hash, K key, V value, int bucketIndex) {
  13. HashMap.Entry<K,V> old = table[bucketIndex];
  14. Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
  15. table[bucketIndex] = e;
  16. e.addBefore(header); //每次都是在相应桶链表的開始处加入
  17. size++;
  18. }
  19. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  20. return false;
  21. }

get方法

  1. public V get(Object key) {
  2. Entry<K,V> e = (Entry<K,V>)getEntry(key);
  3. if (e == null)
  4. return null;
  5. e.recordAccess(this); //实现LRU
  6. return e.value;
  7. }
  8. //重写父类方法,效率更高O(size)
  9. public boolean containsValue(Object value) {
  10. // Overridden to take advantage of faster iterator
  11. if (value==null) {
  12. for (Entry e = header.after; e != header; e = e.after)
  13. if (e.value==null)
  14. return true;
  15. } else {
  16. for (Entry e = header.after; e != header; e = e.after)
  17. if (value.equals(e.value))
  18. return true;
  19. }
  20. return false;
  21. }

视图和迭代器

LinkedHashMap相同继承了创建3个集合类视图:键集合、值集合、键值对集合的方法。因为额外维护一个双向链表保证迭代顺序,重写了相关视图的迭代器实现,LinkedHashIterator通过直接迭代链表的header指针来实现指定顺序遍历。

  1. Iterator<K> newKeyIterator() { return new KeyIterator(); }
  2. Iterator<V> newValueIterator() { return new ValueIterator(); }
  3. Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }
  4. private class KeyIterator extends LinkedHashIterator<K> {
  5. public K next() { return nextEntry().getKey(); }
  6. }
  7. private class ValueIterator extends LinkedHashIterator<V> {
  8. public V next() { return nextEntry().value; }
  9. }
  10. private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
  11. public Map.Entry<K,V> next() { return nextEntry(); }
  12. }
  13. private abstract class LinkedHashIterator<T> implements Iterator<T> {
  14. Entry<K,V> nextEntry = header.after;
  15. Entry<K,V> lastReturned = null;
  16. int expectedModCount = modCount;
  17. public boolean hasNext() {
  18. return nextEntry != header;
  19. }
  20. public void remove() {
  21. if (lastReturned == null)
  22. throw new IllegalStateException();
  23. if (modCount != expectedModCount)
  24. throw new ConcurrentModificationException();
  25. LinkedHashMap.this.remove(lastReturned.key);
  26. lastReturned = null;
  27. expectedModCount = modCount;
  28. }
  29. Entry<K,V> nextEntry() {
  30. if (modCount != expectedModCount)
  31. throw new ConcurrentModificationException();
  32. if (nextEntry == header)
  33. throw new NoSuchElementException();
  34. Entry<K,V> e = lastReturned = nextEntry;
  35. nextEntry = e.after;
  36. return e;
  37. }
  38. }

总结

  1. LinkedHashMap继承自HashMap。相关基本操作性能略低于HashMap。因为须要额外代价维护链表。其遍历操作是通过操作该双向链表实现,而非内部散列表数组。因此性能为O(size)比HashMapO(capacity)更高。
  2. 支持两种顺序遍历:元素插入顺序(反复put不算)和近期使用优先顺序(调用put和get类似LRU),默认是依照元素插入顺序遍历。通过构造函数传入true能够实现近期使用优先遍历。每次put或get操作时,将该元素直接又一次放置到链表头结点后面来实现近期使用优先遍历。

  3. LinkedHashMap并没有又一次创建一个新的链表来实现顺序遍历,而是在每一个Entry上多加了两个指针来决定遍历顺序。有效节约了空间消耗。

    实际仅仅比HashMap多了2*size个引用+1个头结点空间消耗。

  4. LinkedHashMap支持元素淘汰策略,能够通过重写removeEldestEntry方法。来决定调用put时候是否须要删除旧的元素。LinkedHashMap能够用于实现LRU缓存,并自己定义元素淘汰策略。

LinkedHashMap源代码阅读的更多相关文章

  1. Mongodb源代码阅读笔记:Journal机制

    Mongodb源代码阅读笔记:Journal机制 Mongodb源代码阅读笔记:Journal机制 涉及的文件 一些说明 PREPLOGBUFFER WRITETOJOURNAL WRITETODAT ...

  2. 【转】Tomcat总体结构(Tomcat源代码阅读系列之二)

    本文是Tomcat源代码阅读系列的第二篇文章,我们在本系列的第一篇文章:在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码一文中介绍了如何在intelliJ IDEA 和 Ec ...

  3. 利用doxygen提高源代码阅读效率

    阅读开源项目的源代码是提高自己编程能力的好方法,而有一个好的源代码阅读工具无疑能够让你在阅读源代码时事半功倍.之前找过不少源代码阅读工具,像SourceInsight.sourcenav.scitoo ...

  4. CI框架源代码阅读笔记5 基准測试 BenchMark.php

    上一篇博客(CI框架源代码阅读笔记4 引导文件CodeIgniter.php)中.我们已经看到:CI中核心流程的核心功能都是由不同的组件来完毕的.这些组件类似于一个一个单独的模块,不同的模块完毕不同的 ...

  5. 淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式

    淘宝数据库OceanBase SQL编译器部分 源代码阅读--Schema模式 什么是Database,什么是Schema,什么是Table,什么是列,什么是行,什么是User?我们能够能够把Data ...

  6. CI框架源代码阅读笔记3 全局函数Common.php

    从本篇開始.将深入CI框架的内部.一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说.全局函数具有最高的载入优先权.因此大多数的框架中BootStrap ...

  7. [C++ 2011 STL (VS2012 Update4) 源代码阅读系列(2)]熟悉一些宏定义和模版偏特化或叫模版专门化

    [C++ 2011 STL (VS2012 Update4) 源代码阅读系列(2)]熟悉一些宏定义和模版偏特化或叫模版专门化 // point_test.cpp : 知识点练习和测试,用于单步调试,跟 ...

  8. 【转】Tomcat源代码阅读系列

    在IntelliJ IDEA 和 Eclipse运行tomcat 7源代码(Tomcat源代码阅读系列之一) Tomcat总体结构(Tomcat源代码阅读系列之二) Tomcat启动过程(Tomcat ...

  9. 【Java集合源代码剖析】LinkedHashmap源代码剖析

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/37867985 前言:有网友建议分析下LinkedHashMap的源代码.于是花了一晚上时间 ...

随机推荐

  1. 363 Max Sum of Rectangle No Larger Than K 最大矩阵和不超过K

    Given a non-empty 2D matrix matrix and an integer k, find the max sum of a rectangle in the matrix s ...

  2. python--11、协程

    协程,又称微线程,纤程.英文名Coroutine. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕. 所以子程 ...

  3. JS高级——逻辑中断

    1.表达式1||表达式2:表达式1为真,返回表达式1:表达式1为假,返回表达式2 2.表达式1&&表达2:表达式1为真,返回表达式2:表达式1为假,返回表达式1

  4. JS——回调函数

    1.回调函数作为参数的形式进行使用 2.回调函数一定程度上达到了解耦效果(模块化.功能化) <script> console.log(op(1, 2, add)); console.log ...

  5. hibernate Hql 更新的参数只能设置String类型

    最近在项目中发现一个奇怪的现象,请看下面的代码 实体类MyEmployeeEntity @Table(name="myemployee") public class MyEmplo ...

  6. C# 定时无操作则退出登陆,回到登陆界面。

    有时候根据需求需要为程序添加在规定的时间内无操作则退出当前的登陆程序的功能,如下代码模拟描述的需求功能. using System; using System.Collections.Generic; ...

  7. Zabbix自带的mysql监控模块

    Zabbix自带的mysql监控模块 [root@Cagios zabbix-]# cp conf/zabbix_agentd/userparameter_mysql.conf /usr/local/ ...

  8. GatewayWorker + LayIM实现即时聊天

    一.程序目录结构 二.代码展示 附LayIM开发文档:https://www.layui.com/doc/modules/layim.html 1.前端代码 <!DOCTYPE html> ...

  9. 在centos安装 sql server

    主要参考官方文档https://docs.microsoft.com/en-us/sql/linux/sql-server-linux-setup-red-hat

  10. 谷歌浏览器中a:link设置字体颜色无效问题

    <div id="box"> <a href="#">111111</a> <a href=""& ...