LinkedHashMap可认为是哈希表和链接列表综合实现,并允许使用null值和null键。LinkedHashMap实现与HashMap的不同之处在于,LinkedHashMap维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。 LinkedHashMap的实现不是同步的。如果多个线程同时访问LinkedHashMap,而其中至少一个线程从结构上修改了该映射,则它必须保持外部同步。
1.LinkedHashMap的存储结构
![](http://img.blog.csdn.net/20161106105628371?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
LinkedHashMap中加入了一个head头结点,将所有插入到该LinkedHashMap中的Entry按照插入的先后顺序(accessOrder标志位默认为false)依次加入到以head为头结点的双向循环链表的尾部。
LinkedHashMap实际上就是HashMap和LinkedList两个集合类的存储结构的结合。在LinkedHashMapMap中,所有put进来的Entry都保存在如图所示的哈希表中,但它又额外定义了一个以head为头结点的空的双向循环链表,每次put进来Entry,除了将其保存到对哈希表中对应的位置上外,还要将其插入到双向循环链表的尾部。
下面我们来分析LinkedHashMap的源代码。
2.LinkedHashMap成员变量
LinkedHashMap采用的hash算法和HashMap相同,但它重新定义了数组中保存的元素Entry,该Entry除了保存当前对象的引用外,还保存了其上一个元素before和下一个元素after的引用,从而在哈希表的基础上又构成了双向链接列表。
- 1.
- 2.
- 3.
- 4. private transient Entry<K,V> header;
- 5.
- 6.
- 7.
- 8.
- 9. private final boolean accessOrder;
- 10.
- 11.
- 12.
-
-
-
- 16.private static class Entry<K,V> extends HashMap.Entry<K,V> {
- 17. Entry<K,V> before, after;
- 18. ……
- 19.}
3.构造函数
LinkedList一共提供了五个构造方法。
- 1.
- 2.
- 3.
- 4.public LinkedHashMap(int initialCapacity, float loadFactor) {
- 5. super(initialCapacity, loadFactor);
- 6. accessOrder = false;
- 7.}
- 8.
- 9.
- 10.
- 11.
- 12.public LinkedHashMap(int initialCapacity) {
- 13. super(initialCapacity);
- 14. accessOrder = false;
- 15.}
- 16.
- 17.
- 18.
- 19.
- 20.public LinkedHashMap() {
- 21. super();
- 22. accessOrder = false;
- 23.}
- 24.
- 25.
- 26.
- 27.
- 28.public LinkedHashMap(Map<? extends K, ? extends V> m) {
- 29. super(m);
- 30. accessOrder = false;
- 31.}
- 32.
- 33.
- 34.public LinkedHashMap(int initialCapacity,
- 35. float loadFactor,
- 36. boolean accessOrder) {
- 37. super(initialCapacity, loadFactor);
- 38. this.accessOrder = accessOrder;
- 39.}
我们已经知道LinkedHashMap的Entry元素继承HashMap的Entry,提供了双向链表的功能。在HashMap的构造器中,最后会调用init()方法,进行相关的初始化,这个方法在HashMap的实现中是空方法(感叹模板模式的精妙!),只是提供给子类实现相关的初始化调用。LinkedHashMap重写了init()方法,在调用父类的构造方法完成构造后,进一步实现了对其元素Entry的初始化操作。分析init()方法,的确是对header进行了初始化,并构造成一个双向循环链表(和LinkedList的存储结构是一样的)。
- 1.void init() {
- 2. header = new Entry<K,V>(-1, null, null, null);
- 3. header.before = header.after = header;
- 4.}
4.元素存储
LinkedHashMap重写了父类HashMap的put方法调用的子方法void addEntry(int hash, K key, V value, int bucketIndex) 和void createEntry(int hash, K key, V value, int bucketIndex),提供了自己特有的双向链接列表的实现。
- 1.
- 2.
- 3.
- 4.
- 5.void addEntry(int hash, K key, V value, int bucketIndex) {
- 6.
- 7. createEntry(hash, key, value, bucketIndex);
- 8.
- 9.
- 10. Entry<K,V> eldest = header.after;
- 11.
- 12.
- 13. if (removeEldestEntry(eldest)) {
- 14. removeEntryForKey(eldest.key);
- 15. } else {
- 16.
- 17. if (size >= threshold)
- 18. resize(2 * table.length);
- 19. }
- 20.}
- 21.
- 22.void createEntry(int hash, K key, V value, int bucketIndex) {
- 23. HashMap.Entry<K,V> old = table[bucketIndex];
- 24. Entry<K,V> e = new Entry<K,V>(hash, key, value, old);
- 25. table[bucketIndex] = e;
- 26.
- 27.
- 28.
- 29.
- 30. e.addBefore(header);
- 31. size++;
- 32.}
- 33.
- 34.private void addBefore(Entry<K,V> existingEntry) {
- 35. after = existingEntry;
- 36. before = existingEntry.before;
- 37. before.after = this;
- 38. after.before = this;
- 39.}
- 40.
- 41.
- 42.
- 43.
- 44.
- 45. protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
- 46. return false;
- 47. }
- 48.}
5.元素读取
LinkedHashMap重写了父类HashMap的get方法。由于的链表的增加、删除操作是常量级的,性能不会带来较大损失。LinkedHashMap 最牛逼的地方在于recordAccess()方法。
- 1.
- 2.
- 3.
- 4.
- 5.public V get(Object key) {
- 6.
- 7. Entry<K,V> e = (Entry<K,V>)getEntry(key);
- 8. if (e == null)
- 9. return null;
- 10.
- 11. e.recordAccess(this);
- 12. return e.value;
- 13.}
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.void recordAccess(HashMap<K,V> m) {
- 22. LinkedHashMap<K,V> lm = (LinkedHashMap<K,V>)m;
- 23.
- 24.
- 25. if (lm.accessOrder) {
- 26. lm.modCount++;
- 27. remove();
- 28. addBefore(lm.header);
- 29. }
- 30.}
6.元素删除
LinkedHashMap没有重写remove(Object key)方法,重写了被remove调用的recordRemoval方法,再一次感叹模板方法模式的精妙!
HahsMap remove(Object key)把数据从横向数组 * 竖向next链表里面移除之后(就已经完成工作了,所以HashMap里面recordRemoval是空的实现调用了此方法
但在LinkedHashMap里面,还需要移除header链表里面Entry的after和before关系。
7.元素遍历
- 1.
- 2. private abstract class LinkedHashIterator<T> implements Iterator<T> {
- 3. Entry<K,V> nextEntry = header.after;
- 4. Entry<K,V> lastReturned = null;
- 5.
- 6.
-
-
-
-
- 11. int expectedModCount = modCount;
- 12.
- 13. public boolean hasNext() {
- 14. return nextEntry != header;
- 15. }
- 16.
- 17. public void remove() {
- 18. if (lastReturned == null)
- 19. throw new IllegalStateException();
- 20. if (modCount != expectedModCount)
- 21. throw new ConcurrentModificationException();
- 22.
- 23. LinkedHashMap.this.remove(lastReturned.key);
- 24. lastReturned = null;
- 25. expectedModCount = modCount;
- 26. }
- 27.
- 28. Entry<K,V> nextEntry() {
- 29. if (modCount != expectedModCount)
- 30. throw new ConcurrentModificationException();
- 31. if (nextEntry == header)
- 32. throw new NoSuchElementException();
- 33.
- 34. Entry<K,V> e = lastReturned = nextEntry;
- 35. nextEntry = e.after;
- 36. return e;
- 37. }
- 38. }
- 39.
- 40.
- 41. private class KeyIterator extends LinkedHashIterator<K> {
- 42. public K next() { return nextEntry().getKey(); }
- 43. }
- 44.
- 45. private class ValueIterator extends LinkedHashIterator<V> {
- 46. public V next() { return nextEntry().value; }
- 47. }
- 48.
- 49. private class EntryIterator extends LinkedHashIterator<Map.Entry<K,V>> {
- 50. public Map.Entry<K,V> next() { return nextEntry(); }
- 51. }
8.基于LinkedHashMap实现LRU Cache
用LinkedHashmap实现LRU算法,就要覆写方法removeEldestEntry。该方法默认返回false,我们一般在用LinkedHashMap实现LRU算法时,要覆写该方法,一般的实现是,当设定的内存(这里指节点个数)达到最大值时,返回true,这样put新的Entry(该Entry的key在哈希表中没有已经存在)时,就会调用removeEntryForKey方法,将最近最少使用的节点删除(head后面的那个节点,实际上是最近没有使用)。
LinkedHashMap是如何实现LRU的。首先,当accessOrder为true时,才会开启按访问顺序排序的模式,才能用来实现LRU算法。我们可以看到,无论是put方法还是get方法,都会导致目标Entry成为最近访问的Entry,因此便把该Entry加入到了双向链表的末尾(get方法通过调用recordAccess方法来实现,put方法在覆盖已有key的情况下,也是通过调用recordAccess方法来实现,在插入新的Entry时,则是通过createEntry中的addBefore方法来实现),这样便把最近使用了的Entry放入到了双向链表的后面,多次操作后, 双向链表前面的Entry便是最近没有使用的,这样当节点个数满的时候,删除的最前面的Entry(head后面的那个Entry)便是最近最少使用的Entry。
- 1.
-
-
- 4.
- 5.
-
-
-
-
-
- 11.
- 12.
-
-
-
-
-
- 18.
- 19.
-
-
-
-
- 24.
- 25.import java.util.*;
- 26.public class LRULinkedHashMap<K,V> extends LinkedHashMap<K,V>{
- 27. private int capacity;
- 28.
- 29. LRULinkedHashMap(int capacity){
- 30. super(16,0.75f,true);
- 31. this.capacity=capacity;
- 32. }
- 33. @Override
- 34. public boolean removeEldestEntry(Map.Entry<K, V> eldest){
- 35.
- 36. System.out.println("此时的size大小="+size());
- 37. if((size()>capacity))
- 38. {
- 39. System.out.println("超出已定的内存容量,把链表顶端元素移除:"+eldest.getValue());
- 40. }
- 41. return size()>capacity;
- 42. }
- 43.
- 44. public static void main(String[] args) throws Exception{
- 45. Scanner cin = new Scanner(System.in);
- 46.
- 47. System.out.println("请输入总共内存页面数: ");
- 48. int n = cin.nextInt();
- 49. Map<Integer,Integer> map=new LRULinkedHashMap<Integer, Integer>(n);
- 50.
- 51. System.out.println("请输入按顺序输入要访问内存的总共页面数: ");
- 52. int y = cin.nextInt();
- 53.
- 54. System.out.println("请输入按顺序输入访问内存的页面序列: ");
- 55. for(int i=1;i<=y;i++)
- 56. {
- 57. int x = cin.nextInt();
- 58. map.put(x, x);
- 59. }
- 60. System.out.println("此时内存中包含的页面数是有:");
- 61.
- 62. for(java.util.Map.Entry<Integer, Integer> entry: map.entrySet()){
- 63. System.out.println(entry.getValue());
- 64. }
- 65. }
- 66.}
9.总结
1.LinkedHashMap继承自HashMap,具有HashMap的大部分特性,比如支持null键和值,默认容量为16,装载因子为0.75,非线程安全等等;
2.LinkedHashMap通过设置accessOrder控制遍历顺序是按照插入顺序还是按照访问顺序。当accessOrder为true时,可以利用其完成LRU缓存的功能;
3.LinkedHashMap内部维护了一个双向循环链表,并且其迭代操作时通过链表完成的,而不是去遍历hash表。
0 0