您的位置:首页 > 其它

自己动手写写:LinkedHashMap源码浅析

2012-04-18 10:55 351 查看
此系列文章中,上一篇是关于HashMap的源码剖析,这篇文章将向大家剖析一下LinkedHashMap的源码!



四. LinkedHashMap



我们知道从API的描述中可以看出HashMap与LinkedHashMap最大的不同在于,后者维护者一个运行于所有条目的双向链表。有了这个双向链表,就可以在迭代的时候按照插入的顺序迭代出元素(当然也可以通过LRU算法迭代元素,下面会讲到)。



1. 类结构

Java代码



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



我们可以看出LinkedHashMap继承了HashMap!



2. 几个重要的成员变量

Java代码



/**

* The head of the doubly linked list.

*/

private transient Entry<K, V> header;



/**

* The iteration ordering method for this linked hash map: <tt>true</tt>

* for access-order, <tt>false</tt> for insertion-order.

*

* @serial

*/

private final boolean accessOrder;



对于父类HashMap中的成员变量这里就不讲了,可参考http://boy00fly.iteye.com/blog/1139845

其中Entry类是LinkedHashMap的内部类定义,代码片段如下:

Java代码



//继承了HashMap.Entry

private static class Entry<K, V> extends HashMap.Entry<K, V>

//新增的两个成员变量

Entry<K, V> before, after;



可以看得出来新增的这两个变量就是双向链表实现的关键!!分别指向当前Entry的前一个、后一个Entry。



header就是指向双向链表的头部!

accessOrder:true则按照LRU算法迭代整个LinkedHashMap,false则按照元素的插入顺序迭代!



3. 几个重要的构造函数

Java代码



/**

* Constructs an empty <tt>LinkedHashMap</tt> instance with the

* specified initial capacity, load factor and ordering mode.

*

* @param initialCapacity the initial capacity

* @param loadFactor the load factor

* @param accessOrder the ordering mode - <tt>true</tt> for

* access-order, <tt>false</tt> for insertion-order

* @throws IllegalArgumentException if the initial capacity is negative

* or the load factor is nonpositive

*/

public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)

{

super(initialCapacity, loadFactor);

this.accessOrder = accessOrder;

}

是不是很简单,也可参考http://boy00fly.iteye.com/blog/1139845这篇文章的构造函数分析。其中accessOrder就是我们上面所讲的控制迭代顺序的开关,只有此构造函数有这个参数,其他的构造函数默认就是false。



4. 几个重要的方法

Java代码



/**

* Called by superclass constructors and pseudoconstructors (clone,

* readObject) before any entries are inserted into the map. Initializes

* the chain.

*/

void init()

{

header = new Entry<K, V>(-1, null, null, null);

header.before = header.after = header;

}

此方法是在父类构造函数初始化的时候调用的,LinkedHashMap重写了init方法。代码中表达的意思也很明确了,这是双向链表的初始化状态。







Java代码



/**

* This override alters behavior of superclass put method. It causes newly

* allocated entry to get inserted at the end of the linked list and

* removes the eldest entry if appropriate.

*/

void addEntry(int hash, K key, V value, int bucketIndex)

{

createEntry(hash, key, value, bucketIndex);



// Remove eldest entry if instructed, else grow capacity if appropriate

Entry<K, V> eldest = header.after;

if (removeEldestEntry(eldest))

{

removeEntryForKey(eldest.key);

}

else

{

if (size >= threshold)

resize(2 * table.length);

}

}



/**

* This override differs from addEntry in that it doesn't resize the

* table or remove the eldest entry.

*/

void createEntry(int hash, K key, V value, int bucketIndex)

{

HashMap.Entry<K, V> old = table[bucketIndex];

Entry<K, V> e = new Entry<K, V>(hash, key, value, old);

table[bucketIndex] = e;

e.addBefore(header);

size++;

}



/**

* Inserts this entry before the specified existing entry in the list.

*/

private void addBefore(Entry<K, V> existingEntry)

{

after = existingEntry;

before = existingEntry.before;

before.after = this;

after.before = this;

}



addEntry方法是父类HashMap的一个方法,被put相关的方法所调用即在新增元素的时候调用。

我们通过形象的图来看一个基本的流程:

(从初始化到添加了3个元素过程中,各个元素before、after引用变化,画的有点丑,呵呵,不过意思能够表达清楚,代码的内容就不再累述了,大体和HashMap类似!)















再介绍一个get方法



Java代码



/**

* Returns the value to which the specified key is mapped,

* or {@code null} if this map contains no mapping for the key.

*

* <p>More formally, if this map contains a mapping from a key

* {@code k} to a value {@code v} such that {@code (key==null ? k==null :

* key.equals(k))}, then this method returns {@code v}; otherwise

* it returns {@code null}. (There can be at most one such mapping.)

*

* <p>A return value of {@code null} does not <i>necessarily</i>

* indicate that the map contains no mapping for the key; it's also

* possible that the map explicitly maps the key to {@code null}.

* The {@link #containsKey containsKey} operation may be used to

* distinguish these two cases.

*/

public V get(Object key)

{

Entry<K, V> e = (Entry<K, V>)getEntry(key);

if (e == null)

return null;

e.recordAccess(this);

return e.value;

}



/**

* This method is invoked by the superclass whenever the value

* of a pre-existing entry is read by Map.get or modified by Map.set.

* If the enclosing Map is access-ordered, it moves the entry

* to the end of the list; otherwise, it does nothing.

*/

void recordAccess(HashMap<K, V> m)

{

LinkedHashMap<K, V> lm = (LinkedHashMap<K, V>)m;

if (lm.accessOrder)

{

lm.modCount++;

remove();

addBefore(lm.header);

}

}



/**

* Removes this entry from the linked list.

*/

private void remove()

{

before.after = after;

after.before = before;

}



这里我们来看一下recordAccess方法!

上面我们不是讲过accessOrder这个参数值控制着LinkedHashMap的迭代顺序嘛,这里我们来看一下。

当accessOrder为true时,remove方法就是将当前元素从双向链表中移除,

addBefore方法再将当前元素插入到链表的头部去,这样最近读到的元素,在迭代的时候是优先被迭代出来的!

这就是所谓的LRU算法(Least Recently Used):最近最少使用算法。

当accessOrder为false时,不做任何事情,就按照插入顺序迭代出来!



还有些其他的方法这里也不再累述了,重点的都在上面阐述了!

附上:
public class java.util
LinkedHashMap<K, V>
Extends: AbstractMap > HashMap
Implements: Map
Details

Constructors
publicLinkedHashMap(int initialCapacity, float loadFactor) Hide

zh_cn
构造一个带指定初始容量和加载因子的空插入顺序 LinkedHashMap 实例。
initialCapacity
zh_cn
初始容量
loadFactor
zh_cn
加载因子
ThrowsIllegalArgumentException:
zh_cn
如果初始容量为负或者加载因子为非正
publicLinkedHashMap(int initialCapacity) Hide

zh_cn
构造一个带指定初始容量和默认加载因子 (0.75) 的空插入顺序 LinkedHashMap 实例。
initialCapacity
zh_cn
初始容量
ThrowsIllegalArgumentException:
zh_cn
如果初始容量为负
publicLinkedHashMap()

zh_cn
构造一个带默认初始容量 (16) 和加载因子 (0.75) 的空插入顺序 LinkedHashMap 实例。

publicLinkedHashMap(Map m) Hide

zh_cn
构造一个映射关系与指定映射相同的插入顺序 LinkedHashMap 实例。所创建的 LinkedHashMap 实例具有默认的加载因子 (0.75) 和足以容纳指定映射中映射关系的初始容量。
m
zh_cn
映射关系将被存放在此映射中的映射
ThrowsNullPointerException:
zh_cn
如果指定的映射为 null。
publicLinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) Hide

zh_cn
构造一个带指定初始容量、加载因子和排序模式的空 LinkedHashMap 实例。
initialCapacity
zh_cn
初始容量
loadFactor
zh_cn
加载因子
accessOrder
zh_cn
排序模式 - 对于访问顺序,为 true;对于插入顺序,则为 false
ThrowsIllegalArgumentException:
zh_cn
如果初始容量为负或者加载因子为非正
Methods
public voidclear()

zh_cn
从此映射中移除所有映射关系。此调用返回后,映射将为空。

public booleancontainsValue(Object value) Hide

zh_cn
如果此映射将一个或多个键映射到指定值,则返回 true。
value
zh_cn
测试在此映射中是否存在的值
return
zh_cn
如果此映射将一个或多个键映射到指定值,则返回 true
public Objectget(Object key)

zh_cn
返回指定键所映射的值;如果对于该键来说,此映射不包含任何映射关系,则返回
null

更确切地讲,如果此映射包含一个满足
(key==null ? k==null : key.equals(k))
的从
k
键到
v
值的映射关系,则此方法返回
v
;否则返回
null
。(最多只能有一个这样的映射关系。)

返回
null
值并不一定 表明该映射不包含该键的映射关系;也可能该映射将该键显示地映射为
null
。可使用
containsKey
操作来区分这两种情况。

protected booleanremoveEldestEntry(Map.Entry eldest) Hide

zh_cn
如果此映射移除其最旧的条目,则返回 true。在将新条目插入到映射后,put 和 putAll 将调用此方法。此方法可以提供在每次添加新条目时移除最旧条目的实现程序。如果映射表示缓存,则此方法非常有用:它允许映射通过删除旧条目来减少内存损耗。
示例用法:此重写允许映射增加到 100 个条目,然后每次添加新条目时删除最旧的条目,始终维持 100 个条目的稳定状态。

private static final int MAX_ENTRIES = 100;

     protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_ENTRIES;
     }

此方法通常不以任何方式修改映射,相反允许映射在其返回值的指引下进行自我修改。使用此方法直接修改映射是 允许的,但是如果它执行了此操作,则一定 返回 false(表示该映射不应进行任何进一步的修改)。在此方法中修改映射后是否返回 true是不确定的。

此实现仅返回 false(这样,此映射的行为将类似于正常映射,即永远不能移除最旧的元素)。
eldest
zh_cn
在映射中最早插入的条目;如果是访问顺序映射,则为最早访问的条目。如果此方法返回 true,则此为将移除的条目。如果导致此调用的 put 或 putAll 调用之前映射为空,则该条目就是刚刚插入的条目;换句话说,如果映射只包含单个条目,则最旧的条目也是最新的条目。
return
zh_cn
如果应该从映射移除最旧的条目,则返回 true;如果应该保留,则返回 false。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: