Bản đồ / bộ đệm dựa trên thời gian Java với các khóa hết hạn [đã đóng]


253

Có ai trong số các bạn biết về Bản đồ Java hoặc kho lưu trữ dữ liệu tiêu chuẩn tương tự tự động xóa các mục nhập sau một khoảng thời gian nhất định không? Điều này có nghĩa là lão hóa, trong đó các mục cũ đã hết hạn cũ.

Tốt hơn là trong một thư viện mã nguồn mở có thể truy cập thông qua Maven?

Tôi biết các cách để tự thực hiện chức năng và đã thực hiện nó nhiều lần trong quá khứ, vì vậy tôi không yêu cầu lời khuyên về khía cạnh đó, nhưng để chỉ ra cách thực hiện tham chiếu tốt.

Các giải pháp dựa trên WeakReference như WeakHashMap không phải là một tùy chọn, bởi vì các khóa của tôi có thể là các chuỗi không được thực hiện và tôi muốn thời gian chờ có thể định cấu hình không phụ thuộc vào trình thu gom rác.

Ehcache cũng là một tùy chọn tôi không muốn dựa vào vì nó cần các tệp cấu hình bên ngoài. Tôi đang tìm kiếm một giải pháp chỉ mã.


1
Kiểm tra Bộ sưu tập của Google (bây giờ được gọi là ổi). Nó có một bản đồ có thể hết thời gian chờ các mục.
DTY

3
Thật kỳ lạ khi một câu hỏi với 253 lượt tải và 176 nghìn lượt xem - được xếp hạng siêu cao trong các công cụ tìm kiếm cho chủ đề này - đã bị đóng vì không đáp ứng các nguyên tắc
Brian

Câu trả lời:


320

Đúng. Bộ sưu tập của Google, hay Guava như được đặt tên hiện có một thứ gọi là MapMaker có thể thực hiện chính xác điều đó.

ConcurrentMap<Key, Graph> graphs = new MapMaker()
   .concurrencyLevel(4)
   .softKeys()
   .weakValues()
   .maximumSize(10000)
   .expiration(10, TimeUnit.MINUTES)
   .makeComputingMap(
       new Function<Key, Graph>() {
         public Graph apply(Key key) {
           return createExpensiveGraph(key);
         }
       });

Cập nhật:

Kể từ ổi 10.0 (phát hành ngày 28 tháng 9 năm 2011), nhiều phương thức MapMaker này đã không được chấp nhận để ủng hộ CacheBuilder mới :

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
    .maximumSize(10000)
    .expireAfterWrite(10, TimeUnit.MINUTES)
    .build(
        new CacheLoader<Key, Graph>() {
          public Graph load(Key key) throws AnyException {
            return createExpensiveGraph(key);
          }
        });

5
Thật tuyệt vời, tôi biết Guava đã có câu trả lời nhưng tôi không thể tìm thấy nó! (+1)
Sean Patrick Floyd

12
Kể từ v10, thay vào đó, bạn nên sử dụng CacheBuilder ( guava-lologists.googlecode.com/svn/trunk/javadoc/com/google/iêu ) vì đã hết hạn trong MapMaker
wwadge

49
Cảnh báo ! Sử dụng weakKeys()ngụ ý rằng các khóa được so sánh bằng cách sử dụng == ngữ nghĩa, không equals(). Tôi đã mất 30 phút để tìm ra lý do tại sao bộ đệm có khóa Chuỗi của tôi không hoạt động :)
Laurent Grégoire

3
Mọi người, điều mà @Laurent đã đề cập weakKeys()rất quan trọng. weakKeys()không yêu cầu 90% thời gian.
Manu Manjunath

3
@ShervinAsgari vì lợi ích của người mới bắt đầu (bao gồm cả bản thân tôi), bạn có thể chuyển ví dụ ổi đã cập nhật của mình sang ví dụ sử dụng Cache thay vì LoadingCache không? Nó sẽ phù hợp với câu hỏi tốt hơn (vì LoadingCache có các tính năng vượt quá bản đồ với các mục hết hạn và phức tạp hơn nhiều để tạo) xem github.com/google/guava/wiki/CachesExplained#from-a-callable
Jeutnarg

29

Đây là một triển khai mẫu mà tôi đã làm cho cùng một yêu cầu và hoạt động đồng thời hoạt động tốt. Có thể hữu ích cho một ai đó.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 
 * @author Vivekananthan M
 *
 * @param <K>
 * @param <V>
 */
public class WeakConcurrentHashMap<K, V> extends ConcurrentHashMap<K, V> {

    private static final long serialVersionUID = 1L;

    private Map<K, Long> timeMap = new ConcurrentHashMap<K, Long>();
    private long expiryInMillis = 1000;
    private static final SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss:SSS");

    public WeakConcurrentHashMap() {
        initialize();
    }

    public WeakConcurrentHashMap(long expiryInMillis) {
        this.expiryInMillis = expiryInMillis;
        initialize();
    }

    void initialize() {
        new CleanerThread().start();
    }

    @Override
    public V put(K key, V value) {
        Date date = new Date();
        timeMap.put(key, date.getTime());
        System.out.println("Inserting : " + sdf.format(date) + " : " + key + " : " + value);
        V returnVal = super.put(key, value);
        return returnVal;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        for (K key : m.keySet()) {
            put(key, m.get(key));
        }
    }

    @Override
    public V putIfAbsent(K key, V value) {
        if (!containsKey(key))
            return put(key, value);
        else
            return get(key);
    }

    class CleanerThread extends Thread {
        @Override
        public void run() {
            System.out.println("Initiating Cleaner Thread..");
            while (true) {
                cleanMap();
                try {
                    Thread.sleep(expiryInMillis / 2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        private void cleanMap() {
            long currentTime = new Date().getTime();
            for (K key : timeMap.keySet()) {
                if (currentTime > (timeMap.get(key) + expiryInMillis)) {
                    V value = remove(key);
                    timeMap.remove(key);
                    System.out.println("Removing : " + sdf.format(new Date()) + " : " + key + " : " + value);
                }
            }
        }
    }
}


Liên kết Git Repo (Có triển khai Trình nghe)

https://github.com/vivekjustthink/WeakConcienHashMap

Chúc mừng !!


Tại sao bạn thực hiện cleanMap()một nửa thời gian hết hạn?
EliuX

Bcoz nó đảm bảo các khóa đã hết hạn (loại bỏ) và tránh luồng khỏi vòng lặp cực đoan.
Vivek

@Vivek nhưng với cách triển khai này có thể có tối đa (expiryInMillis / 2) số mục đã hết hạn nhưng vẫn còn trong bộ đệm. Vì chủ đề xóa các mục sau khi hết hạn / 2 kỳ
rishi007bansod

19

Bạn có thể thử thực hiện bản đồ băm tự hết hạn của tôi. Việc triển khai này không sử dụng các luồng để loại bỏ các mục đã hết hạn, thay vào đó, nó sử dụng DelayQueue được dọn sạch ở mọi thao tác tự động.


Tôi thích phiên bản của Guava hơn, nhưng +1 để thêm tính hoàn chỉnh cho bức ảnh
Sean Patrick Floyd

@ piero86 Tôi muốn gọi lệnh tới delayQueue.poll () trong phương thức expireKey (ExpiresKey <K> delayKey) là sai. Bạn có thể mất một ExpiresKey tùy ý mà sau này không thể được sử dụng trong dọn dẹp () - một rò rỉ.
Stefan Zobel

1
Một vấn đề khác: bạn không thể đặt cùng một khóa hai lần với các vòng đời khác nhau. Sau khi a) đặt (1, 1, ShortLiving), sau đó b) đặt (1, 2, longLiving), mục nhập Bản đồ cho khóa 1 sẽ biến mất sau shortLiving ms cho dù thời gian tồn tại là bao lâu.
Stefan Zobel

Cảm ơn bạn đã hiểu biết của bạn. Bạn có thể báo cáo những vấn đề này như ý kiến ​​trong ý chính không?
pcan

Đã sửa theo đề xuất của bạn. Cảm ơn.
pcan

19

Apache Commons có trình trang trí cho Map để hết hạn các mục: Bị độngExpiresMap Nó đơn giản hơn bộ nhớ cache từ Guava.

PS hãy cẩn thận, nó không được đồng bộ hóa.


1
Nó đơn giản, nhưng nó chỉ kiểm tra thời gian hết hạn sau khi bạn truy cập một mục.
Badie

Theo Javadoc : Khi gọi các phương thức liên quan đến việc truy cập toàn bộ nội dung bản đồ (ví dụ chứaKey (Object), entryset (), v.v.), trình trang trí này sẽ xóa tất cả các mục đã hết hạn trước khi thực sự hoàn thành việc gọi.
NS du Toit

Nếu bạn muốn xem phiên bản mới nhất của thư viện này (Apache commons commons-sưu tập4
NS du Toit

3

Âm thanh như ehcache là quá mức cần thiết cho những gì bạn muốn, tuy nhiên lưu ý rằng nó không cần các tệp cấu hình bên ngoài.

Nói chung, nên chuyển cấu hình thành tệp cấu hình khai báo (vì vậy bạn không cần biên dịch lại khi cài đặt mới yêu cầu thời gian hết hạn khác nhau), nhưng hoàn toàn không bắt buộc, bạn vẫn có thể định cấu hình theo chương trình. http://www.ehcache.org/documentation/user-guide/configuration


2

Các bộ sưu tập của Google (ổi) có MapMaker trong đó bạn có thể đặt giới hạn thời gian (hết hạn) và bạn có thể sử dụng tham chiếu mềm hoặc yếu khi bạn chọn sử dụng phương pháp xuất xưởng để tạo các phiên bản bạn chọn.



2

Nếu bất cứ ai cần một điều đơn giản, sau đây là một bộ hết hạn đơn giản. Nó có thể được chuyển đổi thành bản đồ một cách dễ dàng.

public class CacheSet<K> {
    public static final int TIME_OUT = 86400 * 1000;

    LinkedHashMap<K, Hit> linkedHashMap = new LinkedHashMap<K, Hit>() {
        @Override
        protected boolean removeEldestEntry(Map.Entry<K, Hit> eldest) {
            final long time = System.currentTimeMillis();
            if( time - eldest.getValue().time > TIME_OUT) {
                Iterator<Hit> i = values().iterator();

                i.next();
                do {
                    i.remove();
                } while( i.hasNext() && time - i.next().time > TIME_OUT );
            }
            return false;
        }
    };


    public boolean putIfNotExists(K key) {
        Hit value = linkedHashMap.get(key);
        if( value != null ) {
            return false;
        }

        linkedHashMap.put(key, new Hit());
        return true;
    }

    private static class Hit {
        final long time;


        Hit() {
            this.time = System.currentTimeMillis();
        }
    }
}

2
Điều này tốt cho một tình huống đơn luồng, nhưng nó sẽ phá vỡ một cách thảm hại trong một tình huống đồng thời.
Sean Patrick Floyd

@SeanPatrickFloyd, ý bạn là như chính LinkedHashMap?! "nó phải được đồng bộ hóa bên ngoài" giống như LinkedHashMap, HashMap ... bạn đặt tên cho nó.
palindrom

vâng, giống như tất cả những thứ đó, nhưng không giống như bộ nhớ cache của Guava (câu trả lời được chấp nhận)
Sean Patrick Floyd

Ngoài ra, hãy xem xét sử dụng System.nanoTime()để tính toán sự khác biệt về thời gian vì System.cienTimeMillis () không nhất quán vì nó phụ thuộc vào thời gian hệ thống và có thể không liên tục.
Ercksen

2

Thông thường, bộ đệm sẽ giữ các đối tượng trong một khoảng thời gian và sẽ phơi bày chúng một thời gian sau đó. Thế nào là thời điểm tốt để giữ một đối tượng phụ thuộc vào trường hợp sử dụng. Tôi muốn điều này là đơn giản, không có chủ đề hoặc lịch trình. Cách tiếp cận này làm việc cho tôi. Không giống như SoftReferences, các đối tượng được đảm bảo có sẵn một lượng thời gian tối thiểu. Tuy nhiên, không ở lại trong ký ức cho đến khi mặt trời biến thành một người khổng lồ đỏ .

Như ví dụ về cách sử dụng, hãy nghĩ đến một hệ thống phản hồi chậm có thể kiểm tra xem một yêu cầu đã được thực hiện gần đây chưa, và trong trường hợp đó không thực hiện hành động được yêu cầu hai lần, ngay cả khi người dùng bận rộn nhấn nút nhiều lần. Nhưng, nếu hành động tương tự được yêu cầu một thời gian sau đó, nó sẽ được thực hiện lại.

class Cache<T> {
    long avg, count, created, max, min;
    Map<T, Long> map = new HashMap<T, Long>();

    /**
     * @param min   minimal time [ns] to hold an object
     * @param max   maximal time [ns] to hold an object
     */
    Cache(long min, long max) {
        created = System.nanoTime();
        this.min = min;
        this.max = max;
        avg = (min + max) / 2;
    }

    boolean add(T e) {
        boolean result = map.put(e, Long.valueOf(System.nanoTime())) != null;
        onAccess();
        return result;
    }

    boolean contains(Object o) {
        boolean result = map.containsKey(o);
        onAccess();
        return result;
    }

    private void onAccess() {
        count++;
        long now = System.nanoTime();
        for (Iterator<Entry<T, Long>> it = map.entrySet().iterator(); it.hasNext();) {
            long t = it.next().getValue();
            if (now > t + min && (now > t + max || now + (now - created) / count > t + avg)) {
                it.remove();
            }
        }
    }
}

tốt, cảm ơn bạn
bigbadmouse

1
HashMap không an toàn cho chuỗi, do điều kiện chủng tộc, hoạt động map.put hoặc thay đổi kích thước bản đồ có thể dẫn đến hỏng dữ liệu. Xem tại đây: mailinator.blogspot.com/2009/06/beautitable-race-condition.html
Eugene Maysyuk

Đúng. Thật vậy, hầu hết các lớp Java không phải là luồng an toàn. Nếu bạn cần bảo mật luồng, bạn cần kiểm tra mọi lớp bị ảnh hưởng của thiết kế để xem nó có đáp ứng yêu cầu không.
Matthias Rrid

1

Bộ đệm ổi rất dễ thực hiện. Chúng tôi có thể hết hạn khóa trên cơ sở thời gian bằng cách sử dụng bộ đệm ổi. Tôi đã đọc bài viết đầy đủ và dưới đây cung cấp chìa khóa của nghiên cứu của tôi.

cache = CacheBuilder.newBuilder().refreshAfterWrite(2,TimeUnit.SECONDS).
              build(new CacheLoader<String, String>(){
                @Override
                public String load(String arg0) throws Exception {
                    // TODO Auto-generated method stub
                    return addcache(arg0);
                }

              }

Tham khảo: ví dụ bộ đệm ổi


1
vui lòng cập nhật liên kết vì nó không hoạt động ngay bây giờ
smaiakov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.