常见算法
缓存
LFU
使用了Map来存储缓存数据,使用PriorityQueue来维护访问频次的优先级,并通过Node类来封装每个缓存项的key、value、访问频次和时间戳等信息。在get和put方法中,通过操作PriorityQueue来更新访问频次和移除访问频次最低的缓存项,同时也更新了缓存项的时间戳。
java
package com.example.demo.util;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
/**
* @author dehua.xiang
* @date 2023/3/24-10:20
*/
public class LFUCache<T> {
/**
* 内部类Node用于存储key和value以及对应的访问次数和最后访问的时间戳
* @param <T>
*/
private static class Node<T> implements Comparable<Node<T>> {
String key;
T value;
int frequency;
long timestamp;
public Node(String key, T value) {
this.key = key;
this.value = value;
this.frequency = 0;
this.timestamp = System.nanoTime();
}
// compareTo方法用于PriorityQueue的排序,优先级低的先被移除
@Override
public int compareTo(Node other) {
if (this.frequency == other.frequency) {
return (int)(this.timestamp - other.timestamp);
}
return this.frequency - other.frequency;
}
}
/**
* 缓存的最大容量
* 当缓存中的元素数量达到了capacity时,需要进行淘汰策略,即移除访问频次最低的元素,给新元素腾出空间。
*/
private final int capacity;
/**
* 缓存
*/
private final Map<String, Node<T>> cache;
/**
* 优先级队列
*/
private final PriorityQueue<Node<T>> queue;
/**
* 记录节点最后一次被访问的时间戳
*/
private long timestamp;
public LFUCache(int capacity) {
this.capacity = capacity;
cache = new HashMap<>(capacity);
queue = new PriorityQueue<>(capacity);
timestamp = 0;
}
public T get(String key) {
if (!cache.containsKey(key)) {
return null;
}
this.timestamp = System.nanoTime();
Node<T> node = cache.get(key);
queue.remove(node);
node.frequency++;
node.timestamp = System.nanoTime();
queue.offer(node);
return node.value;
}
public void put(String key, T value) {
if (capacity == 0) {
return;
}
Node<T> node = cache.get(key);
if (node != null) {
node.value = value;
node.frequency++;
node.timestamp = System.nanoTime();
} else {
if (cache.size() >= capacity) {
// 获取队首元素
Node<T> removeNode = queue.peek();
if (removeNode != null) {
cache.remove(removeNode.key);
// 移除队首元素
queue.poll();
}
}
node = new Node<>(key, value);
cache.put(key, node);
queue.offer(node);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
LRU
一种常见的缓存淘汰策略。 缓存是一种用于存储计算机程序运行时频繁使用的数据的技术,目的是为了加快程序的执行速度。LRU Cache 则是一种能够最大化缓存命中率的技术,它通过保留最近最常使用的数据项,而淘汰最近最少使用的数据项来实现空间上的限制。 当一个数据项被访问时,LRU Cache 会将这个数据项移动到缓存列表的头部(或者更新位置),表示这个数据项是最近被使用过的。当缓存已满时,LRU Cache 会将缓存列表末尾的数据项淘汰掉,因为它们是最近最少使用的。
java
package com.example.demo.util;
import java.util.HashMap;
import java.util.Map;
/**
* @author dehua.xiang
* @date 2023/3/24-10:48
*/
public class LRUCache<K, V> {
private final int capacity;
private final Map<K, Node<K, V>> cacheMap;
private Node<K, V> head;
private Node<K, V> tail;
public LRUCache(int capacity) {
this.capacity = capacity;
this.cacheMap = new HashMap<>(capacity);
}
public V get(K key) {
Node<K, V> node = cacheMap.get(key);
if (node == null) {
return null;
}
updateNode(node);
return node.value;
}
public void put(K key, V value) {
Node<K, V> node = cacheMap.get(key);
if (node != null) {
node.value = value;
updateNode(node);
} else {
if (cacheMap.size() >= capacity) {
removeHead();
}
Node<K, V> newNode = new Node<>(key, value);
cacheMap.put(key, newNode);
addToTail(newNode);
}
}
private void updateNode(Node<K, V> node) {
if (node == tail) {
return;
}
// 头结点的后驱结点设置为队列头结点
if (node == head) {
head = head.next;
} else {
// 当前的前驱的后继结点指向 队列的后驱结点
node.prev.next = node.next;
node.next.prev = node.prev;
}
// 头结点直接移动到队尾
addToTail(node);
}
private void removeHead() {
cacheMap.remove(head.key);
if (head == tail) {
head = null;
tail = null;
} else {
head = head.next;
head.prev = null;
}
}
private void addToTail(Node<K, V> node) {
if (tail == null) {
head = node;
} else {
tail.next = node;
node.prev = tail;
node.next = null;
}
tail = node;
}
private static class Node<K, V> {
K key;
V value;
Node<K, V> prev;
Node<K, V> next;
Node(K key, V value) {
this.key = key;
this.value = value;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
TimedCache
java
package com.example.demo.util;
import java.util.HashMap;
import java.util.Map;
/**
* @author dehua.xiang
* @date 2023/3/24-11:06
*/
public class TimedCache<K, V> {
// 用 HashMap 存储缓存项
private final Map<K, CacheItem<V>> cacheMap;
public TimedCache() {
this.cacheMap = new HashMap<>();
}
public void put(K key, V value){
put(key,value,-1);
}
public synchronized void put(K key, V value, long expireTime) {
long expireTimestamp = (expireTime > 0) ? (System.currentTimeMillis() + expireTime) : Long.MAX_VALUE;
cacheMap.put(key, new CacheItem<>(value, expireTimestamp));
}
public V get(K key) {
CacheItem<V> item = cacheMap.get(key);
if (item == null || item.isExpired()) {
cacheMap.remove(key);
return null;
}
return item.getValue();
}
public synchronized void clear() {
cacheMap.clear();
}
private static class CacheItem<V> {
private final V value;
private final long expireTimestamp;
public CacheItem(V value, long expireTimestamp) {
this.value = value;
this.expireTimestamp = expireTimestamp;
}
public V getValue() {
return value;
}
public boolean isExpired() {
return System.currentTimeMillis() >= expireTimestamp;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
限流
java
public interface Limiter {
boolean allow();
}
1
2
3
4
2
3
4
CounterRateLimiter 计数器算法
java
public class CounterRateLimiter implements Limiter {
/**
* 限制的请求数量
*/
private final int limit;
/**
* 限制的时间间隔,单位为毫秒
*/
private final long interval;
/**
* 计数器,用于记录当前时间窗口内已经处理的请求数量
*/
private final AtomicInteger count = new AtomicInteger(0);
/**
* 上一次计数器重置的时间
*/
private volatile long lastResetTime;
public CounterRateLimiter(int limit, long interval) {
this.limit = limit;
this.interval = interval;
this.lastResetTime = System.currentTimeMillis();
}
@Override
public synchronized boolean allow() {
long now = System.currentTimeMillis();
// 超过时间间隔,需要重置计数器
if (now - lastResetTime >= interval) {
count.set(0);
lastResetTime = now;
}
int currentCount = count.incrementAndGet();
// 超过限制的请求数量,拒绝处理请求
return currentCount <= limit;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
LeakyBucketRateLimiter 漏桶算法
java
public class LeakyBucketRateLimiter implements Limiter {
/**
* 桶的容量
*/
private final int capacity;
/**
* 流出速率,单位为请求数/秒
*/
private final int rate;
/**
* 桶中当前的水量
*/
private int water;
/**
* 上一次漏水的时间
*/
private long lastLeakTime;
public LeakyBucketRateLimiter(int capacity, int rate) {
this.capacity = capacity;
this.rate = rate;
this.water = 0;
this.lastLeakTime = System.currentTimeMillis();
}
@Override
public synchronized boolean allow() {
long currentTime = System.currentTimeMillis();
long timeElapsed = currentTime - lastLeakTime;
//漏水的数量
int leakedWater = (int) (timeElapsed * rate / 1000);
//更新桶中的水量
water = Math.max(0, water - leakedWater);
lastLeakTime = currentTime;
//桶未满,可以处理请求
if (water < capacity) {
water++;
return true;
}
//桶已满,拒绝处理请求
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
TokenBucketLimiter 令牌桶算法
java
public class TokenBucketLimiter implements Limiter {
/**
* 令牌桶的容量
*/
private final int capacity;
/**
* 令牌产生速率
*/
private final double rate;
/**
* 当前令牌数量
*/
private double tokens;
/**
* 上一次令牌发放的时间戳
*/
private long timestamp;
public TokenBucketLimiter(int capacity, double rate) {
this.capacity = capacity;
this.rate = rate;
// 初始化时,令牌桶满
this.tokens = capacity;
this.timestamp = System.currentTimeMillis();
}
@Override
public boolean allow() {
return tryAcquire(1);
}
/**
* 尝试获取指定数量的令牌
*
* @param count 要获取的令牌数量
* @return 是否获取成功
*/
public synchronized boolean tryAcquire(int count) {
// 先更新令牌数量
refill();
// 如果令牌数量足够
if (tokens >= count) {
// 则减去相应数量的令牌
tokens -= count;
// 返回获取成功
return true;
} else {
// 否则返回获取失败
return false;
}
}
/**
* 更新令牌桶中的令牌数量
*/
private void refill() {
long now = System.currentTimeMillis();
// 计算时间间隔,单位为秒
double elapsedTime = (now - timestamp) / 1000.0;
// 计算新增的令牌数量
double newTokens = elapsedTime * rate;
// 更新令牌桶中的令牌数量
tokens = Math.min(tokens + newTokens, capacity);
// 更新时间戳
timestamp = now;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
SlidingWindowRateLimiter 滑动窗口算法
java
public class SlidingWindowRateLimiter implements Limiter {
/**
* 窗口大小
*/
private final Integer windowSize;
/**
* 窗口内最多允许的请求数量
*/
private final Integer limit;
/**
* 窗口计数器
*/
private final Integer[] counter;
/**
* 窗口当前的位置
*/
private Integer index;
/**
* 窗口的起始时间
*/
private Long startTime;
public SlidingWindowRateLimiter(Integer windowSize, Integer limit) {
this.windowSize = windowSize;
this.limit = limit;
this.counter = new Integer[windowSize];
this.index = 0;
this.startTime = System.currentTimeMillis() / 1000;
}
/**
* 尝试执行一个请求
* @return 是否允许执行该请求
*/
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis() / 1000;
int offset = (int) (now - startTime) % windowSize;
// 如果窗口已经滑动,则重置计数器
if (offset != index) {
counter[index] = 0;
index = offset;
startTime = now;
}
// 如果窗口内的请求数量超过了限制,则拒绝该请求
if (counter[index] >= limit) {
return false;
}
// 计数器加1,并允许执行该请求
counter[index]++;
return true;
}
@Override
public boolean allow() {
return tryAcquire();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59