`
zengdan2011
  • 浏览: 15751 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
最近访客 更多访客>>
社区版块
存档分类
最新评论

java容器类源码分析——LinkedHashMap

    博客分类:
  • Java
阅读更多
LinkedHashMap类似于HashMap,但是迭代遍历它时,取得“键值对”的顺序是插入次序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点;而在迭代访问时反而更快,因为它使用链表维护内部次序(HashMap是基于散列表实现的。
public class LinkedHashMap<K,V> extends HashMap<K,V> implements Map<K,V>

LinkedHashMap继承自HashMap并实现了Map接口。

     LinkedHashMap只定义了两个属性:
1 /**
 2      * The head of the doubly linked list.
 3      * 双向链表的头节点
 4      */
 5     private transient Entry<K,V> header;
 6     /**
 7      * The iteration ordering method for this linked hash map: true
 8      * for access-order, false for insertion-order.
 9      * true表示最近最少使用次序,false表示插入顺序
10      */
11     private final boolean accessOrder;

LinkedList一共提供了五个构造方法:
1 // 构造方法1,构造一个指定初始容量和负载因子的、按照插入顺序的LinkedList
 2 public LinkedHashMap(int initialCapacity, float loadFactor) {
 3     super(initialCapacity, loadFactor);
 4     accessOrder = false;
 5 }
 6 // 构造方法2,构造一个指定初始容量的LinkedHashMap,取得键值对的顺序是插入顺序
 7 public LinkedHashMap(int initialCapacity) {
 8     super(initialCapacity);
 9     accessOrder = false;
10 }
11 // 构造方法3,用默认的初始化容量和负载因子创建一个LinkedHashMap,取得键值对的顺序是插入顺序
12 public LinkedHashMap() {
13     super();
14     accessOrder = false;
15 }
16 // 构造方法4,通过传入的map创建一个LinkedHashMap,容量为默认容量(16)和(map.zise()/DEFAULT_LOAD_FACTORY)+1的较大者,装载因子为默认值
17 public LinkedHashMap(Map<? extends K, ? extends V> m) {
18     super(m);
19     accessOrder = false;
20 }
21 // 构造方法5,根据指定容量、装载因子和键值对保持顺序创建一个LinkedHashMap
22 public LinkedHashMap(int initialCapacity,
23              float loadFactor,
24                          boolean accessOrder) {
25     super(initialCapacity, loadFactor);
26     this.accessOrder = accessOrder;
27 }

从构造方法中可以看出,默认都采用插入顺序来维持取出键值对的次序。所有构造方法都是通过调用父类的构造方法来创建对象的。

     LinkedHashMap是基于双向链表的,而且属性中定了一个header节点,为什么构造方法都没有对其进行初始化呢?

     注意LinkedHashMap中有一个init()方法, HashMap的构造方法都调用了init()方法,这里LinkedHashMap的构造方法在调用父类构造方法后将从父类构造方法中调用init()方法(这也解释了为什么HashMap中会有一个没有内容的init()方法)。
1 void init() {
2         header = new Entry<K,V>(-1, null, null, null);
3         header.before = header.after = header;
4 }

看init()方法,的确是对header进行了初始化,并构造成一个双向循环链表(和LinkedList的存储结构是一样的)。

     transfer(HashMap.Entry[] newTable)方法和init()方法一样也在HashTable中被调用。transfer(HashMap.Entry[] newTable)方法在HashMap调用resize(int newCapacity)方法的时候被调用。
1 void transfer(HashMap.Entry[] newTable) {
2     int newCapacity = newTable.length;
3     for (Entry<K,V> e = header.after; e != header; e = e.after) {
4         int index = indexFor(e.hash, newCapacity);
5         e.next = newTable[index];
6         newTable[index] = e;
7     }
8 }

根据链表节点e的哈希值计算e在新容量的table数组中的索引,并将e插入到计算出的索引所引用的链表中。

     containsValue(Object value)
1 public boolean containsValue(Object value) {
 2     // Overridden to take advantage of faster iterator
 3     if (value==null) {
 4         for (Entry e = header.after; e != header; e = e.after)
 5             if (e.value==null)
 6                 return true;
 7     } else {
 8         for (Entry e = header.after; e != header; e = e.after)
 9                 if (value.equals(e.value))
10                     return true;
11     }
12     return false;
13 }

重写父类的containsValue(Object value)方法,直接通过header遍历链表判断是否有值和value相等,而不用查询table数组。

     get(Object key)
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);
6     return e.value;
7 }

get(Object key)方法通过HashMap的getEntry(Object key)方法获取节点,并返回该节点的value值,获取节点如果为null则返回null。recordAccess(HashMap<K,V> m)是LinkedHashMap的内部类Entry的一个方法,将在介绍Entry的时候进行详细的介绍。

     clear()
1 public void clear() {
2     super.clear();
3     header.before = header.after = header;
4 }

clear()方法先调用父类的方法clear()方法,之后将链表的header节点的before和after引用都指向header自身,即header节点就是一个双向循环链表。这样就无法访问到原链表中剩余的其他节点,他们都将被GC回收。

     以上的内容多多少少都涉及到了LinkedHashMap的内部类Entry<K,V>,下面详细介绍这个内部类。
1 // 这是一个私有的、静态的内部类,继承自HashMap的Entry。
 2 private static class Entry<K,V> extends HashMap.Entry<K,V> {
 3         // 对前后节点的引用
 4         Entry<K,V> before, after;
 5     // 构造方法直接调用父类的构造方法
 6     Entry(int hash, K key, V value, HashMap.Entry<K,V> next) {
 7         super(hash, key, value, next);
 8     }
 9 
10     // 移除该节点,只需修改前一节点的after引用和后一节点的before引用
11 private void remove() {
12     // 修改后该节点服务再被访问,会被GC回收
13         before.after = after;
14         after.before = before;
15     }
16 
17     // 在指定节点之前插入当前节点(双向链表插入节点的过程)
18 private void addBefore(Entry<K,V> existingEntry) {
19     // 将当前节点的after引用指向existingEntry
20         after  = existingEntry;
21         // 将before的引用指向existingEntry节点的前一节点
22 before = existingEntry.before;
23         // 将原先existingEntry节点的前一节点的after引用指向当前节点
24 before.after = this; 
25         // 将原先existingEntry节点的后一节点的before引用指向当前节点
26         after.before = this;
27     }
28 
29 // 当调用此类的get方法或put方法(put方法将调用到父类HashMap.Entry的put
30 // 方法)都将调用到recordAccess(HashMap<K,V> m)方法
31 // 如果accessOrder为true,即使用的是最近最少使用的次序,则将当前被修改的
32 // 节点移动到header节点之前,即链表的尾部。
33 // 这也是为什么在HashMap.Entry中有一个空的
34 // recordAccess(HashMap<K,V> m)方法的原因
35     void recordAccess(HashMap<K,V> m) {
36         LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
37         if (lm.accessOrder) {
38             lm.modCount++;
39             remove();
40             addBefore(lm.header);
41         }
42     }
43     // 和recordAccess(HashMap<K.V> m)方法一样,在HashMap.Entry中同样有一个对应的空方法。当进行删除(remove)操作的时候会被调用
44     void recordRemoval(HashMap<K,V> m) {
45         remove();
46     }
47 }

介绍完了内部类Entry,下面是创建一个Entry节点和添加一个Entry的两个方法。

     createEntry(int hash,K key,V value,int bucketIndex)
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     e.addBefore(header);
6     size++;
7 }

createEntry(int hash,K key,V value,int bucketIndex)方法覆盖了父类HashMap中的方法。这个方法不会拓展table数组的大小。该方法首先保留table中bucketIndex处的节点,然后调用Entry的构造方法(将调用到父类HashMap.Entry的构造方法)添加一个节点,即将当前节点的next引用指向table[bucketIndex] 的节点,之后调用的e.addBefore(header)是修改链表,将e节点添加到header节点之前。

     该方法同时在table[bucketIndex]的链表中添加了节点,也在LinkedHashMap自身的链表中添加了节点。

     addEntry(int hash, K key, V value, int bucketIndex)
1 void addEntry(int hash, K key, V value, int bucketIndex) {
 2     createEntry(hash, key, value, bucketIndex);
 3     Entry<K,V> eldest = header.after;
 4     if (removeEldestEntry(eldest)) {
 5         removeEntryForKey(eldest.key);
 6     } else {
 7         if (size >= threshold)
 8             resize(2 * table.length);
 9     }
10 }

首先调用createEntry(int hash,K key,V value,int bucketIndex)方法,之后获取LinkedHashMap中“最老”(最近最少使用)的节点,接着涉及到了removeEldestEntry(Entry<K,V> eldest)方法,来看一下:
1 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
2     return false;
3 }

为什么这个方法始终返回false?

     结合上面的addEntry(int hash,K key,V value,int bucketIndex)方法,这样设计可以使LinkedHashMap成为一个正常的Map,不会去移除“最老”的节点。

     为什么不在代码中直接去除这部分逻辑而是设计成这样呢?

     这为开发者提供了方便,若希望将Map当做Cache来使用,并且限制大小,只需继承LinkedHashMap并重写removeEldestEntry(Entry<K,V> eldest)方法,像这样:
1 private static final int MAX_ENTRIES = 100;
2 protected boolean removeEldestEntry(Map.Entry eldest) {
3      return size() > MAX_ENTRIES;
4 }

  LinkedHashMap除了以上内容外还有和迭代相关的三个方法及三个内部类以及一个抽象内部类,分别是:newKeyIterator()、newValueIterator()、newEntryIterator()和KeyIterator类、ValueIterator类、EntryIterator类以及LinkedHashIterator类。

     三个new方法分别返回对应的三个类的实例。而三个类都继承自抽象类LinkedHashIterator。下面看迭代相关的三个类。
1 private class KeyIterator extends LinkedHashIterator<K> {
2     public K next() { return nextEntry().getKey(); }
3 }
4 private class ValueIterator extends LinkedHashIterator<V> {
5     public V next() { return nextEntry().value; }
6 }
7 private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
8     public Map.Entry<K,V> next() { return nextEntry(); }
9 }

从上面可以看出这三个类都很简单,只有一个next()方法,next()方法也只是去调用LinkedHashIterator类中相应的方法。 和KeyIterator类、ValueIterator类、EntryIterator类以及LinkedHashIterator类。

     下面是LinkedHashIterator类的内容。
 1 private abstract class LinkedHashIterator<T> implements Iterator<T> {
 2     Entry<K,V> nextEntry    = header.after;
 3     Entry<K,V> lastReturned = null;
 4     // 和LinkedList中ListItr类定义了expectedModCount用途一致
 5     int expectedModCount = modCount;
 6     // 下一个节点如果是header节点说明当前节点是链表的最后一个节点,即已经遍历完链表了,没有下一个节点了
 7     public boolean hasNext() {
 8         return nextEntry != header;
 9     }
10     //移除上一次被返回的节点lastReturned 
11     public void remove() {
12         if (lastReturned == null)
13             throw new IllegalStateException();
14         if (modCount != expectedModCount)
15             throw new ConcurrentModificationException();
16         LinkedHashMap.this.remove(lastReturned.key);
17         lastReturned = null;
18         expectedModCount = modCount;
19     }
20     // 返回下一个节点
21     Entry<K,V> nextEntry() {
22         if (modCount != expectedModCount)
23             throw new ConcurrentModificationException();
24         if (nextEntry == header)
25             throw new NoSuchElementException();
26         // 获取并记录返回的节点
27         Entry<K,V> e = lastReturned = nextEntry;
28         // 保存对下一个节点的引用
29         nextEntry = e.after;
30         return e;
31     }
32 }

LinkedHashMap本应和HashMap及LinkedList一起分析,比较他们的异同。为了弥补,这里简单的总结一些他们之间的异同:

     HashMap使用哈希表来存储数据,并用拉链法来处理冲突。LinkedHashMap继承自HashMap,同时自身有一个链表,使用链表存储数据,不存在冲突。LinkedList和LinkedHashMap一样使用一个双向循环链表,但存储的是简单的数据,并不是“键值对”。所以HashMap和LinkedHashMap是Map,而LinkedList是一个List,这是他们本质的区别。LinkedList和LinkedHashMap都可以维护内容的顺序,但HashMap不维护顺序。
分享到:
评论

相关推荐

    Java集合框架源码分析之LinkedHashMap详解

    Java集合框架源码分析之LinkedHashMap详解 Java集合框架中的LinkedHashMap是HashMap的子类,它继承了HashMap的存储结构,但引入了一个双向链表的头结点,将所有put到LinkedHashMap的节点连接成一个双向循环链表,...

    Java源码解析——看优秀源码最能使人进步

    本文将对Java.lang.Object类、Java.lang.Integer类、Java.lang.String类、java.util.Arrays类、java.util.ArrayList类、java.util.LinkedList类...LinkedHashMap类、java.util.LinkedHashSet类等JDK源码进行详细的解析...

    java容器类研究与分析

    Java容器类,也称为集合类,是Java编程中用于存储和管理对象的重要工具。它们提供了比数组更加灵活和强大的功能,适用于各种复杂的数据结构需求。本文主要探讨Java容器类的基本概念、特点以及不同类型的容器。 首先...

    清华妹子的Java仓库(进阶学习路线)

    本仓库记录了我的Java学习进阶之路,涵盖了Java基础、JDK源码、JVM中的重要知识,附有代码和博客讲解,旨在...Java集合框架源码解读(3)——LinkedHashMap Java集合框架源码解读(4)——WeakHashMap Java集合框架源码解读

    java 集合类 容器类

    ### Java集合类与容器类详解 #### 一、引言 在Java编程中,集合类是一种非常重要的数据结构,用于存储一系列对象。相比于数组,集合类提供了更多的灵活性和功能,尤其是在处理未知数量的对象时更为方便。Java标准...

    Java容器类的教学实践与思考.pdf

    Java容器类是Java编程中的核心概念,主要用于存储和管理对象。在Java程序设计课程中,容器类的教学至关重要,因为它们提供了动态数据结构,使得开发者能够高效地组织和操作数据。然而,由于容器类的深入理解需要数据...

    Java 容器类源码详解 Set

    Java 容器类源码详解 Set Java 容器类源码详解 Set 是 Java 集合框架中的一种重要的集合类型,直接扩展自 Collection 接口。Set 表示由无重复对象组成的集合,在一个 Set 中,不能有两个引用指向同一个对象,或两个...

    java中HashMap,LinkedHashMap,TreeMap,HashTable的区别

    本文将详细分析四种常用的`Map`实现类:`HashMap`, `LinkedHashMap`, `TreeMap`以及`HashTable`之间的区别。 #### 1. HashMap `HashMap`是一种基于哈希表实现的`Map`接口,提供了一个非同步的、允许使用`null`键和...

    Java JDK 6学习笔记——ppt简体版

    这份"Java JDK 6学习笔记——ppt简体版"很可能是对这一版本特性和使用方法的详细讲解,旨在帮助初学者和有经验的开发者深入理解JDK 6的核心功能和改进。 JDK(Java Development Kit)是Java编程语言的软件开发工具...

    JAVA容器对象整理

    这些知识点仅仅是Java容器对象的一部分,实际的博客可能会包含更多细节,如源码分析、性能对比和最佳实践。通过阅读博客中的`持有对象.xmind`文件,可以进一步了解博主对这些概念的详细整理和分类。如果你对Java容器...

    Java集合系列之LinkedHashMap源码分析

    Java集合系列之LinkedHashMap源码分析 Java集合系列之LinkedHashMap源码分析是Java集合框架中的一部分,主要对LinkedHashMap的源码进行了详细的分析。LinkedHashMap是继承自HashMap的,它重新写了一个Entry,在原来...

    JavaLinkedHashMap源码解析Java开发Ja

    Java LinkedHashMap 是一个根据插入顺序或者访问顺序来维护元素顺序的哈希表与链表相结合的数据结构。在 Java 集合框架中,它继承自 HashMap 类,并实现了 Map 接口。LinkedHashMap 的特点在于,它不仅仅是一个哈希...

    JAVA-SE入门学习——第八讲集合

    HashMap、TreeMap、LinkedHashMap和Hashtable是Map接口的主要实现类。HashMap是无序的,允许null键和值;TreeMap是有序的,按键的自然排序或自定义比较器排序;LinkedHashMap保持插入顺序或访问顺序;Hashtable是...

    免费开源-【Java学习+面试指南】部分内容大部分是Java程序员所需要掌握的核心知识

    Java序列化详解泛型&通配符详解Java 引用机制详解Java代理模式详解BigDecimal 详细解Java 魔法类 Unsafe 详细解Java SPI 机制详解Java语法糖详解集合知识点/面试题总结:Java集合常见知识点&面试题总结(上)(必看...

    Java 容器.pdf_电子版pdf版

    源码分析 ArrayList 是一个基于动态数组实现的 List 实现类。它实现了 RandomAccess 接口,因此支持随机访问。ArrayList 的默认大小为 10,添加元素时使用 ensureCapacityInternal() 方法来保证容量足够,如果不够...

    Java集合类源码(摘自jr源码)

    在给定的压缩包文件中,包含了一些关键的集合类源码,如`TreeMap`、`Hashtable`、`ArrayList`、`HashMap`、`LinkedList`、`List`、`Map`、`TreeSet`、`LinkedHashMap`和`Set`。这些类都是Java集合框架的重要组成部分...

    剑指offer计划5(查找算法中等版)---java(csdn)————程序.pdf

    本文将深入探讨三个来自“剑指offer计划5(查找算法中等版)---java(csdn)”的题目,并提供相应的解法。 1. **二维数组中的查找**(剑指 Offer 04) 题目要求在给定的二维整数矩阵中查找目标值是否存在。这个...

    【Java学习+面试指南】 一份涵盖大部分Java程序员所需要掌握的核心知识

    项目相关 项目介绍 使用建议 贡献指南 常见问题 Java 基础 知识点/面试题总结 : (必看 ): Java 基础常见知识点&面试题总结(上) ...LinkedHashMap 核心源码分析 CopyOnWriteArrayList 核心源码分析

    java软件技术文档-深入java8的集合4:LinkedHashMap的实现原理.pdf

    它的内部节点(Entry 类)继承自 HashMap 的 Node 类,并增加了前后指针,形成一个双向链表。通过回调方法和 `accessOrder` 属性,LinkedHashMap 实现了动态维护顺序的功能,使得在遍历 Map 时可以保持一定的顺序,...

Global site tag (gtag.js) - Google Analytics