Lru 캐시 알고리즘의 간단한 설계 및 구현


머리말

LRU 알고리즘은 LRU 알고리즘이라고도 하며, 오랫동안 사용하지 않은 데이터는 향후 사용할 가능성이 낮기 때문에 새로운 데이터가 들어올 때 이를 대체할 수 있다는 기본 개념입니다. 먼저 데이터.


1. LinkedHashMap의 키 순서

LinkedHashMap은 특수한 Map 개체입니다. 맨 아래 레이어는 양방향 목록을 기반으로 Map에 키를 추가하거나 액세스하는 순서를 기록합니다.

1. 인수가 없는 생성자를 사용하여 LinkedHashMap 객체를 생성할 때 스토리지 객체는 기본적으로 키가 추가되는 순서를 기록합니다.다음 예에서 출력 결과는 {F=100, A=200, M입니다. =300}

 public static void main(String[] args) {
        LinkedHashMap<Object,Object> map=new LinkedHashMap<>();
        map.put("F", 100);
        map.put("A", 200);
        map.put("M", 300);
        map.get("A");
        System.out.println(map);

    }

2. 매개변수가 있는 생성 방법을 통해 LinkedHashMap 개체를 생성할 때 키 액세스 시퀀스가 ​​기록되며 다음 예와 같이 출력 결과는 다음과 같습니다.

{F=100,A=200,M=300,P=400}

 public static void main(String[] args) {
        LinkedHashMap<Object,Object> map=new LinkedHashMap<>(3,0.75f,true);
        map.put("F", 100);
        map.put("A", 200);
        map.put("M", 300);
        map.get("A");
        map.put("P",400);
        System.out.println(map);

    }

3. LinkedHashMap 클래스에는 removeEldestEntry() 메서드가 있습니다: 이 메서드는 컨테이너가 가득 차 있는지 확인하기 위해 put 메서드가 실행될 때마다 호출됩니다. 이 메서드의 반환 값은 Boolean입니다. 반환 값이 true이면 먼저 제거(가장 최근에 액세스: 가장 먼저 추가되고 나중에 액세스하지 않음)한 다음 넣습니다. 기본 반환 값은 false입니다.

LinkedHashMap 익명 내부 클래스 개체를 생성한 다음 removeEldestEntry() 메서드를 다시 작성하여 컨테이너가 가득 차면 요소를 제거하고 추가한다는 것을 인식합니다.예시는 다음과 같으며 출력 결과는 다음과 같습니다.

{F=100,A=200,E=400}

이 결과를 통해 LinkedHashMap을 기반으로 간단한 Lru 캐시를 설계할 수 있습니다.

 public static void main(String[] args) {
        LinkedHashMap<Object,Object> map=
                new LinkedHashMap(3,0.75f,true){
                    //每次执行put方法都会调用此方法,用于判断容器是否已经满了
                    //方法返回true表示已满,此时会先移除(最近访问最少)再放入。
                    @Override
                    protected boolean removeEldestEntry(Map.Entry eldest) {
                        return size()>3;//默认值是false(表示不移除,即便是满了)
                    }
                };
        map.put("F", 100);
        map.put("A", 200);
        map.put("M", 300);
        map.get("F");
        map.get("A");
        map.put("E", 400);
        System.out.println(map);

    }

그래픽:


2. 디자인 루루

1. 캐시 표준 인터페이스 준비

이 인터페이스는 사양 정의 역할을 하며 코드는 다음과 같습니다.

public interface Cache {
    
    void putObject(Object key,Object value);

    Object getObject(Object key);

    Object removeObject(Object key);

    void clear();

    int size();
}

2. 인터페이스 구현 클래스 준비

간단한 캐시 구현의 경우:

/**
 * 简易Cache实现
 * 1)存储结构:散列表(基于JAVA中的hashmap存储)
 * 2)淘汰算法:没有(直到到内存溢出)
 * 3)线程安全:否
 * 4)缓存对对象的引用?强引用
 * 5)对象获取:浅拷贝(获取对象地址)
 */
public class PerpetualCache implements Cache{

    private HashMap<Object,Object> cache=new HashMap<>();
    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
    }

    @Override
    public Object getObject(Object key) {
        return cache.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        return cache.remove(key);
    }

    @Override
    public void clear() {
        cache.clear();
    }

    @Override
    public int size() {
        return cache.size();
    }

    @Override
    public String toString() {
        return "PerpetualCache{" +
                "cache=" + cache.toString() +
                '}';
    }
}

3. 디자인 루루

네 가지 속성이 주어집니다.

데이터를 저장하는 데 사용되는 캐시 캐시

int maxCap, 최대 용량 설정

LinkedHashMap<Object,Object> keyAccessOrders, 키의 액세스 순서를 기록하는 데 사용됨

방문 횟수가 가장 적은 키를 기록하는 데 사용되는 객체 eldEstKey

a. 캐시에 요소를 추가할 때 keyAccessOrders에 요소를 추가할 때 기본 설정에 따라 keyAccessOrders의 값을 추가할 수 있으며 키는 순서를 보장하는 키이기 때문에 키는 캐시와 일치해야 합니다.

b. 마찬가지로 캐시에 액세스, 제거 또는 지울 때 keyAccessOrders를 동기화해야 합니다.

c.컨테이너가 찼을 때 eldEstKey는 값을 가지고 있고 접근 횟수가 가장 적은 키이므로 eldEstKey에 값이 있는지를 판단하여 컨테이너가 찼는지 여부를 판단할 수 있습니다. eldEstKey 값에 따라 최소 액세스 수(keyAccessOrders는 자동으로 제거됨) 및 eldEstKey를 null로 재할당

Override
            protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
                boolean isFull= size()>maxCap;
                if(isFull)eldEstKey=eldest.getKey();//获取最近访问次数最少的key
                return isFull;
            }
        };
    }

    @Override
    public void putObject(Object key, Object value) {
        //1.向Cache中添加新的元素
        cache.putObject(key, value);
        //2.记录key的访问顺序
        keyAccessOrders.put(key, key);
        //3.Cache满了则移除最近访问次数最少的key/value
        if(eldEstKey!=null){
            cache.removeObject(eldEstKey);
            eldEstKey=null;
        }
    }

    @Override
    public Object getObject(Object key) {
        //1.记录key的访问顺序
        keyAccessOrders.get(key);
        //2.返回cache中的指定key对应的value
        return cache.getObject(key);
    }

    @Override
    public Object removeObject(Object key) {
        Object object = cache.removeObject(key);
        keyAccessOrders.remove(key);
        return object;
    }

    @Override
    public void clear() {
           cache.clear();
           keyAccessOrders.clear();
    }

    @Override
    public int size() {
        return cache.size();
    }

    @Override
    public String toString() {
        return "LruCache{" +
                "cache=" + cache.toString() +
                '}';
    }

다음 테스트에서 출력 결과는 LruCache{cache=PerpetualCache{cache={A=100, D=400, E=500}}}입니다.

public static void main(String[] args) {
        Cache cache=new LruCache(//负责添加算法
                new PerpetualCache(),//负责存数据
                3);
        cache.putObject("A", 100);
        cache.putObject("B", 200);
        cache.putObject("C", 300);
        cache.getObject("A");
        cache.putObject("D", 400);
        cache.putObject("E", 500);
        System.out.println(cache);
    }

}

셋, 사용

실제 비즈니스를 예로 들어 보겠습니다.

클라이언트가 처음으로 서버에 요청을 보낼 때 콘솔은 데이터베이스에서 데이터 가져오기를 출력합니다. 해당 데이터는 키 쿼리 캐시를 통해 쿼리할 수 없기 때문에 데이터가 데이터베이스에서 요청되어 캐시에 저장되기 때문입니다.

클라이언트가 서버에 다시 요청을 보낼 때 캐시된 데이터가 제거되지 않는 한 콘솔은 아무 것도 출력하지 않습니다. 서버가 캐시에서 데이터를 직접 가져오기 때문입니다.

private Cache cache=new LruCache(new PerpetualCache(),3);
@GetMapping("/list")
public List<User> list(){
    Object obj = cache.getObject("userListKey");
    if(obj != null){
        return (List<User>)obj;//向下造型
    }
    System.out.println("Get Data from Database");
    List<User> list = mapper.list();
    cache.putObject("userListKey",list);
    return list;
}


요약하다

Lru 알고리즘만 설계하고 간단하게 구현하면 HashLinkedMap 클래스의 키 순서만 생각하면 설계 방법이 더 간단해질 수 있다. 나중에 장식.

추천

출처blog.csdn.net/weixin_72125569/article/details/126754008