LinkedHahMap源码部分分析

LinkedHahMap有两个属性:

private transient Entry header;

private final boolean accessOrder;

构造方法有以下几种:

1.public LinkedHashMap(int initialCapacity, float loadFactor) {

super(initialCapacity, loadFactor);

accessOrder = false;

}

2. public LinkedHashMap(int initialCapacity) {

super(initialCapacity);

accessOrder = false;

}

3.public LinkedHashMap() {

super();

accessOrder = false;

}

4.public LinkedHashMap(Map m) {

super(m);

accessOrder = false;

}

5. public LinkedHashMap(int initialCapacity,

float loadFactor,

boolean accessOrder) {

super(initialCapacity, loadFactor);

this.accessOrder = accessOrder;

}

由于LinkedHashMap继承与HashMap,因此super()方法是

public HashMap(int initialCapacity, float loadFactor) {

if (initialCapacity < 0)

throw new IllegalArgumentException("Illegal initial capacity: " +

initialCapacity);

if (initialCapacity > MAXIMUM_CAPACITY)

initialCapacity = MAXIMUM_CAPACITY;

if (loadFactor <= 0 || Float.isNaN(loadFactor))

throw new IllegalArgumentException("Illegal load factor: " +

loadFactor);

// Find a power of 2 >= initialCapacity

int capacity = 1;

while (capacity < initialCapacity)

capacity <<= 1;

this.loadFactor = loadFactor;

threshold = (int)(capacity * loadFactor);

table = new Entry[capacity];

init();

}

注意init()方法,LinkedHashMap中重写了init方法,如下:

void init() {

header = new Entry(-1, null, null, null);

header.before = header.after = header;

}

除了第五种,其他情况下,accessOrder 都默认为false,此时,会实现先入先出的方式输出;当我们以第五种方式实现,且accessOrder 为true时,则最不常用的数据先输出;因此LinkedHashMap常被用来实现LRU算法,将常用数据放入缓存,且可定量,当有新数据存入时,将最不常用数据移除;

下面分析源码中如何实现以上两种输出方式:

我们先看下LinkedHashMap的Entry结构与方法(Map存储数据,是以Entry数组来存储的,有些类似于List以Object数组)

private static class Entry extends HashMap.Entry {

Entry before, after;

Entry(int hash, K key, V value, HashMap.Entry next) {

super(hash, key, value, next);

}

/**

* Removes this entry from the linked list.

*/

private void remove() {

before.after = after;

after.before = before;

}

/**

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

* 将调用者放到existingEntry的前面,将before,after关联起来,参考循环双端队列

*/

private void addBefore(Entry existingEntry) {

after  = existingEntry;

before = existingEntry.before;

before.after = this;

after.before = this;

}

/**

* 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.

* 大体意思是,此方法会在Map.get或Map.set.(此处意思应该是put)时调用,

* 当accessOrder为true时,

* 将此entry移到链表的尾部

*/

void recordAccess(HashMap m) {

LinkedHashMap lm = (LinkedHashMap)m;

if (lm.accessOrder) {

lm.modCount++;

remove();

addBefore(lm.header);

}

}

void recordRemoval(HashMap m) {

remove();

}

}

有了Entry之后,再去看put()方法,由于LinkedHashMap自己没有重写put方法,因此调用父类的方法:

public V put(K key, V value) {

if (key == null)

return putForNullKey(value);

int hash = hash(key.hashCode());

int i = indexFor(hash, table.length);

for (Entry e = table[i]; e != null; e = e.next) {

Object k;

if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

V oldValue = e.value;

e.value = value;

e.recordAccess(this);

return oldValue;

}

}

modCount++;

addEntry(hash, key, value, i);

return null;

}

putForNullKey(value)方法逻辑与此相同,不单独分析;当有相同的key值时,替换数据,且调用一个recordAccess(this)方法,下面再分析,如果没有相同的key值,则调用addEntry(hash, key, value, i);此方法代码为:

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

createEntry(hash, key, value, bucketIndex);

Entry eldest = header.after;

if (removeEldestEntry(eldest)) {

removeEntryForKey(eldest.key);

} else {

if (size >= threshold)

resize(2 * table.length);

}

}

其中 createEntry(hash, key, value, bucketIndex)代码为:

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

HashMap.Entry old = table[bucketIndex];

Entry e = new Entry(hash, key, value, old);

table[bucketIndex] = e;

e.addBefore(header);

size++;

}

createEntry()方法中,除了常规新增外,还调用了addBefore(header)方法,该方法是将新增的entry增加到header前一位;

addEntry()方法中,除了createEntry()外,还有根据removeEldestEntry(eldest)的返回值来分别执行方法,removeEldestEntry代码如下:

protected boolean removeEldestEntry(Map.Entry eldest) {

return false;

}

代码很简单,不过注释很长,注释中给了例子:

*

*    private static final int MAX_ENTRIES = 100;

*

*    protected boolean removeEldestEntry(Map.Entry eldest) {

*        return size() > MAX_ENTRIES;

*    }

*

可以看出,当我们重写此方法时,如果return size() > MAX_ENTRIES;则可实现当size大于我们设置的变量时,返回true,则可在addEntry方法中调用removeEntryForKey(eldest.key);将我们需要的数据从map中移除。如果为false,则调用resize()方法,将数组大小翻倍,且重新对entry进行定位,resize方法调用父类方法,代码如下:

void resize(int newCapacity) {

Entry[] oldTable = table;

int oldCapacity = oldTable.length;

if (oldCapacity == MAXIMUM_CAPACITY) {

threshold = Integer.MAX_VALUE;

return;

}

Entry[] newTable = new Entry[newCapacity];

transfer(newTable);

table = newTable;

threshold = (int)(newCapacity * loadFactor);

}

其中transfer(newTable)方法,LinkedHashMap进行了重写,代码与注释如下:

/**

* Transfers all entries to new table array.  This method is called

* by superclass resize.  It is overridden for performance, as it is

* faster to iterate using our linked list.

*/

void transfer(HashMap.Entry[] newTable) {

int newCapacity = newTable.length;

for (Entry e = header.after; e != header; e = e.after) {

int index = indexFor(e.hash, newCapacity);

e.next = newTable[index];

newTable[index] = e;

}

}

注释大概意思就是重新定位,用它比较快,由于Entry对象是队列连接,因此的确会比HashMap中的方法快一些。

我们平时获取map的值有几种方法,常用的就是map.values(),然后遍历这个集合,然而这个values实际上是一个null,我们去一步步研究下

transient volatileCollectionvalues=null;

public Collection values() {

Collection vs = values;

return (vs != null ? vs : (values = new Values()));

}

private final class Values extends AbstractCollection {

public Iterator iterator() {

return newValueIterator();

}

public int size() {

return size;

}

public boolean contains(Object o) {

return containsValue(o);

}

public void clear() {

HashMap.this.clear();

}

}

会发现,new Values()并没有插入值的操作,而我们调用foreach循环,实际调用的是iterator方法,而该方法返回 return newValueIterator();

可以简单看一下Iterable接口的注释:

/** Implementing this interface allows an object to be the target of

*  the "foreach" statement.

比如我们写个简单的循环代码:

Map l = new LinkedHashMap();

for(Object o:l.values()){

System.out.println(o.toString());

}

通过查看字节码:

L0

LINENUMBER 36 L0

NEW java/util/LinkedHashMap

DUP

INVOKESPECIAL java/util/LinkedHashMap. ()V

ASTORE 1

L3

LINENUMBER 37 L3

ALOAD 1

INVOKEINTERFACE java/util/Map.values ()Ljava/util/Collection;

INVOKEINTERFACE java/util/Collection.iterator ()Ljava/util/Iterator;

ASTORE 2

L4

FRAME APPEND [java/util/Map java/util/Iterator]

ALOAD 2

INVOKEINTERFACE java/util/Iterator.hasNext ()Z

IFEQ L1

ALOAD 2

INVOKEINTERFACE java/util/Iterator.next ()Ljava/lang/Object;

ASTORE 3

可以看出  INVOKEINTERFACE java/util/Collection.iterator ()Ljava/util/Iterator;和  INVOKEINTERFACE java/util/Iterator.hasNext ()Z 实际上调用的是Iterator方法。

ValueIterator()方法为linkedHashMap重写的方法:

private class ValueIterator extends LinkedHashIterator {

public V next() { return nextEntry().value; }

}

因此我们得知,返回值为nextEntry().value;其中nextEntry()方法为

Entry nextEntry() {

if (modCount != expectedModCount)

throw new ConcurrentModificationException();

if (nextEntry == header)

throw new NoSuchElementException();

Entry e = lastReturned = nextEntry;

nextEntry = e.after;

return e;

}

其中nextEntry 初始化时为Entry nextEntry    = header.after;

因此输出时按照从顶端到末端输出其中entry的value值,

总结一下就是:当put时,如果此key已存在(hash相同,可key.equals(已存在key)),根据初始化的accessOrder值是否为true来选择是否将此entry移到链表的尾端。如果key在map中不存在,则插入,

根据removeEldestEntry()方法的返回值是否为true,决定是否将header.next(即链表首端数据)移除,不重写此方法时,默认为false,则如果size大于理论容量threshold时,resize数组;

由此可看出,当我们需要容量大于设定值时,将最不常用数据从map中移除的功能时,需重写removeEldestEntry()方法,且需在构造方法中,定义accessOrder为true,否则实际功能是将最旧的数据移除,而不是最不常用数据。

回顾开头结论:当构造函数不明确定义accessOrder为true时,按照先入先出顺序删除,定义accessOrder为true时,按照不常用数据先出的规则输出


------------------------------------------------------------------------------------

放个大狗镇楼,未经允许,转载必究


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,548评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,497评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,990评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,618评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,618评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,246评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,819评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,725评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,268评论 1 320
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,356评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,488评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,181评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,862评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,331评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,445评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,897评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,500评论 2 359

推荐阅读更多精彩内容