Problem
讀取某個檔案建立model,可能因為成本昂貴而建立Cache;此外,程式可能會因為檔案有修改而重新建立Cache。本篇教學,將告訴你如何透過Guava達到這個需求。
How to?
Guava本身有提供ExpiringMemoizingSupplier讓你以時間為條件,去重讀cache;ExpiredFileMemorizeSupplier使用Proxy的概念,去控管何時該真的去讀實際資料。我參考這個做法,建立了ExpiredFileMemorizeSupplier物件,會根據檔案是否修改,去決定是否讀真實資料:
package org.tonylin.practice.guava.cache; import java.io.File; import com.google.common.base.Supplier; public class ExpiredFileMemorizeSupplier<T> implements Supplier<T> { private File mFile; private Supplier<T> mDelegate; private long mLastModified = 0; private transient volatile T value; public ExpiredFileMemorizeSupplier(IFileDataSupplier<T> delegate){ mFile = delegate.getFile(); mDelegate = delegate; } public ExpiredFileMemorizeSupplier(Supplier<T> delegate, File file){ mFile = file; mDelegate = delegate; } @Override public T get() { long lastModified = mFile.lastModified(); synchronized (this) { if( lastModified != mLastModified ) { mLastModified = lastModified; value = mDelegate.get(); } return value; } } }
通常提供資料的物件,本身應為對應檔案的Information Export;因此我另外定義IFileDataSupplier介面去做這事情:
package org.tonylin.practice.guava.cache; import java.io.File; import com.google.common.base.Supplier; public interface IFileDataSupplier<T> extends Supplier<T> { File getFile(); }
以下為我的測試案例,去驗證資料是從cache來還是實際讀: (這延續之前的測試修改的,並非量身打造,莫見怪)
package org.tonylin.practice.guava.cache; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.nio.charset.Charset; import java.util.concurrent.TimeUnit; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.io.Files; import com.google.common.io.Resources; public class TestSuppliers { private int count = 0; private File testFile = new File("TestSuppliersTestFile.txt"); @Before public void setup() throws Exception { Files.write("test".getBytes(), testFile); } @After public void teardown(){ testFile.delete(); } @Test public void testExpiredFileMemorizeSupplier_IFileDataSupplier() throws Exception { IFileDataSupplier<String> fileDataSupplier = new IFileDataSupplier<String>() { private File testFile = new File("TestSuppliersTestFile.txt"); @Override public String get() { try { count++; return Resources.toString(testFile.toURI().toURL(), Charset.forName("utf-8")); } catch (Exception e) { throw new RuntimeException(e); } } @Override public File getFile() { return testFile; } }; ExpiredFileMemorizeSupplier<String> s = new ExpiredFileMemorizeSupplier<>(fileDataSupplier); Assert.assertEquals( 0, count); Assert.assertEquals("test", s.get()); Assert.assertEquals( 1, count); Assert.assertEquals("test", s.get()); Assert.assertEquals( 1, count); Files.write("test2".getBytes(), testFile); Assert.assertEquals("test2", s.get()); Assert.assertEquals( 2, count); } @Test public void testExpiredFileMemorizeSupplier() throws Exception{ ExpiredFileMemorizeSupplier<String> s = new ExpiredFileMemorizeSupplier<>(this::getMemoize, testFile); Assert.assertEquals( 0, count); Assert.assertEquals("testMemoize", s.get()); Assert.assertEquals( 1, count); Assert.assertEquals("testMemoize", s.get()); Assert.assertEquals( 1, count); Files.write("test".getBytes(), testFile); Assert.assertEquals("testMemoize", s.get()); Assert.assertEquals( 2, count); } public String getMemoize(){ count++; return "testMemoize"; } }
也許應該設計成Event-based的Eviction?
留言
張貼留言