java LRU(Least Recently Used) 상세 정보 및 인스턴스 코드

13946 단어 javaLRU
java LRU(Least Recently Used) 상세 정보
LRU는 Least Recently Used의 줄임말로 번역하면'최근에 가장 적게 사용한다'는 것이다. LRU 캐시는 이런 원리로 이루어진다. 간단하게 말하면 일정량의 데이터를 캐시하는 것이다. 설정된 한도값을 초과할 때 기한이 지난 데이터를 삭제한다. 예를 들어 우리는 10000개의 데이터를 캐시한다. 데이터가 10000개보다 작을 때 마음대로 추가할 수 있고 10000개가 넘을 때 새로운 데이터를 추가해야 한다.동시에 기한이 지난 데이터를 삭제하여 우리의 최대 캐시 10000개를 확보해야 한다. 그러면 어떤 기한이 지난 데이터를 삭제할지 어떻게 확정할 수 있겠는가. LRU 알고리즘으로 실현하면 가장 오래된 데이터를 삭제하는 것이다. 쓸데없는 말은 하지 않겠다. 다음은 자바 버전의 LRU 캐시 실현
자바에서 LRU 캐시를 실현하는 데는 보통 두 가지 선택이 있는데 하나는 LinkedHashMap을 사용하는 것이고 하나는 데이터 구조를 스스로 설계하는 것이다. 체인 테이블+HashMap을 사용하는 것이다
LRU Cache의 LinkedHashMap 구현
LinkedHashMap 자체는 이미 순서 저장을 실현했다. 기본적으로 요소의 추가 순서에 따라 저장할 수도 있고 접근 순서에 따라 저장할 수도 있다. 즉, 최근에 읽은 데이터를 맨 앞에 놓고 가장 먼저 읽은 데이터를 맨 뒤에 놓는다. 그리고 가장 오래된 데이터를 삭제할지 여부를 판단하는 방법도 있다. 기본적으로false를 되돌려주는 것이다. 즉, 데이터를 삭제하지 않는 것이다.우리가 LinkedHashMap을 사용하여 LRU 캐시를 실현하는 방법은 LinkedHashMap에 대해 간단한 확장을 실현하는 것이다. 확장 방식은 두 가지가 있는데 하나는 inheritance이고 하나는delegation이다. 구체적으로 어떤 방식으로 개인의 취향을 보느냐에 따라

//LinkedHashMap , accessOrder true , , , 
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
    super(initialCapacity, loadFactor);
    this.accessOrder = accessOrder;
}

//LinkedHashMap , false, 
// , 
protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
    return false;
}

LRU 캐시 LinkedHashMap(inheritance) 구현
inheritance 방식으로 실현하는 것이 비교적 간단하고 맵 인터페이스를 실현하여 다중 스레드 환경에서 사용할 때 Collections를 사용할 수 있다.synchronizedMap () 방법으로 스레드 보안 작업 수행


package cn.lzrabbit.structure.lru;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by liuzhao on 14-5-15.
 */
public class LRUCache2<K, V> extends LinkedHashMap<K, V> {
  private final int MAX_CACHE_SIZE;

  public LRUCache2(int cacheSize) {
    super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
    MAX_CACHE_SIZE = cacheSize;
  }

  @Override
  protected boolean removeEldestEntry(Map.Entry eldest) {
    return size() > MAX_CACHE_SIZE;
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<K, V> entry : entrySet()) {
      sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue()));
    }
    return sb.toString();
  }
}

이렇게 하면 비교적 표준적인 실현이라고 할 수 있겠지, 실제 사용에서 이렇게 쓰는 것은 여전히 좀 번거롭고, 더욱 실용적인 방법은 아래와 같이 써서, 단독으로 한 종류의 번거로움을 줄였다


final int cacheSize = 100;
Map<String, String> map = new LinkedHashMap<String, String>((int) Math.ceil(cacheSize / 0.75f) + 1, 0.75f, true) {
  @Override
  protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
  return size() > cacheSize;
  }
};

LRU 캐시 LinkedHashMap(delegation) 구현
delegation 방식은 더욱 우아하게 실현되지만 맵 인터페이스가 실현되지 않았기 때문에 스레드 동기화는 스스로 해결해야 한다


package cn.lzrabbit.structure.lru;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

/**
 * Created by liuzhao on 14-5-13.
 */
public class LRUCache3<K, V> {

  private final int MAX_CACHE_SIZE;
  private final float DEFAULT_LOAD_FACTOR = 0.75f;
  LinkedHashMap<K, V> map;

  public LRUCache3(int cacheSize) {
    MAX_CACHE_SIZE = cacheSize;
    // cacheSize hashmap capactiy,+1 cacheSize hashmap ,
    int capacity = (int) Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTOR) + 1;
    map = new LinkedHashMap(capacity, DEFAULT_LOAD_FACTOR, true) {
      @Override
      protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > MAX_CACHE_SIZE;
      }
    };
  }

  public synchronized void put(K key, V value) {
    map.put(key, value);
  }

  public synchronized V get(K key) {
    return map.get(key);
  }

  public synchronized void remove(K key) {
    map.remove(key);
  }

  public synchronized Set<Map.Entry<K, V>> getAll() {
    return map.entrySet();
  }

  public synchronized int size() {
    return map.size();
  }

  public synchronized void clear() {
    map.clear();
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry entry : map.entrySet()) {
      sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue()));
    }
    return sb.toString();
  }
}

LRU Cache의 체인 테이블 + HashMap 구현
주: 이 구현은 비스레드 안전입니다. 다중 스레드 환경에서 사용하려면 관련 방법에synchronized를 추가하여 스레드 안전 조작을 실현해야 합니다.


package cn.lzrabbit.structure.lru;


import java.util.HashMap;

/**
 * Created by liuzhao on 14-5-12.
 */
public class LRUCache1<K, V> {

  private final int MAX_CACHE_SIZE;
  private Entry first;
  private Entry last;

  private HashMap<K, Entry<K, V>> hashMap;

  public LRUCache1(int cacheSize) {
    MAX_CACHE_SIZE = cacheSize;
    hashMap = new HashMap<K, Entry<K, V>>();
  }

  public void put(K key, V value) {
    Entry entry = getEntry(key);
    if (entry == null) {
      if (hashMap.size() >= MAX_CACHE_SIZE) {
        hashMap.remove(last.key);
        removeLast();
      }
      entry = new Entry();
      entry.key = key;
    }
    entry.value = value;
    moveToFirst(entry);
    hashMap.put(key, entry);
  }

  public V get(K key) {
    Entry<K, V> entry = getEntry(key);
    if (entry == null) return null;
    moveToFirst(entry);
    return entry.value;
  }

  public void remove(K key) {
    Entry entry = getEntry(key);
    if (entry != null) {
      if (entry.pre != null) entry.pre.next = entry.next;
      if (entry.next != null) entry.next.pre = entry.pre;
      if (entry == first) first = entry.next;
      if (entry == last) last = entry.pre;
    }
    hashMap.remove(key);
  }

  private void moveToFirst(Entry entry) {
    if (entry == first) return;
    if (entry.pre != null) entry.pre.next = entry.next;
    if (entry.next != null) entry.next.pre = entry.pre;
    if (entry == last) last = last.pre;

    if (first == null || last == null) {
      first = last = entry;
      return;
    }

    entry.next = first;
    first.pre = entry;
    first = entry;
    entry.pre = null;
  }

  private void removeLast() {
    if (last != null) {
      last = last.pre;
      if (last == null) first = null;
      else last.next = null;
    }
  }


  private Entry<K, V> getEntry(K key) {
    return hashMap.get(key);
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    Entry entry = first;
    while (entry != null) {
      sb.append(String.format("%s:%s ", entry.key, entry.value));
      entry = entry.next;
    }
    return sb.toString();
  }

  class Entry<K, V> {
    public Entry pre;
    public Entry next;
    public K key;
    public V value;
  }
}

LinkedHashMap의 FIFO 구현
FIFO는 First Input First Output의 줄임말이다. 즉, 흔히 말하는 선입선출이다. 기본적으로 LinkedHashMap은 추가 순서에 따라 저장된다. 우리는removeEldest Entry 방법을 다시 쓰면 FIFO 캐시를 쉽게 실현할 수 있다. 간략한 버전의 실현 코드는 다음과 같다.


final int cacheSize = 5;
LinkedHashMap<Integer, String> lru = new LinkedHashMap<Integer, String>() {
  @Override
  protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) {
  return size() > cacheSize;
  }
};

호출 예제

package cn.lzrabbit.structure.lru;

import cn.lzrabbit.ITest;

import java.util.LinkedHashMap;
import java.util.Map;

/**
 * Created by liuzhao on 14-5-15.
 */
public class LRUCacheTest {

  public static void main(String[] args) throws Exception {
    System.out.println("start...");

    lruCache1();
    lruCache2();
    lruCache3();
    lruCache4();
   
    System.out.println("over...");
  }
 

 static  void lruCache1() {
    System.out.println();
    System.out.println("===========================LRU  ===========================");
    LRUCache1<Integer, String> lru = new LRUCache1(5);
    lru.put(1, "11");
    lru.put(2, "11");
    lru.put(3, "11");
    lru.put(4, "11");
    lru.put(5, "11");
    System.out.println(lru.toString());
    lru.put(6, "66");
    lru.get(2);
    lru.put(7, "77");
    lru.get(4);
    System.out.println(lru.toString());
    System.out.println();
  }


static  <T> void lruCache2() {
    System.out.println();
    System.out.println("===========================LRU LinkedHashMap(inheritance) ===========================");
    LRUCache2<Integer, String> lru = new LRUCache2(5);
    lru.put(1, "11");
    lru.put(2, "11");
    lru.put(3, "11");
    lru.put(4, "11");
    lru.put(5, "11");
    System.out.println(lru.toString());
    lru.put(6, "66");
    lru.get(2);
    lru.put(7, "77");
    lru.get(4);
    System.out.println(lru.toString());
    System.out.println();
  }

 static void lruCache3() {
    System.out.println();
    System.out.println("===========================LRU LinkedHashMap(delegation) ===========================");
    LRUCache3<Integer, String> lru = new LRUCache3(5);
    lru.put(1, "11");
    lru.put(2, "11");
    lru.put(3, "11");
    lru.put(4, "11");
    lru.put(5, "11");
    System.out.println(lru.toString());
    lru.put(6, "66");
    lru.get(2);
    lru.put(7, "77");
    lru.get(4);
    System.out.println(lru.toString());
    System.out.println();
  }

 static void lruCache4() {
    System.out.println();
    System.out.println("===========================FIFO LinkedHashMap ===========================");
    final int cacheSize = 5;
    LinkedHashMap<Integer, String> lru = new LinkedHashMap<Integer, String>() {
      @Override
      protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) {
        return size() > cacheSize;
      }
    };
    lru.put(1, "11");
    lru.put(2, "11");
    lru.put(3, "11");
    lru.put(4, "11");
    lru.put(5, "11");
    System.out.println(lru.toString());
    lru.put(6, "66");
    lru.get(2);
    lru.put(7, "77");
    lru.get(4);
    System.out.println(lru.toString());
    System.out.println();
  }

}

실행 결과


"C:\Program Files (x86)\Java\jdk1.6.0_10\bin\java" -Didea.launcher.port=7535 "-Didea.launcher.bin.path=C:\Program Files (x86)\JetBrains\IntelliJ IDEA 13.0.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\charsets.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\deploy.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\javaws.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\jce.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\jsse.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\management-agent.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\plugin.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\resources.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\rt.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\ext\dnsns.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\ext\localedata.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\ext\sunjce_provider.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\ext\sunmscapi.jar;C:\Program Files (x86)\Java\jdk1.6.0_10\jre\lib\ext\sunpkcs11.jar;D:\SVN\projects\Java\Java.Algorithm\target\test-classes;D:\SVN\projects\Java\Java.Algorithm\target\classes;C:\Program Files (x86)\JetBrains\IntelliJ IDEA 13.0.2\lib\idea_rt.jar" com.intellij.rt.execution.application.AppMain Main
start...

===========================LRU  ===========================
5:11 4:11 3:11 2:11 1:11 
4:11 7:77 2:11 6:66 5:11 


===========================LRU LinkedHashMap(inheritance) ===========================
1:11 2:11 3:11 4:11 5:11 
5:11 6:66 2:11 7:77 4:11 


===========================LRU LinkedHashMap(delegation) ===========================
1:11 2:11 3:11 4:11 5:11 
5:11 6:66 2:11 7:77 4:11 


===========================FIFO LinkedHashMap ===========================
{1=11, 2=11, 3=11, 4=11, 5=11}
{3=11, 4=11, 5=11, 6=66, 7=77}

over...

Process finished with exit code 0

읽어주셔서 감사합니다. 여러분에게 도움이 되었으면 좋겠습니다. 본 사이트에 대한 지지에 감사드립니다!

좋은 웹페이지 즐겨찾기