Apache 为我们提供了一个 commons-collections 的框架,里面有一个非常好用的数据结构 LRUMap 可以保存指定数量的固定的数据,并且它会按照 LRU 算法,帮你清除最不常用的数据。
LRU 是 Least Recently Used 的缩写,即最近最少使用,是一种常用的数据淘汰算法,选择最近最久未使用的数据予以淘汰。
import org.apache.commons.collections4.map.LRUMap;
/**
* 幂等性判断
*/
public class IdempotentUtils {
// 根据 LRU(Least Recently Used,最近最少使用)算法淘汰数据的 Map 集合,最大容量 100 个
private static LRUMap reqCache = new LRUMap<>(100);/**
* 幂等性判断
* @return
*/public static boolean judge(String id, Object lockClass) {
synchronized (lockClass) {
// 重复请求判断
if (reqCache.containsKey(id)) {
// 重复请求
System.out.println("请勿重复提交!!!" + id);return false;
}// 非重复请求,存储请求 ID
reqCache.put(id, 1);
}return true;
}
}
实现如下:
import com.example.idempote.util.IdempotentUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController4 {
@RequestMapping("/add")
public String addUser(String id) {
// 非空判断(忽略)...
// -------------- 幂等性调用(开始) --------------
if (!IdempotentUtils.judge(id, this.getClass())) {
return "执行失败";
}
// -------------- 幂等性调用(结束) --------------
// 业务代码...
System.out.println("添加用户ID:" + id);
return "执行成功!";
}
}
//LRUMap 的本质是持有头结点的环回双链表结构,它的存储结构如下:
AbstractLinkedMap.LinkEntry entry;
//当调用查询方法时,会将使用的元素放在双链表 header 的前一个位置,源码如下:
public V get(Object key, boolean updateToMRU) {
LinkEntry entry = this.getEntry(key);if (entry == null) {return null;
} else {if (updateToMRU) {this.moveToMRU(entry);
}return entry.getValue();
}
}protected void moveToMRU(LinkEntry entry) {if (entry.after != this.header) {
++this.modCount;if (entry.before == null) {throw new IllegalStateException("Entry.before is null. This should not occur if your keys are immutable, and you have used synchronization properly.");
}
entry.before.after = entry.after;
entry.after.before = entry.before;
entry.after = this.header;
entry.before = this.header.before;this.header.before.after = entry;this.header.before = entry;
} else if (entry == this.header) {throw new IllegalStateException("Can't move header to MRU This should not occur if your keys are immutable, and you have used synchronization properly.");
}
}
//如果新增元素时,容量满了就会移除 header 的后一个元素,添加源码如下:
protected void addMapping(int hashIndex, int hashCode, K key, V value) {
// 判断容器是否已满
if (this.isFull()) {
LinkEntry reuse = this.header.after;boolean removeLRUEntry = false;if (!this.scanUntilRemovable) {
removeLRUEntry = this.removeLRU(reuse);
} else {while(reuse != this.header && reuse != null) {if (this.removeLRU(reuse)) {
removeLRUEntry = true;break;
}
reuse = reuse.after;
}if (reuse == null) {throw new IllegalStateException("Entry.after=null, header.after=" + this.header.after + " header.before=" + this.header.before + " key=" + key + " value=" + value + " size=" + this.size + " maxSize=" + this.maxSize + " This should not occur if your keys are immutable, and you have used synchronization properly.");
}
}if (removeLRUEntry) {if (reuse == null) {throw new IllegalStateException("reuse=null, header.after=" + this.header.after + " header.before=" + this.header.before + " key=" + key + " value=" + value + " size=" + this.size + " maxSize=" + this.maxSize + " This should not occur if your keys are immutable, and you have used synchronization properly.");
}this.reuseMapping(reuse, hashIndex, hashCode, key, value);
} else {super.addMapping(hashIndex, hashCode, key, value);
}
} else {super.addMapping(hashIndex, hashCode, key, value);
}
}
// LRUMap 判断容量的源码
public boolean isFull() {
return size >= maxSize;
}
//容量未满就直接添加数据:
super.addMapping(hashIndex, hashCode, key, value);
综合来说:LRUMap 的本质是持有头结点的环回双链表结构,当使用元素时,就将该元素放在双链表 header 的前一个位置,在新增元素时,如果容量满了就会移除 header 的后一个元素。