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内核中类似数据结构的使用实例,可以参考之后的一篇文章《数据结构:数组+链表》。
就介绍到这里。
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内核中类似数据结构的使用实例,可以参考之后的一篇文章《数据结构:数组+链表》。
就介绍到这里。
相关文章推荐
- Java-jfree报表(学习整理)----饼状图、柱状图、折线统计图
- oracle 基础SQL语句 多表查询 子查询 分页查询 合并查询 分组查询 group by having order by
- javaweb基础(34)_jdbc处理mysql大数据
- initgroups
- servlet容器中servlet,filter,listener的生命周期
- dependencies与dependencyManagement的区别
- PyCharm设置断点,调试(五)
- Tomcat的session管理探究
- C语言setgroups()函数:设置组代码函数
- BZOJ 1003 物流运输(最短路+dp)
- [置顶] 【PPT&视频】《陈新河:万亿元大数据产业新生态》——央视网大数据名人讲堂之大数据产业系列
- Selenium手册
- C语言getgroups()函数:获取组代码函数
- Android——实现无障碍
- linux命令-1
- 环境配置
- CSS隐藏元素的几种方式
- python 接口自动化测试--代码实现(八)
- hdu-5721 Palace(最近点对)
- iOS---获取设备各种信息