美文网首页
LinkedHahMap源码部分分析

LinkedHahMap源码部分分析

作者: 嗷呜呜啦啦 | 来源:发表于2017-11-22 22:21 被阅读0次

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时,按照不常用数据先出的规则输出

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

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

相关文章

网友评论

      本文标题:LinkedHahMap源码部分分析

      本文链接:https://www.haomeiwen.com/subject/syfavxtx.html