您的位置:首页 > 其它

LinkedHashMap源码分析

2016-07-18 10:48 417 查看
之前文章《HashMap源码分析》中我们分析了HashMap的源码,本篇我们来分析LinkedHashMap的源码。同样进一步阅读之前强烈建议先浏览一下之前文章《《Java
Generics and Collections》笔记-Lists/Maps》中关于Maps的部分,并熟悉《HashMap源码分析》中关于HashMap的介绍。

我们知道HashMap与LinkedHashMap的最大区别就是后者可以记录(访问或者插入)顺序。本篇我们重点分析它是如何实现的。

首先我们还是先回顾一下HashMap,下图来自之前的文章:



前面的文章中已经详细介绍了,这里就不再赘述。

继承关系:

147 public class LinkedHashMap<K,V>

148     extends HashMap<K,V>

149     implements Map<K,V>

LinkedHashMap中增加了一个成员变量accessOrder,如下:

159     /**

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

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

164      */

165     private final boolean accessOrder;
具体注释中已经指明了。

另外增加的一个成员为header,如下:

154     /**

155      * The head of the doubly linked list.

156      */

157     private transient Entry<K,V> header;

注释中也说明了,LinkedHashMap采用的是双向链表,header是链表首部,通过增加一个header,简化了边界条件的判断,这一点在后面的插入、删除等操作中可以体会到。

上面可以看到header的类型为Entry<K,V>,我们来看一下:

317     /**

318      * LinkedHashMap entry.

319      */

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

321         // These fields comprise the doubly linked list used for iteration.
322         Entry<K,V> before, after; 

323

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

325             super(hash, key, value, next);

326         }

327

328         /**

329          * Removes this entry from the linked list.

330          */

331         private void remove() {

332             before.after = after;

333             after.before = before;

334         }

335 就像前面所说,由于添加了首部header,删除元素变得简单(不需要判断为null的情况)。

336         /**

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

338          */

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

340            after  = existingEntry;

341             before = existingEntry.before;

342             before.after = this;

343             after.before = this;

344         }

把该entry插入到existingEntry之前。

346         /**

347          * This method is invoked by the superclass whenever the value

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


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

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

351          */

352         void recordAccess(HashMap<K,V> m) {

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

354             if (lm.accessOrder) {

355                 lm.modCount++;

356                 remove();

357                 addBefore(lm.header);

358             }

359         }

前面也已经说过,accessOrder为true说明是按照访问顺序组织元素,此时355行增加modCount,356行将该entry从链表中移除,357行将其添加到header前面。也就是说header前面的元素是最近访问的。该方法在对已经存在的元素调用Map.get或Map.set时就会被超类调用。

361         void recordRemoval(HashMap<K,V> m) {

362             remove();

363         }

364     }

可见Entry在HashMap.Entry的基础上增加两个域before和after从而实现了可以按照不同的策略将元素进行链接。
LinkedHashMap直接继承了HashMap的table数组,接下来我们来看一下LinkedHashMap是如何创建table数组的。

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

427         super.addEntry(hash, key, value, bucketIndex);

可以看到直接调用了父类的addEntry方法,父类的addEntry中又调用了createEntry,如下:
HashMap:
899     void createEntry(int hash, K key, V value, int bucketIndex) {

 900         Entry<K,V> e = table[bucketIndex];

 901         table[bucketIndex] = new Entry<>(hash, key, value, e);

 902         size++;

 903     }

由于LinkedHashMap重写了该方法,因此最终调用的是重写后(这里涉及到对象模型的知识,暂时不表)的,如下:

436     /**

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

438      * table or remove the eldest entry.

439      */

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

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

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

443         table[bucketIndex] = e;

444         e.addBefore(header);

445         size++;

446     }

现在我们给出新的table结构如下图(可以对比图):



之前在HashMap源码分析过程中,我们重点分析了它的iterator过程,现在我们来看LinkedHashMap是如何实现Iterator的。

366     private abstract class LinkedHashIterator<T> implements Iterator<T> {

367         Entry<K,V> nextEntry    = header.after;

368         Entry<K,V> lastReturned = null;

369

370         /**

371          * The modCount value that the iterator believes that the backing

372          * List should have.  If this expectation is violated, the iterator

373          * has detected concurrent modification.

374          */

375         int expectedModCount = modCount;

376

377         public boolean hasNext() {

378             return nextEntry != header;     //循环链表

379         }

380

381         public void remove() {

382             if (lastReturned == null)

383                 throw new IllegalStateException();

384             if (modCount != expectedModCount)

385                 throw new ConcurrentModificationException();

386

387             LinkedHashMap.this.remove(lastReturned.key);

388             lastReturned = null;

389             expectedModCount = modCount;

390         }

392         Entry<K,V> nextEntry() {

393             if (modCount != expectedModCount)

394                 throw new ConcurrentModificationException();

395             if (nextEntry == header)

396                 throw new NoSuchElementException();

397
398             Entry<K,V> e = lastReturned = nextEntry;
399             nextEntry = e.after;

400             return e;

401         }

402     }

通过上面代码可以看出,LinkedHashMap的Iterator的实现比HashMap的要简单,主要原因在于这里所有的元素通过after与before链接成一个链表,而不再像HashMap中那样,不同的元素可能会不相邻(如图1)。其他的思想与HashMap是一致的,可以参考之前的文章,由于比较简单,不再多言。

404     private class KeyIterator extends LinkedHashIterator<K> {

405         public K next() { return nextEntry().getKey(); }

406     }

407

408     private class ValueIterator extends LinkedHashIterator<V> {

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

410     }

411

412     private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {

413         public Map.Entry<K,V> next() { return nextEntry(); }

414     }

这几个Iterator都是基于LinkedHashIterator的,区别仅仅在于返回的元素不同。

看一下LinkedHashIterator中的其他几个方法:

416     // These Overrides alter the behavior of superclass view iterator()methods

417     Iterator<K> newKeyIterator()   { return new KeyIterator();   }

418     Iterator<V> newValueIterator() { return new ValueIterator(); }

419     Iterator<Map.Entry<K,V>> newEntryIterator() { return new EntryIterator(); }

这里重写了父类的方法,因此改变了iterator的行为方式。

LinkedHashMap并没有继承EntrySet、KeySet,事实上它们在HashMap中被定义为final类型的。这里我们仅以EntrySet为例(其他的是类似的)来看一下LinkedHashMap的Iterator实现原理:

1080     private final class EntrySet extends AbstractSet<Map.Entry<K,V>> {

1081         public Iterator<Map.Entry<K,V>> iterator() {

1082             return newEntryIterator();

1083         }

由于LinkedHashMap重写了newEntryIterator()(见上面419行),所以调用EntrySet的iterator方法就能够返回LinkedHashMap自己实现的一套遍历策略。

现在我们进一步给出LinkedHashMap的底层数据结构图如下:



到此比较重要的内容已经分析过了,接下来看一下几个简单的方法:

240     void init() {

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

242         header.before = header.after = header;

243     }

初始化header的

245     /**

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

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

248      * faster to iterate using our linked list.

249      */

250     @Override

251     void transfer(HashMap.Entry[] newTable, boolean rehash) {

252         int newCapacity = newTable.length;

253         for (Entry<K,V> e = header.after; e != header; e = e.after) {

254             if (rehash)

255                 e.hash = (e.key == null) ? 0 : hash(e.key);

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

257             e.next = newTable[index];

258             newTable[index] = e;

259         }

260     }

该方法的实现与HashMap有所不同了,因为这里所有元素通过before,next连接成一个双向链表,在遍历元素的时候就可以直接通过遍历该链表来实现了。它的效率有所提高,节省了遍历table数组的时间。

271     public boolean containsValue(Object value) {

272         // Overridden to take advantage of faster iterator

273         if (value==null) {

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

275                 if (e.value==null)

276                     return true;

277         } else {

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

279                 if (value.equals(e.value))

280                     return true;

281         }

282         return false;

283     }

这个同上面的分析是一样的,不再多言。

300     public V get(Object key) {

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

302         if (e == null)

303             return null;

304         e.recordAccess(this);

305         return e.value;

306     }

其中getEntry是直接继承的HashMap的,在那篇文章中已经分析过了。304行在上面分析过了,如果accessOrder为true,就按范文顺序组织元素,这里访问过e之后,e会被从原来的双向链表中删除,并被重新加到header之前。

HashIterator中还有几个方法前面没有分析,现在来看一下:

421     /**

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

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

424      * removes the eldest entry if appropriate.

425      */

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

427         super.addEntry(hash, key, value, bucketIndex);

429         // Remove eldest entry if instructed

430         Entry<K,V> eldest = header.after;
431         if (removeEldestEntry(eldest))
{     //提供一种策略
432             removeEntryForKey(eldest.key);    
433         }

434     }

435

436     /**
437      * This override differs from addEntry in that it doesn't resize the
438      * table or remove the eldest entry.

439      */

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

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

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

443         table[bucketIndex] = e;
444         e.addBefore(header);

445         size++;

446     }

插入过程与HashMap是一致的,只不过这里把e添加到header前面,表示它是最新加入的。

448     /**

449      * Returns <tt>true</tt> if this map should remove its eldest entry.

450      * This method is invoked by <tt>put</tt> and <tt>putAll</tt> after

451      * inserting a new entry into the map.  It provides the implementor

452      * with the opportunity to remove the eldest entry each time a new one
453      * is added.  This is useful if the map represents a cache: it allows

454      * the map to reduce memory consumption by deleting stale entries.
478      * @param    eldest The least recently inserted entry in the map, or if
479      *           this is an access-ordered map, the least recently accessed
480      *           entry.  This is the entry that will be removed it this

481      *           method returns <tt>true</tt>.  If the map was empty prior

482      *           to the <tt>put</tt> or <tt>putAll</tt> invocation resulting

483      *           in this invocation, this will be the entry that was just

484      *           inserted; in other words, if the map contains a single

485      *           entry, the eldest entry is also the newest.

486      * @return   <tt>true</tt> if the eldest entry should be removed

487      *           from the map; <tt>false</tt> if it should be retained.

488      */

489     protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {

490         return false;

491     }
关于removeEldestEntry方法注释中已经解释的非常清楚了,它可以被用来实现缓存。

整个LinkedHashMap的实现是比较巧妙的,尤其是底层采用的数据结构。更过关于Java源码或Linux内核中类似数据结构的使用实例,可以参考之后的一篇文章《数据结构:数组+链表》。
就介绍到这里。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: