LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的数据淘汰算法,选择最近最久未使用的数据予以淘汰。 LRUMap 可以保存指定数量的固定的数据,并且它会按照 LRU 算法,帮你清除最不常用的数据。

示例:
幂等性判断

实现原理

LRUMap则是实现的LRP算法的Map集合类,它继承于AbstractLinkedMap抽象类。
image.png
LRUMap的初始化时需要指定最大集合元素个数,新增的元素个数大于允许的最大集合个数时,则会执行LRU淘汰算法。所有的元素在LRUMap中会根据最近使用情况进行排序。最近使用的会放在元素的最前面(LRUMap是通过链表来存储元素内容). 所以LRUMap进行淘汰时只需要删除链表最后一个即可(即header.after所指的元素对象)
那么那些操作会影响元素的使用情况:

  1. put 当新增加一个集合元素对象,则表示该对象是最近被访问的
  2. get 操作会把当前访问的元素对象作为最近被访问的,会被移到链接表头

注:当执行containsKey和containsValue操作时,不会影响元素的访问情况。
LRUMap也是非线程安全。在多线程下使用可通过Collections.synchronizedMap(Map)操作来保证线程安全。

get操作

  1. public Object get(Object key) {
  2. LinkEntry entry = (LinkEntry) getEntry(key);
  3. if (entry == null) {
  4. return null;
  5. }
  6. moveToMRU(entry);//执行LRU操作
  7. return entry.getValue();
  8. }
  9. //moveToMRU方法代码如下:
  10. protected void moveToMRU(LinkEntry entry) {
  11. if (entry.after != header) {
  12. modCount++;
  13. // remove 从链接中移除当前点
  14. entry.before.after = entry.after;
  15. entry.after.before = entry.before;
  16. // add first 把节点增加到链接头部
  17. entry.after = header;
  18. entry.before = header.before;
  19. header.before.after = entry;
  20. header.before = entry;
  21. } else if (entry == header) {
  22. throw new IllegalStateException("Can't move header to MRU" +
  23. " (please report this to commons-dev@jakarta.apache.org)");
  24. }
  25. }

put操作

  1. //由于继承的AbstractLinkedMap,所以put操作会调用addMapping方法,代码如下:
  2. protected void addMapping(int hashIndex, int hashCode, Object key, Object value) {
  3. if (isFull()) {
  4. LinkEntry reuse = header.after;
  5. boolean removeLRUEntry = false;
  6. if (scanUntilRemovable) {
  7. while (reuse != header && reuse != null) {
  8. if (removeLRU(reuse)) {
  9. removeLRUEntry = true;
  10. break;
  11. }
  12. reuse = reuse.after;
  13. }
  14. if (reuse == null) {
  15. throw new IllegalStateException(
  16. "Entry.after=null, header.after" + header.after + " header.before" + header.before +
  17. " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
  18. " Please check that your keys are immutable, and that you have used synchronization properly." +
  19. " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
  20. }
  21. } else {
  22. removeLRUEntry = removeLRU(reuse);
  23. }
  24. if (removeLRUEntry) {
  25. if (reuse == null) {
  26. throw new IllegalStateException(
  27. "reuse=null, header.after=" + header.after + " header.before" + header.before +
  28. " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
  29. " Please check that your keys are immutable, and that you have used synchronization properly." +
  30. " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
  31. }
  32. reuseMapping(reuse, hashIndex, hashCode, key, value);
  33. } else {
  34. super.addMapping(hashIndex, hashCode, key, value);
  35. }
  36. } else {
  37. super.addMapping(hashIndex, hashCode, key, value);
  38. }
  39. }
  40. //当集合的个数超过指定的最大个数时,会调用reuseMapping函数,把要删除的对象的key和value更新为新加入的值,并再次加入到链接表中,并重新排定次序
  41. protected void reuseMapping(LinkEntry entry, int hashIndex, int hashCode, Object key, Object value) {
  42. // find the entry before the entry specified in the hash table
  43. // remember that the parameters (except the first) refer to the new entry,
  44. // not the old one
  45. try {
  46. int removeIndex = hashIndex(entry.hashCode, data.length);
  47. HashEntry[] tmp = data; // may protect against some sync issues
  48. HashEntry loop = tmp[removeIndex];
  49. HashEntry previous = null;
  50. while (loop != entry && loop != null) {
  51. previous = loop;
  52. loop = loop.next;
  53. }
  54. if (loop == null) {
  55. throw new IllegalStateException(
  56. "Entry.next=null, data[removeIndex]=" + data[removeIndex] + " previous=" + previous +
  57. " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
  58. " Please check that your keys are immutable, and that you have used synchronization properly." +
  59. " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
  60. }
  61. // reuse the entry
  62. modCount++;
  63. removeEntry(entry, removeIndex, previous);
  64. reuseEntry(entry, hashIndex, hashCode, key, value);
  65. addEntry(entry, hashIndex);
  66. } catch (NullPointerException ex) {
  67. throw new IllegalStateException(
  68. "NPE, entry=" + entry + " entryIsHeader=" + (entry==header) +
  69. " key=" + key + " value=" + value + " size=" + size + " maxSize=" + maxSize +
  70. " Please check that your keys are immutable, and that you have used synchronization properly." +
  71. " If so, then please report this to commons-dev@jakarta.apache.org as a bug.");
  72. }
  73. }
  74. /**
  75. * Adds an entry into this map, maintaining insertion order.
  76. * <p>
  77. * This implementation adds the entry to the data storage table and
  78. * to the end of the linked list.
  79. *
  80. * @param entry the entry to add
  81. * @param hashIndex the index into the data array to store at
  82. */
  83. protected void addEntry(HashEntry entry, int hashIndex) {
  84. LinkEntry link = (LinkEntry) entry;
  85. link.after = header;
  86. link.before = header.before;
  87. header.before.after = link;
  88. header.before = link;
  89. data[hashIndex] = entry;
  90. }

removeEntry 方法是删除最近最少使用的节点在链接中的引用
reuseEntry方法则把该节点的key和value赋新加入的节点的key和value值
addEntry方法则把该节点加入到链接表,并保障相关的链接顺序