转自:LinkedHashMap介绍

转载:http://uule.iteye.com/blog/1522291

jdk1.7API文档链接:http://tool.oschina.net/apidocs/apidoc?api=jdk_7u4

HashMap:

put -> addEntry(新建一个Entry)

get

getEntry

LinkedHashMap:

put -> addEntry(重写)

新建一个Entry,然后将其加入header前

e.addBefore(header)

get -> 调用HashMap的getEntry - recordAccess(重写)

HashMap的get与getEntry

  1. final Entry<K,V> getEntry(Object key) {
  2. int hash = (key == null) ? 0 : hash(key.hashCode());
  3. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  4. e != null;
  5. e = e.next) {
  6. Object k;
  7. if (e.hash == hash &&
  8. ((k = e.key) == key || (key != null && key.equals(k))))
  9. return e;
  10. }
  11. return null;
  12. }
  13. public V get(Object key) {
  14. if (key == null)
  15. return getForNullKey();
  16. int hash = hash(key.hashCode());
  17. for (Entry<K,V> e = table[indexFor(hash, table.length)];
  18. e != null;
  19. e = e.next) {
  20. Object k;
  21. if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
  22. return e.value;
  23. }
  24. return null;
  25. }

1. LinkedHashMap概述:

LinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。

LinkedHashMap是Map接口的哈希表和链接列表实现,具有可预知的迭代顺序。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
   LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
   注意,此实现不是同步的。如果多个线程同时访问链接的哈希映射,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。

根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。

默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。  可以重写removeEldestEntry方法返回true值指定插入元素时移除最老的元素。

2. LinkedHashMap的实现:

对于LinkedHashMap而言,它继承与HashMap、底层使用哈希表与双向链表来保存所有元素。其基本操作与父类HashMap相似,它通过重写父类相关的方法,来实现自己的链接列表特性。下面我们来分析LinkedHashMap的源代码:

类结构:

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

1) 成员变量:

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

  1. //true表示按照访问顺序迭代,false时表示按照插入顺序
  2. private final boolean accessOrder;
  1. /**
  2. * 双向链表的表头元素。
  3. */
  4. private transient Entry<K,V> header;
  5. /**
  6. * LinkedHashMap的Entry元素。
  7. * 继承HashMap的Entry元素,又保存了其上一个元素before和下一个元素after的引用。
  8. */
  9. private static class Entry<K,V> extends HashMap.Entry<K,V> {
  10. Entry<K,V> before, after;
  11. ……
  12. }

HashMap.Entry:

  1. static class Entry<K,V> implements Map.Entry<K,V> {
  2. final K key;
  3. V value;
  4. Entry<K,V> next;
  5. final int hash;
  6. Entry(int h, K k, V v, Entry<K,V> n) {
  7. value = v;
  8. next = n;
  9. key = k;
  10. hash = h;
  11. }
  12. }

2) 初始化:

通过源代码可以看出,在LinkedHashMap的构造方法中,实际调用了父类HashMap的相关构造方法来构造一个底层存放的table数组。如:

  1. public LinkedHashMap(int initialCapacity, float loadFactor) {
  2. super(initialCapacity, loadFactor);
  3. accessOrder = false;
  4. }

HashMap中的相关构造方法:

  1. public HashMap(int initialCapacity, float loadFactor) {
  2. if (initialCapacity < 0)
  3. throw new IllegalArgumentException("Illegal initial capacity: " +
  4. initialCapacity);
  5. if (initialCapacity > MAXIMUM_CAPACITY)
  6. initialCapacity = MAXIMUM_CAPACITY;
  7. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  8. throw new IllegalArgumentException("Illegal load factor: " +
  9. loadFactor);
  10. // Find a power of 2 >= initialCapacity
  11. int capacity = 1;
  12. while (capacity < initialCapacity)
  13. capacity <<= 1;
  14. this.loadFactor = loadFactor;
  15. threshold = (int)(capacity * loadFactor);
  16. table = new Entry[capacity];
  17. init();
  18. }

我们已经知道LinkedHashMap的Entry元素继承HashMap的Entry,提供了双向链表的功能。在上述HashMap的构造器中,最后会调用init()方法,进行相关的初始化,这个方法在HashMap的实现中并无意义,只是提供给子类实现相关的初始化调用。
   LinkedHashMap重写了init()方法,在调用父类的构造方法完成构造后,进一步实现了对其元素Entry的初始化操作。

  1. void init() {
  2. header = new Entry<K,V>(-1, null, null, null);
  3. header.before = header.after = header;
  4. }

3) 存储:

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),提供了自己特有的双向链接列表的实现。

HashMap.put:

  1. public V put(K key, V value) {
  2. if (key == null)
  3. return putForNullKey(value);
  4. int hash = hash(key.hashCode());
  5. int i = indexFor(hash, table.length);
  6. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  7. Object k;
  8. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  9. V oldValue = e.value;
  10. e.value = value;
  11. e.recordAccess(this);
  12. return oldValue;
  13. }
  14. }
  15. modCount++;
  16. addEntry(hash, key, value, i);
  17. return null;
  18. }

重写方法:

  1. void recordAccess(HashMap<K,V> m) {
  2. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
  3. if (lm.accessOrder) {
  4. lm.modCount++;
  5. remove();
  6. addBefore(lm.header);
  7. }
  8. }
  1. void addEntry(int hash, K key, V value, int bucketIndex) {
  2. // 调用create方法,将新元素以双向链表的的形式加入到映射中。
  3. createEntry(hash, key, value, bucketIndex);
  4. // 删除最近最少使用元素的策略定义
  5. Entry<K,V> eldest = header.after;
  6. if (removeEldestEntry(eldest)) {
  7. removeEntryForKey(eldest.key);
  8. } else {
  9. if (size >= threshold)
  10. resize(2 * table.length);
  11. }
  12. }
  1. void createEntry(int hash, K key, V value, int bucketIndex) {
  2. HashMap.Entry<K,V> old = table[bucketIndex];
  3. Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
  4. table[bucketIndex] = e;
  5. // 调用元素的addBrefore方法,将元素加入到哈希、双向链接列表。
  6. e.addBefore(header);
  7. size++;
  8. }
  1. private void addBefore(Entry<K,V> existingEntry) {
  2. after  = existingEntry;
  3. before = existingEntry.before;
  4. before.after = this;
  5. after.before = this;
  6. }

4) 读取:

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

HashMap.containsValue:

  1. public boolean containsValue(Object value) {
  2. if (value == null)
  3. return containsNullValue();
  4. Entry[] tab = table;
  5. for (int i = 0; i < tab.length ; i++)
  6. for (Entry e = tab[i] ; e != null ; e = e.next)
  7. if (value.equals(e.value))
  8. return true;
  9. return false;
  10. }
  1. /*查找Map中是否包含给定的value,还是考虑到,LinkedHashMap拥有的双链表,在这里Override是为了提高迭代的效率。
  2. */
  3. public boolean containsValue(Object value) {
  4. // Overridden to take advantage of faster iterator
  5. if (value==null) {
  6. for (Entry e = header.after; e != header; e = e.after)
  7. if (e.value==null)
  8. return true;
  9. } else {
  10. for (Entry e = header.after; e != header; e = e.after)
  11. if (value.equals(e.value))
  12. return true;
  13. }
  14. return false;
  15. }
  1. /*该transfer()是HashMap中的实现:遍历整个表的各个桶位,然后对桶进行遍历得到每一个Entry,重新hash到newTable中,
  2. //放在这里是为了和下面LinkedHashMap重写该法的比较,
  3. void transfer(Entry[] newTable) {
  4. Entry[] src = table;
  5. int newCapacity = newTable.length;
  6. for (int j = 0; j < src.length; j++) {
  7. Entry<K,V> e = src[j];
  8. if (e != null) {
  9. src[j] = null;
  10. do {
  11. Entry<K,V> next = e.next;
  12. int i = indexFor(e.hash, newCapacity);
  13. e.next = newTable[i];
  14. newTable[i] = e;
  15. e = next;
  16. } while (e != null);
  17. }
  18. }
  19. }
  20. */
  21. /**
  22. *transfer()方法是其父类HashMap调用resize()的时候调用的方法,它的作用是表扩容后,把旧表中的key重新hash到新的表中。
  23. *这里从写了父类HashMap中的该方法,是因为考虑到,LinkedHashMap拥有的双链表,在这里Override是为了提高迭代的效率。
  24. */
  25. void transfer(HashMap.Entry[] newTable) {
  26. int newCapacity = newTable.length;
  27. for (Entry<K, V> e = header.after; e != header; e = e.after) {
  28. int index = indexFor(e.hash, newCapacity);
  29. e.next = newTable[index];
  30. newTable[index] = e;
  31. }
  32. }
  1. public V get(Object key) {
  2. // 调用父类HashMap的getEntry()方法,取得要查找的元素。
  3. Entry<K,V> e = (Entry<K,V>)getEntry(key);
  4. if (e == null)
  5. return null;
  6. // 记录访问顺序。
  7. e.recordAccess(this);
  8. return e.value;
  9. }
  1. void recordAccess(HashMap<K,V> m) {
  2. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
  3. // 如果定义了LinkedHashMap的迭代顺序为访问顺序,
  4. // 则删除以前位置上的元素,并将最新访问的元素添加到链表表头。
  5. if (lm.accessOrder) {
  6. lm.modCount++;
  7. remove();
  8. addBefore(lm.header);
  9. }
  10. }
  1. /**
  2. * Removes this entry from the linked list.
  3. */
  4. private void remove() {
  5. before.after = after;
  6. after.before = before;
  7. }
  1. /**clear链表,设置header为初始状态*/
  2. public void clear() {
  3. super.clear();
  4. header.before = header.after = header;
  5. }

5) 排序模式:

LinkedHashMap定义了排序模式accessOrder,该属性为boolean型变量,对于访问顺序,为true;对于插入顺序,则为false。

  1. private final boolean accessOrder;

一般情况下,不必指定排序模式,其迭代顺序即为默认为插入顺序。看LinkedHashMap的构造方法,如:

  1. public LinkedHashMap(int initialCapacity, float loadFactor) {
  2. super(initialCapacity, loadFactor);
  3. accessOrder = false;
  4. }

这些构造方法都会默认指定排序模式为插入顺序。如果你想构造一个LinkedHashMap,并打算按从近期访问最少到近期访问最多的顺序(即访问顺序)来保存元素,那么请使用下面的构造方法构造LinkedHashMap:

  1. public LinkedHashMap(int initialCapacity,
  2. float loadFactor,
  3. boolean accessOrder) {
  4. super(initialCapacity, loadFactor);
  5. this.accessOrder = accessOrder;
  6. }

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

当有新元素加入Map的时候会调用Entry的addEntry方法,会调用removeEldestEntry方法,这里就是实现LRU元素过期机制的地方,默认的情况下removeEldestEntry方法只返回false表示元素永远不过期。

  1. /**
  2. * This override alters behavior of superclass put method. It causes newly
  3. * allocated entry to get inserted at the end of the linked list and
  4. * removes the eldest entry if appropriate.
  5. */
  6. void addEntry(int hash, K key, V value, int bucketIndex) {
  7. createEntry(hash, key, value, bucketIndex);
  8. // Remove eldest entry if instructed, else grow capacity if appropriate
  9. Entry<K,V> eldest = header.after;
  10. if (removeEldestEntry(eldest)) {
  11. removeEntryForKey(eldest.key);
  12. } else {
  13. if (size >= threshold)
  14. resize(2 * table.length);
  15. }
  16. }
  17. /**
  18. * This override differs from addEntry in that it doesn't resize the
  19. * table or remove the eldest entry.
  20. */
  21. void createEntry(int hash, K key, V value, int bucketIndex) {
  22. HashMap.Entry<K,V> old = table[bucketIndex];
  23. Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
  24. table[bucketIndex] = e;
  25. e.addBefore(header);
  26. size++;
  27. }
  28. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
  29. return false;
  30. }

此方法通常不以任何方式修改映射,相反允许映射在其返回值的指引下进行自我修改。如果用此映射构建LRU缓存,则非常方便,它允许映射通过删除旧条目来减少内存损耗。

例如:重写此方法,维持此映射只保存100个条目的稳定状态,在每次添加新条目时删除最旧的条目。

  1. private static final int MAX_ENTRIES = 100;
  2. protected boolean removeEldestEntry(Map.Entry eldest) {
  3. return size() > MAX_ENTRIES;
  4. }

分类: Java

LinkedHashMap介绍相关推荐

  1. 有序的Map集合--LinkedHashMap

    提出问题: 在写一个dao的时候,我的需求是这个dao是一个万能的,目前的方法只有一个查询出实体类对应的表中所有的数据,通过传入的对象,利用反射获取实体类中的属性名,属性类型,利用字符串拼接获取相应属 ...

  2. Java—Map集合详解(HashMap/Hashtable/LinkedHashMap/Properties/TreeMap/WeakHashMap/IdentityHashMap/EnumMap)

    关注微信公众号:CodingTechWork,一起学习进步. Map Map集合介绍   Map(也称为字典.关联数组)是用于保存具有映射关系的数据,保存两组值,key和value,这两组值可以是任何 ...

  3. Java 基础 - 各项集合实现

    [toc] Java 类库中的集合接口和迭代器 集合接口及迭代器 集合类的基本接口是:Collection public interface Collection<E>{// 集合改变返回 ...

  4. c++ map iterator 获取key_JAVA | Map集合使用详解

    引言   了解Set集合如何使用和旗下各类比较,这篇我们继续和大家一起看看Map集合的使用机制. Map Map集合介绍   Map(也称为字典.关联数组)是用于保存具有映射关系的数据,保存两组值,k ...

  5. 转:LruCache算法

    目录 1. 简介 下面,将详细介绍 LrhCache算法 2. LruCache算法 3. 实现原理 LrhCache算法的算法核心 = LRU 算法 + LinkedHashMap数据结构 下面,我 ...

  6. Java双列集合之Map以及斗地主案列

    Map集合 知识点-- 概述 讲解 图文演示 现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射.Java提供了专门的 ...

  7. LruCache的深入解析

    LruCache的深入解析 大家在学习图片多级缓存的时候,肯定都会接触过LruCache这个类,本片文章对LruCache进行一个独特的,深入的分析.主要分为以下两点 LinkedHashMap介绍 ...

  8. 大数据学习day16-Set接口、Map、可变参数

    大数据学习day16 Set接口.Map.可变参数 今日内容 Set接口中hashSet LinkedHashSet Map集合特点 Map集合的遍历方式 方法可变参数 斗地主案例排序 总结 第一章 ...

  9. Map集合概述、冒泡排序

    Map集合 1.1 概述 Map集合作为最常用的集合之一,在现实生活中,我们通常会看到IP地址与主机,身份证与个人,丈夫与妻子,这种一一对应的关系,我们通常也称之为映射,所以,java专门有一个存储这 ...

  10. Java基础学习系列--(六)【Map集合,HashMapTreeMap,斗地主、图书管理系统,排序算法】

    第一章 Map集合 1.1 概述 现实生活中,我们常会看到这样的一种集合:IP地址与主机名,身份证号与个人,系统用户名与系统用户对象等,这种一一对应的关系,就叫做映射.Java提供了专门的集合类用来存 ...

最新文章

  1. 在析构函数中delete this指针问题
  2. python argparse举例说明
  3. 500万相机芯片尺寸_华硕ZenFone Live L2推出500万像素自拍照相机,电池容量3000毫安...
  4. 在中国Windows Azure服务中创建应用程序的一些不同之处
  5. 13.2.3 视图层——视图函数
  6. 转载的SSO文章,很基础
  7. 文献记录(part90)--A novel density-based clustering algorithm using nearest neighbor graph
  8. Android之adb jdwp获取debug版本app的进程Id
  9. Oracle入门(三)之连接与登录
  10. linux mysql数据库备份并删除前一分钟的数据
  11. 跨湖跨仓场景下如何实现海量数据分钟级分析
  12. 2021年5月9日,是第108个母亲节,祝福所有的母亲节日快乐
  13. 马斯克辞任董事长,罚款2000万美元,钢铁侠一夜服软
  14. 再见了SpringMVC!这个框架有点厉害,甚至干掉了Servlet!
  15. Codeforces 1027C. Minimum Value Rectangle
  16. 简单的一个百度注册界面
  17. 2022泰迪杯a题害虫检测
  18. 神策杯 2018高校算法大师赛(个人、top2、top6)方案总结
  19. 2022美亚杯第八届中国电子数据取证大赛-个人赛write up详解,软件就用弘连和美亚,尽量写的细致一点。建议入门看,仅为了解题,没有专业精神。专业选手去看后面推荐的两篇解析,都是大佬。
  20. zynq 7000 clg400 可作为外部AD 的引脚列表

热门文章

  1. EthBox以太坊开发套件,一键安装部署以太坊开发环境
  2. 第三百四十四节,Python分布式爬虫打造搜索引擎Scrapy精讲—craw母版l创建自动爬虫文件—以及 scrapy item loader机制...
  3. 采用C#泛型实现状态(State)模式
  4. Google Play在GDC大会上发布全新工具及游戏
  5. JAVA内部类(一)
  6. Linux Netcat command – The swiss army knife of net
  7. linux tricks 之数据对齐。
  8. jQuery选择器--总结
  9. 自己写的一个分享按钮的插件(可扩展,内附开发制作流程)
  10. java 句柄无效_sql报句柄无效。 (异常来自 HRESULT:0x80070006 (E_HANDLE))