แผนที่ / แคชตามเวลาของ Java พร้อมปุ่มหมดอายุ [ปิด]


253

มีใครบ้างที่รู้เกี่ยวกับ Java Map หรือแหล่งข้อมูลมาตรฐานที่คล้ายกันซึ่งจะทำการลบข้อมูลโดยอัตโนมัติหลังจากหมดเวลาที่กำหนด นี่หมายถึงความแก่ชราที่ซึ่งรายการเก่า ๆ นั้น“ หมดอายุ” โดยอัตโนมัติ

โดยเฉพาะอย่างยิ่งในห้องสมุดโอเพนซอร์สที่สามารถเข้าถึงได้ผ่าน Maven?

ฉันรู้วิธีที่จะใช้งานฟังก์ชั่นของตัวเองและเคยทำมาแล้วหลายครั้งในอดีตดังนั้นฉันจึงไม่ขอคำแนะนำในแง่นั้น แต่เพื่อเป็นแนวทางสำหรับการอ้างอิงที่ดี

โซลูชันที่ใช้WeakReferenceเช่นWeakHashMapไม่ใช่ตัวเลือกเนื่องจากคีย์ของฉันน่าจะเป็นสตริงที่ไม่ได้อยู่ภายในและฉันต้องการหมดเวลาที่กำหนดได้ซึ่งไม่ได้ขึ้นอยู่กับตัวรวบรวมขยะ

Ehcacheเป็นตัวเลือกที่ฉันไม่ต้องการพึ่งพาเนื่องจากต้องการไฟล์การกำหนดค่าภายนอก ฉันกำลังมองหาโซลูชันที่ใช้รหัสเท่านั้น


1
ลองใช้ Google Collections (ปัจจุบันเรียกว่า Guava) มันมีแผนที่ที่สามารถหมดเวลาเข้ารายการโดยอัตโนมัติ
dty

3
ช่างน่าประหลาดใจมากที่คำถามที่มีจำนวนผู้อัปโหลด 253 ครั้งและการดู 176 พันครั้ง - ซึ่งติดอันดับสูงสุดในเครื่องมือค้นหาสำหรับหัวข้อนี้ - ถูกปิดเนื่องจากไม่เป็นไปตามหลักเกณฑ์
Brian

คำตอบ:


320

ใช่. Google Collections หรือGuavaชื่อนี้มีสิ่งที่เรียกว่าMapMakerซึ่งสามารถทำสิ่งนั้นได้อย่างแน่นอน

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

ปรับปรุง:

ในฐานะของฝรั่ง 10.0 (เผยแพร่เมื่อ 28 กันยายน 2011) วิธีการ MapMaker เหล่านี้หลายวิธีได้ถูกคัดค้านเพื่อสนับสนุนCacheBuilderใหม่:

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
เยี่ยมมากฉันรู้ว่าฝรั่งมีคำตอบ แต่ฉันหามันไม่เจอ! (+1)
ฌอนแพทริคฟลอยด์

12
ตั้งแต่ v10 คุณควรใช้ CacheBuilder แทน ( guava-l ไลบรารี.googlecode.com/svn/trunk/javadoc/com/google/ ...... ) เนื่องจากการหมดอายุ ฯลฯ ถูกเลิกใช้ใน MapMaker
wwadge

49
คำเตือน ! ใช้weakKeys()บ่งบอกว่าคีย์จะเปรียบเทียบโดยใช้ == equals()ความหมายไม่ได้ ฉันหายไป 30 นาทีหาสาเหตุแคช String-คีย์ของฉันไม่ได้ทำงาน :)
Laurent Grégoire

3
คนที่ @Laurent พูดถึงweakKeys()เป็นเรื่องสำคัญ weakKeys()ไม่จำเป็นต้องใช้ 90% ของเวลา
มนู Manjunath

3
@ShervinAsgari เพื่อประโยชน์ของผู้เริ่มต้น (รวมตัวเองด้วย) คุณสามารถเปลี่ยนตัวอย่างฝรั่งที่อัปเดตของคุณเป็นหนึ่งที่ใช้ Cache แทน LoadingCache ได้หรือไม่? มันจะตรงกับคำถามที่ดีกว่า (เนื่องจาก LoadingCache มีคุณสมบัติที่เกินแผนที่ที่มีรายการที่หมดอายุและมีความซับซ้อนกว่าในการสร้าง) ดูgithub.com/google/guava/wiki/CachesExplained#from-a-callable
Jeutnarg

29

นี่เป็นตัวอย่างการนำไปใช้ที่ฉันทำเพื่อความต้องการเดียวกันและการทำงานพร้อมกันทำงานได้ดี อาจมีประโยชน์สำหรับใครบางคน

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


Git Repo Link (ด้วยการใช้ฟัง)

https://github.com/vivekjustthink/WeakConcurrentHashMap

ไชโย !!


ทำไมคุณcleanMap()ถึงใช้เวลาครึ่งชั่วโมงในการอธิบาย?
EliuX

Bcoz ทำให้แน่ใจว่าคีย์หมดอายุ (ลบออก) และหลีกเลี่ยงเธรดจากการวนซ้ำมาก
Vivek

@Vivek แต่ด้วยการใช้งานนี้อาจมีจำนวนรายการสูงสุด (expiryInMillis / 2) ซึ่งหมดอายุแล้ว แต่ยังคงอยู่ในแคช ในฐานะที่เป็นกระทู้ลบรายการหลังจาก expiryInMillis / 2 ระยะเวลา
rishi007bansod

19

คุณสามารถลองใช้งานแผนที่แฮชที่หมดอายุด้วยตนเองได้ การใช้งานนี้ไม่ได้ใช้เธรดเพื่อลบรายการที่หมดอายุ แต่ใช้DelayQueueที่ล้างข้อมูลในทุกการดำเนินการโดยอัตโนมัติ


ฉันชอบเวอร์ชั่นของ Guava ที่ดีกว่า แต่ +1 สำหรับการเพิ่มความสมบูรณ์ให้กับรูปภาพ
Sean Patrick Floyd

@ piero86 ฉันว่าการโทรไปที่ล่าช้า Queue.poll () ในวิธี expireKey (ExpiringKey <K> delayKey) ผิด คุณอาจสูญเสีย ExpiringKey ตามอำเภอใจซึ่งไม่สามารถใช้ในการล้างข้อมูลในภายหลัง () - รั่วไหล
Stefan Zobel

1
ปัญหาอื่น: คุณไม่สามารถใส่รหัสเดิมสองครั้งด้วยอายุการใช้งานที่แตกต่างกัน หลังจาก a) ใส่ (1, 1, shortLived) แล้ว b) ใส่ (1, 2, longLived) รายการแผนที่สำหรับคีย์ 1 จะหายไปหลังจาก shortLived ms ไม่ว่าจะมีชีวิตอีกนานแค่ไหน
Stefan Zobel

ขอบคุณสำหรับความเข้าใจของคุณ คุณช่วยรายงานปัญหาเหล่านี้เป็นความคิดเห็นในส่วนสำคัญได้ไหม
pcan

แก้ไขตามคำแนะนำของคุณ ขอบคุณ
pcan

19

Apache Commons มี decorator สำหรับ Map ที่จะหมดอายุรายการ: PassiveExpiringMap มันง่ายกว่าแคชจาก Guava

PS ระวังไม่ซิงโครไนซ์


1
มันง่าย แต่จะตรวจสอบเวลาหมดอายุเฉพาะหลังจากที่คุณเข้าถึงรายการ
Badie

ตามJavadoc : เมื่อเรียกใช้เมธอดที่เกี่ยวข้องกับการเข้าถึงเนื้อหาแผนที่ทั้งหมด (เช่น containKey (Object), entrySet (), ฯลฯ ) มัณฑนากรนี้จะลบรายการที่หมดอายุทั้งหมดก่อนที่จะทำการร้องขอให้เสร็จจริง ๆ
NS du Toit

หากคุณต้องการที่จะดูว่ารุ่นล่าสุดของห้องสมุดนี้ (Apache Commons Commons-collection4) อยู่ที่นี่คือลิงค์ไปยังห้องสมุดที่เกี่ยวข้องใน mvnrepository
NS du Toit

3

ดูเหมือนว่า ehcache นั้นเกินความต้องการของคุณอย่างไรก็ตามโปรดทราบว่าไม่จำเป็นต้องใช้ไฟล์การกำหนดค่าภายนอก

เป็นความคิดที่ดีที่จะย้ายการกำหนดค่าไปยังไฟล์การกำหนดค่าที่ประกาศไว้ (ดังนั้นคุณไม่จำเป็นต้องคอมไพล์ใหม่เมื่อการติดตั้งใหม่ต้องใช้เวลาหมดอายุที่แตกต่างกัน) แต่ก็ไม่จำเป็นเลย http://www.ehcache.org/documentation/user-guide/configuration


2

คอลเลกชันของ Google (ฝรั่ง) มีMapMakerซึ่งคุณสามารถกำหนดเวลา (สำหรับการหมดอายุ) และคุณสามารถใช้การอ้างอิงอ่อนหรืออ่อนตามที่คุณเลือกโดยใช้วิธีการจากโรงงานเพื่อสร้างอินสแตนซ์ที่คุณเลือก



2

หากใครต้องการสิ่งที่เรียบง่ายต่อไปนี้เป็นชุดคีย์หมดอายุง่าย มันอาจถูกแปลงเป็นแผนที่ได้อย่างง่ายดาย

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
นี่เป็นเรื่องปกติสำหรับสถานการณ์แบบเธรดเดียว แต่มันจะแตกอย่างน่าสังเวชในสถานการณ์ที่เกิดขึ้นพร้อมกัน
ฌอนแพทริคฟลอยด์

@SeanPatrickFloyd คุณหมายถึงต้องการตัวของ LinkedHashMap หรือเปล่า?! "จะต้องซิงโครไนซ์ภายนอก" เช่นเดียวกับ LinkedHashMap, HashMap ... คุณตั้งชื่อมัน
palindrom

ใช่เหมือนทุกคน แต่ไม่เหมือนฝรั่งแคชของ (คำตอบที่ได้รับการยอมรับ)
ฌอนแพทริคฟลอยด์

และพิจารณาใช้System.nanoTime()สำหรับการคำนวณความแตกต่างของเวลาเนื่องจาก System.currentTimeMillis () ไม่สอดคล้องกันเนื่องจากขึ้นอยู่กับเวลาของระบบและอาจไม่ต่อเนื่อง
Ercksen

2

โดยปกติแล้วแคชควรเก็บวัตถุในบางช่วงเวลาและจะเปิดเผยในภายหลัง อะไรคือสิ่งที่เป็นเวลาที่ดีที่จะถือวัตถุขึ้นอยู่กับกรณีการใช้งาน ฉันต้องการให้สิ่งนี้เป็นเรื่องง่ายไม่มีเธรดหรือตัวกำหนดเวลา วิธีนี้ใช้ได้ผลสำหรับฉัน SoftReferenceวัตถุต่างๆ จะได้รับการรับประกันว่าจะมีเวลาน้อยที่สุด แต่ไม่ได้อยู่รอบ ๆ ในความทรงจำจนผลัดดวงอาทิตย์เป็นดาวยักษ์แดง

ตามตัวอย่างการใช้ลองนึกถึงระบบที่ตอบสนองช้าซึ่งจะสามารถตรวจสอบได้ว่ามีการทำคำขอค่อนข้างเร็ว ๆ นี้และในกรณีนั้นจะไม่ดำเนินการตามที่ขอสองครั้ง แต่หากมีการร้องขอให้ดำเนินการเดียวกันในภายหลังจะต้องดำเนินการอีกครั้ง

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

ดีขอบคุณ
bigbadmouse

1
HashMap ไม่ปลอดภัยสำหรับเธรดเนื่องจากสภาพการแข่งขันการดำเนินการ map.put หรือการปรับขนาดของแผนที่อาจทำให้ข้อมูลเสียหาย ดูที่นี่: mailinator.blogspot.com/2009/06/beautiful-race-condition.html
Eugene Maysyuk

นั่นเป็นเรื่องจริง แน่นอนคลาส Java ส่วนใหญ่จะไม่ปลอดภัยเธรด ถ้าคุณต้องการความปลอดภัยของเธรดคุณต้องตรวจสอบคลาสที่ได้รับผลกระทบของการออกแบบเพื่อดูว่าตรงกับความต้องการหรือไม่
Matthias Ronge

1

แคช Guava นั้นง่ายต่อการนำไปใช้งานเราสามารถหมดอายุคีย์เวลาโดยใช้ฝรั่งแคช ฉันได้อ่านโพสต์อย่างเต็มที่และด้านล่างให้ความสำคัญของการศึกษาของฉัน

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

              }

การอ้างอิง: ตัวอย่างแคชฝรั่ง


1
โปรดอัปเดตลิงก์เนื่องจากมันไม่ทำงานในขณะนี้
smaiakov
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.