2009-06-15 4 views
15

자바는 LinkedHashMap이 gets you 99% there to an LRU cache입니다.자바에서 LRU 캐시 구현

  1. 이해할 수
  2. 효율적인 (상각 O (1)/삭제/넣어 수)
:

은 바람직하게는 신뢰할 수있는 소스에서, 즉, LRU 캐시의 자바 스크립트 구현이 있습니까

? 웹에서 검색했지만 찾지 못했습니다. 나는 Ajax Design Patterns에 하나를 발견했다고 생각했지만 sendToTail() 메소드를 통해 빛나고 O (n) 성능을 가지고 있습니다 (아마도 대기열과 연관 배열이 분리되어 있기 때문일 것입니다).

은 내가 내 자신을 작성할 수 있습니다 생각하지만 핵심 알고리즘 바퀴를 개혁하는 것은 하나의 건강에 위험 할 수있는 하드 방법을 배운 :/

답변

7

이 :

https://github.com/monsur/jscache

비록 당신의 경우에 맞을 것 같습니다. 최악의 경우 setItem (예 : put)은 O (N)입니다. 삽입시 캐시가 가득 차면 발생합니다. 이 경우 만료 된 항목이나 가장 최근에 사용하지 않은 항목을 제거하려면 캐시가 검색됩니다. getItem은 O (1)이고 만료는 getItem 연산에서 처리됩니다 (즉, 가져 오는 항목이 만료 된 경우 제거하고 null을 반환합니다).

코드는 이해하기 쉽도록 충분히 컴팩트합니다.

P. 생성자에 fillFactor을 지정하는 옵션을 추가하는 것이 유용 할 수 있습니다.이 옵션은 0.75로 고정되어 있습니다 (캐시가 제거 될 때 크기가 최소한 최대 크기의 3/4까지 줄어든다는 것을 의미).

+0

덕분에, 나는 걸쳐 실행했다. 내 응용 프로그램에 너무 많은 종소리와 호루라기가있는 것 같았습니다. (내 생각에 거대한 붉은 깃발 인 ASP.NET은 말할 것도없고) 어쩌면 다른 모양을 주어야합니다. –

+0

+1 구현은 ASP.NET과 아무런 관련이 없습니다. 나는 그럴 가치가 있다고 생각합니다. –

0

LRU 캐시,하지만 난 my own linked map implementation 있어요. JS 객체를 저장소로 사용하므로 유사한 성능 특성을 갖게됩니다 (래퍼 객체와 해시 함수는 성능 저하를 낳습니다).

현재 문서는 basically non-existant이지만, related SO answer입니다.

each() 메서드는 현재 키와 현재 값 및 더 많은 요소가 콜백 함수에 대한 인수로 있는지 나타내는 부울 값을 전달합니다.

또는 루프는 monsur.com 구현은

for(var i = map.size; i--; map.next()) { 
    var currentKey = map.key(); 
    var currentValue = map.value(); 
} 
3

를 통해 수동으로 실제로 현실 세계의 시간에 만료 항목이있는 경우에만 있기 때문에 삽입시 O (n)이된다 할 수 있습니다. 그것은 단순한 LRU가 아닙니다. 실제 시간과 관계없이 가장 최근에 사용한 항목을 유지하는 데만 신경 쓰면 O (1)에서 수행 할 수 있습니다. 이중 연결 목록으로 구현 된 대기열은 끝에 삽입하거나 삭제할 때 O (1)이며 이는 캐시에 필요한 것입니다. 조회에 관해서는 자바 스크립트가 쉽게 만들 수있는 해시지도는 거의 O (1) 룩업 (javascript 엔진이 좋은 해시 맵을 사용한다고 가정하면 구현에 따라 달라질 수 있습니다). 따라서 항목을 가리키는 해시 맵이있는 연결된 항목 목록이 있습니다. 필요에 따라 연결된 목록의 끝을 조작하여 한쪽 끝에 새 항목과 요청한 항목을 넣고 다른 쪽에서 오래된 항목을 제거합니다.

+1

LRU 캐시에서 항목을 제거하고 다시 삽입하면 링크 된 목록이 중간에서 항목을 삭제할 수 있어야합니다 (삽입하지 않아야 함). 그게 어려운 부분인데, 그 대답은 그걸 극명하게하는 것 같습니다. –

+1

중복 적으로 이중 링크 된 목록의 중간에서 제거하는 것은 O (n)입니다. LRU invariant를 액세스 할 때 유지 관리해야합니다. – Eloff

+0

@Eloff, O (1)가있는 목록의 모든 요소에 도달하기위한 추가 해시 맵이 있습니다. 그러나 당신과 "Jason S"는 끝을 조작하는 것만으로는 충분하지 않습니다. 목록의 어느 위치에있는 항목이라도 앞에있는 위치로 돌아갈 필요가있는 다음 항목이 될 수 있으므로 삽입이 한쪽 끝에서 제거 될 수 있습니다. 어느 위치에서나있을 수 있습니다. 그래도 목록의 길이에 관계없이 수행 할 수있는 해시 맵 덕분입니다. –

0

이 질문은 오래된 질문이지만 향후 참조 할 수있는 링크가 추가되었음을 알고 있습니다. https://github.com/monmohan/dsjslib을 확인하십시오. 이것은 다른 데이터 구조에 추가하여 LRU 캐시 구현을가집니다. 이러한 캐시 (및 이것도)는 LRU 순서에서 캐시 항목의 이중 연결 목록을 유지합니다. 즉 항목이 액세스 될 때 헤드로 ​​이동하고 캐시가 회수 될 때 (예 : 만료 또는 크기 제한에 도달했기 때문에) 꼬리에서 항목이 제거됩니다. O (1)은 포인터 조작의 일정한 수만 포함하기 때문에.

2

이 당신만큼 효율적이지 않다 있습니다 원하지만 단순성과 가독성 측면에서 그 이점을 보완합니다. 그리고 많은 경우에 그것은 충분히 빠를 것입니다.

적은 수의 비싼 작업 (1 초)을 위해 간단한 LRU 캐시가 필요했습니다. 외부에 뭔가를 소개하는 것보다는 복사하는 것이 더 좋았지 만 찾을 수 없었기 때문에 나는 그것을 썼다 :

업데이트 : 이제는 공간과 시간면에서 훨씬 효율적입니다. Map은 삽입 순서를 유지하므로 배열을 제거했습니다.

class LRU { 
    constructor(max=10) { 
     this.max = max; 
     this.cache = new Map(); 
    } 
    get(key) { 
     let item = this.cache.get(key); 
     if (item) // refresh key 
     { 
      this.cache.delete(key); 
      this.cache.set(key, item); 
     } 
     return item; 
    } 
    set(key, val) { 
     if (this.cache.has(key)) // refresh key 
      this.cache.delete(key); 
     else if (this.cache.size == this.max) // evict oldest 
      this.cache.delete(this._first()); 
     this.cache.set(key, val); 
    } 
    _first(){ 
     return this.cache.keys().next().value; 
    } 
} 

는 사용법 :

> let cache = new LRU(3) 
> [1, 2, 3, 4, 5].forEach(v => cache.set(v, 'v:'+v)) 
> cache.get(2) 
undefined 
> cache.get(3) 
"v:3" 
> cache.set(6, 6) 
> cache.get(4) 
undefined 
> cache.get(3) 
"v:3"