2016-04-02 26 views
13

Tôi đang cố gắng triển khai bộ nhớ cache LRU của riêng mình. Có, tôi biết rằng Java cung cấp một LinkedHashMap cho mục đích này, nhưng tôi đang cố gắng thực hiện nó bằng cách sử dụng các cấu trúc dữ liệu cơ bản.ConcurrentModificationException khi cập nhật Iterator được lưu trữ (để thực hiện bộ nhớ cache LRU)

Từ đọc về chủ đề này, tôi hiểu rằng tôi cần một HashMap cho O (1) tra cứu khóa và danh sách được liên kết để quản lý chính sách trục xuất "ít được sử dụng gần đây nhất". Tôi tìm thấy những tài liệu tham khảo mà tất cả sử dụng một hashmap thư viện chuẩn nhưng thực hiện danh sách liên kết riêng của họ:

bảng băm là nghĩa vụ phải lưu trữ trực tiếp một danh sách liên kết Node như tôi hiển thị bên dưới. Bộ nhớ cache của tôi sẽ lưu trữ các khóa Integer và các giá trị String.

enter image description here

Tuy nhiên, trong bộ sưu tập Java LinkedList không tiếp xúc với các nút nội bộ của mình, vì vậy tôi không thể lưu trữ chúng bên trong HashMap. Thay vào đó, tôi có thể có các chỉ mục cửa hàng HashMap vào LinkedList, nhưng sau đó nhận được một mục sẽ yêu cầu thời gian O (N). Vì vậy, tôi đã cố gắng để lưu trữ một ListIterator thay thế.

import java.util.Map; 
import java.util.HashMap; 
import java.util.List; 
import java.util.LinkedList; 
import java.util.ListIterator; 

public class LRUCache { 

    private static final int DEFAULT_MAX_CAPACITY = 10; 

    protected Map<Integer, ListIterator> _map = new HashMap<Integer, ListIterator>(); 
    protected LinkedList<String> _list = new LinkedList<String>(); 

    protected int _size = 0; 
    protected int _maxCapacity = 0; 

    public LRUCache(int maxCapacity) { 
     _maxCapacity = maxCapacity; 
    } 

    // Put the key, value pair into the LRU cache. 
    // The value is placed at the head of the linked list. 
    public void put(int key, String value) { 

     // Check to see if the key is already in the cache. 
     ListIterator iter = _map.get(key); 

     if (iter != null) { 
      // Key already exists, so remove it from the list. 
      iter.remove(); // Problem 1: ConcurrentModificationException! 
     } 

     // Add the new value to the front of the list. 
     _list.addFirst(value); 
     _map.put(key, _list.listIterator(0)); 

     _size++; 

     // Check if we have exceeded the capacity. 
     if (_size > _maxCapacity) { 
      // Remove the least recently used item from the tail of the list. 
      _list.removeLast(); 
     } 
    } 

    // Get the value associated with the key. 
    // Move value to the head of the linked list. 
    public String get(int key) { 

     String result = null; 
     ListIterator iter = _map.get(key); 

     if (iter != null) { 

      //result = iter 
      // Problem 2: HOW DO I GET THE STRING FROM THE ITERATOR? 

     } 

     return result; 
    } 

    public static void main(String argv[]) throws Exception { 
     LRUCache lruCache = new LRUCache(10); 

     lruCache.put(10, "This"); 
     lruCache.put(20, "is"); 
     lruCache.put(30, "a"); 
     lruCache.put(40, "test"); 
     lruCache.put(30, "some"); // Causes ConcurrentModificationException 
    } 
} 

Vì vậy, điều này dẫn đến ba vấn đề:

Vấn đề 1: Tôi nhận được một ConcurrentModificationException khi tôi cập nhật LinkedList sử dụng iterator mà tôi lưu trữ trong HashMap.

Exception in thread "main" java.util.ConcurrentModificationException 
    at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:953) 
    at java.util.LinkedList$ListItr.remove(LinkedList.java:919) 
    at LRUCache.put(LRUCache.java:31) 
    at LRUCache.main(LRUCache.java:71) 

Bài toán 2. Làm thế nào để lấy giá trị được chỉ bởi ListIterator? Có vẻ như tôi chỉ có thể lấy giá trị tiếp theo().

Sự cố 3. Có cách nào để thực hiện bộ nhớ cache LRU này bằng cách sử dụng các bộ sưu tập Java LinkedList, hay tôi thực sự phải thực hiện danh sách liên kết của riêng mình?

+2

Vâng, không có cách nào bạn sẽ thực hiện công việc đó. Bạn sẽ phải tự thực hiện lại ít nhất một trong các cấu trúc dữ liệu này nếu bạn muốn phát minh lại bánh xe này. –

Trả lời

1

tôi sẽ đối phó với vấn đề 3 đầu tiên:

Như bạn chỉ ra trong câu hỏi của bạn, LinkedList (giống như tất cả cũng được thiết kế bộ sưu tập chung) ẩn các chi tiết của việc thực hiện như các nút có chứa các liên kết. Trong trường hợp của bạn, bạn cần bản đồ băm của bạn để tham khảo các liên kết này trực tiếp như các giá trị của bản đồ. Để thực hiện cách khác (ví dụ: có hướng dẫn thông qua lớp thứ ba) sẽ đánh bại mục đích của bộ nhớ cache LRU để cho phép chi phí rất thấp trên truy cập giá trị. Nhưng điều này là không thể với Bộ sưu tập Java tiêu chuẩn - chúng không (và không nên) cung cấp quyền truy cập trực tiếp vào các cấu trúc bên trong.

Vì vậy, kết luận hợp lý của điều này là, có, bạn cần phải thực hiện theo cách riêng của bạn để lưu trữ thứ tự trong đó các mục trong bộ nhớ cache đã được sử dụng. Đó không phải là danh sách được liên kết kép. Những người này thường được sử dụng cho bộ nhớ cache LRU bởi vì hoạt động phổ biến nhất là di chuyển một nút lên đầu danh sách khi nó được truy cập. Đó là một hoạt động cực kỳ rẻ tiền trong một danh sách liên kết đôi yêu cầu chỉ có bốn nút được relinked không có cấp phát bộ nhớ hoặc miễn phí.

Vấn đề 1 & 2:

Về cơ bản nguyên nhân gốc rễ ở đây là bạn này đang cố gắng sử dụng vòng lặp như một con trỏ. Chúng được thiết kế để tạo ra, bước qua để thực hiện một số thao tác và sau đó được xử lý. Ngay cả khi bạn vượt qua những vấn đề bạn đang gặp phải, tôi hy vọng sẽ có thêm nhiều vấn đề đằng sau chúng. Bạn đang đặt một chốt hình vuông trong một lỗ tròn.

Vì vậy, kết luận của tôi là bạn cần phải thực hiện theo cách riêng của bạn để giữ các giá trị trong một lớp học theo dõi thứ tự truy cập. Tuy nhiên nó có thể cực kỳ đơn giản: chỉ có ba hoạt động được yêu cầu: tạo, lấy giá trị và loại bỏ khỏi đuôi. Cả tạo và nhận giá trị phải di chuyển nút đến đầu danh sách. Không chèn hoặc xóa từ giữa danh sách. Không xóa đầu. Không tìm kiếm. Trung thực chết đơn giản.

Hy vọng rằng điều này sẽ giúp bạn bắt đầu :-)

public class <K,V> LRU_Map implements Map<K,V> { 
    private class Node { 
     private final V value; 
     private Node previous = null; 
     private Node next = null; 

     public Node(V value) { 
      this.value = value; 
      touch(); 
      if (tail == null) 
       tail = this; 
     } 

     public V getValue() { 
      touch(); 
      return value; 
     } 

     private void touch() { 
      if (head != this) { 
       unlink(); 
       moveToHead(); 
      } 
     } 

     private void unlink() { 
      if (tail == this) 
       tail = prev; 
      if (prev != null) 
       prev.next = next; 
      if (next != null) 
       next.prev = prev; 
     } 

     private void moveToHead() { 
      prev = null; 
      next = head; 
      head = this; 
     } 

     public void remove() { 
      assert this == tail; 
      assert this != head; 
      assert next == null; 
      if (prev != null) 
       prev.next = null; 
      tail = prev; 
     } 
    } 

    private final Map<K,Node> map = new HashMap<>(); 
    private Node head = null; 
    private Node tail = null; 

    public void put(K key, V value) { 
     if (map.size() >= MAX_SIZE) { 
      assert tail != null; 
      tail.remove(); 
     } 
     map.put(key, new Node(value)); 
    } 

    public V get(K key) { 
     if (map.containsKey(key)) 
      return map.get(key).getValue(); 
     else 
      return null; 
    } 

    // and so on for other Map methods 
} 
+0

Cảm ơn. Làm cho cảm giác bây giờ. – stackoverflowuser2010

2

1) Đây không thực sự là những gì mà Iterator làm.

Bằng hợp đồng, nếu bạn sửa đổi danh sách mà không sử dụng iterator - như bạn làm ở đây

_list.addFirst(value);

sau đó lặp ALL MỞ trong danh sách đó nên ném ConcurrentModificationException. Họ đã được mở cho một phiên bản của danh sách mà không còn tồn tại.

2) Danh sách liên kết không chính xác là danh sách các nút được liên kết. Đó là một java.util.List, có thực hiện sao lưu là danh sách các nút được liên kết gấp đôi. Hợp đồng Danh sách đó là lý do tại sao nó không đưa ra các tham chiếu đến việc thực hiện sao lưu - vì vậy các hoạt động như "loại bỏ nút này, dưới dạng nút và di chuyển nó đến đầu" không tốt.Sự đóng gói này là để bảo vệ riêng bạn (giống như ngoại lệ mod đồng thời) - nó cho phép mã của bạn dựa vào ngữ nghĩa List của một LinkedList (ví dụ như iterability) mà không cần lo lắng rằng một số joker hai khối đã bị hack đi và phá vỡ hợp đồng.

3) Những gì bạn thực sự cần ở đây KHÔNG phải là một LinkedList. Những gì bạn cần là một ngăn xếp cho phép bạn di chuyển bất kỳ mục nhập tùy ý nào vào đầu và đổ đuôi. Bạn ngụ ý rằng bạn muốn có thời gian tìm kiếm nhanh chóng cho một mục nhập tùy ý và cũng nhanh chóng xóa và thêm nhanh, VÀ bạn muốn có thể tìm thấy đuôi bất kỳ lúc nào trong trường hợp bạn cần xóa nó.

nhanh thời gian tìm kiếm == Hash Something

nhanh thêm/gỡ bỏ các yếu tố độc đoán == Liên Kết Something

nhanh địa chỉ của phần tử thức == Somekinda Danh sách

4) Bạn sẽ cần phải xây dựng cấu trúc liên kết của riêng bạn ... hoặc sử dụng LinkedHashMap.

PS LinkedHashSet là gian lận, nó được triển khai bằng LinkedHashMap.

+0

Cảm ơn. Lời giải thích tuyệt vời. – stackoverflowuser2010

0

Một cách để da mèo này sẽ được thực hiện một lớp học rất đơn giản mà mở rộng LinkedList, nhưng chạy bất kỳ sửa đổi vào danh sách (ví dụthêm, loại bỏ, vv) bên trong một khối "đồng bộ". Bạn sẽ cần phải chạy con trỏ giả HashMap của bạn thông qua get() mỗi lần, nhưng nó sẽ hoạt động tốt. ví dụ.

... 
private Object lock = new Object(); //semaphore 

//override LinkedList's implementations... 
@Override 
public <T> remove(int index) { synchronized(lock) { return super.remove(index); } } 
... 

Nếu bạn có Eclipse hoặc IntelliJ IDEA, bạn có thể tự động tạo ra các phương thức bạn cần ngay lập tức và bạn có thể đánh giá cái nào cần khóa.

Các vấn đề liên quan