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 メソッドが実行されるたびに呼び出され、コンテナがいっぱいかどうかを判断します. このメソッドの戻り値はブール値です. 戻り値が 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.Lruを設計する

1. Cache 標準インターフェースの準備

このインターフェイスは仕様定義として機能し、コードは次のとおりです。

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.Lruを設計する

与えられた 4 つのプロパティ:

データを保存するために使用されるキャッシュキャッシュ

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);
    }

}

三、使う

例として、実際のビジネスを取り上げます。

クライアントが初めてサーバーにリクエストを送信すると、コンソールは Get Data from Database を出力します。これは、キー クエリ キャッシュを介して対応するデータをクエリできないため、データはデータベースからリクエストされ、キャッシュに格納されます。

クライアントがサーバーにリクエストを再度送信すると、キャッシュされたデータが削除されない限り、コンソールは何も出力しません。これは、サーバーがキャッシュからデータを直接フェッチするためです。

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