http://www.cnblogs.com/peida/p/Guava_Cache.html
Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。整体上来说Guava cache 是本地缓存的不二之选,简单易用,性能好。
Guava Cache有两种创建方式:
1. cacheLoader
2. callable callback
通过这两种方法创建的cache,和通常用map来缓存的做法比,不同在于,
这两种方法都实现了一种逻辑——从缓存中取key X的值,如果该值已经缓存过了,
则返回缓存中的值,如果没有缓存过,可以通过某个方法来获取这个值。
但不同的在于cacheloader的定义比较宽泛,是针对整个cache定义的,
可以认为是统一的根据key值load value的方法。
而callable的方式较为灵活,允许你在get的时候指定。
cacheLoader方式实现实例:
@Test
public void TestLoadingCache() throws Exception{
LoadingCache<String,String> cahceBuilder=CacheBuilder
.newBuilder()
.build(new CacheLoader<String, String>(){
@Override
public String load(String key) throws Exception {
String strProValue="hello "+key+"!";
return strProValue;
}
});
System.out.println("jerry value:"+cahceBuilder.apply("jerry"));
System.out.println("jerry value:"+cahceBuilder.get("jerry"));
System.out.println("peida value:"+cahceBuilder.get("peida"));
System.out.println("peida value:"+cahceBuilder.apply("peida"));
System.out.println("lisa value:"+cahceBuilder.apply("lisa"));
cahceBuilder.put("harry", "ssdded");
System.out.println("harry value:"+cahceBuilder.get("harry"));
}
输出:
jerry value:hello jerry!
jerry value:hello jerry!
peida value:hello peida!
peida value:hello peida!
lisa value:hello lisa!
harry value:ssdded
callable callback的实现:
@Test
public void testcallableCache()throws Exception{
Cache<String, String> cache = CacheBuilder.newBuilder().maximumSize(1000).build();
String resultVal = cache.get("jerry", new Callable<String>() {
public String call() {
String strProValue="hello "+"jerry"+"!";
return strProValue;
}
});
System.out.println("jerry value : " + resultVal);
resultVal = cache.get("peida", new Callable<String>() {
public String call() {
String strProValue="hello "+"peida"+"!";
return strProValue;
}
});
System.out.println("peida value : " + resultVal);
}
输出:
jerry value : hello jerry!
peida value : hello peida!
cache的参数说明:
回收的参数:
1. 大小的设置:CacheBuilder.maximumSize(long) CacheBuilder.weigher(Weigher) CacheBuilder.maxumumWeigher(long)
2. 时间:expireAfterAccess(long, TimeUnit) expireAfterWrite(long, TimeUnit)
3. 引用:CacheBuilder.weakKeys() CacheBuilder.weakValues() CacheBuilder.softValues()
4. 明确的删除:invalidate(key) invalidateAll(keys) invalidateAll()
5. 删除监听器:CacheBuilder.removalListener(RemovalListener)
refresh机制:
1. LoadingCache.refresh(K) 在生成新的value的时候,旧的value依然会被使用。
2. CacheLoader.reload(K, V) 生成新的value过程中允许使用旧的value
3. CacheBuilder.refreshAfterWrite(long, TimeUnit) 自动刷新cache
基于泛型的实现:
/**
* 不需要延迟处理(泛型的方式封装)
* @return
*/
public <K , V> LoadingCache<K , V> cached(CacheLoader<K , V> cacheLoader) {
LoadingCache<K , V> cache = CacheBuilder
.newBuilder()
.maximumSize(2)
.weakKeys()
.softValues()
.refreshAfterWrite(120, TimeUnit.SECONDS)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener(new RemovalListener<K, V>(){
@Override
public void onRemoval(RemovalNotification<K, V> rn) {
System.out.println(rn.getKey()+"被移除");
}})
.build(cacheLoader);
return cache;
}
/**
* 通过key获取value
* 调用方式 commonCache.get(key) ; return String
* @param key
* @return
* @throws Exception
*/
public LoadingCache<String , String> commonCache(final String key) throws Exception{
LoadingCache<String , String> commonCache= cached(new CacheLoader<String , String>(){
@Override
public String load(String key) throws Exception {
return "hello "+key+"!";
}
});
return commonCache;
}
@Test
public void testCache() throws Exception{
LoadingCache<String , String> commonCache=commonCache("peida");
System.out.println("peida:"+commonCache.get("peida"));
commonCache.apply("harry");
System.out.println("harry:"+commonCache.get("harry"));
commonCache.apply("lisa");
System.out.println("lisa:"+commonCache.get("lisa"));
}
输出:
peida:hello peida!
harry:hello harry!
peida被移除
lisa:hello lisa!
基于泛型的Callable Cache实现:
private static Cache<String, String> cacheFormCallable = null;
/**
* 对需要延迟处理的可以采用这个机制;(泛型的方式封装)
* @param <K>
* @param <V>
* @param key
* @param callable
* @return V
* @throws Exception
*/
public static <K,V> Cache<K , V> callableCached() throws Exception {
Cache<K, V> cache = CacheBuilder
.newBuilder()
.maximumSize(10000)
.expireAfterWrite(10, TimeUnit.MINUTES)
.build();
return cache;
}
private String getCallableCache(final String userName) {
try {
//Callable只有在缓存值不存在时,才会调用
return cacheFormCallable.get(userName, new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println(userName+" from db");
return "hello "+userName+"!";
}
});
} catch (ExecutionException e) {
e.printStackTrace();
return null;
}
}
@Test
public void testCallableCache() throws Exception{
final String u1name = "peida";
final String u2name = "jerry";
final String u3name = "lisa";
cacheFormCallable=callableCached();
System.out.println("peida:"+getCallableCache(u1name));
System.out.println("jerry:"+getCallableCache(u2name));
System.out.println("lisa:"+getCallableCache(u3name));
System.out.println("peida:"+getCallableCache(u1name));
}
输出:
peida from db
peida:hello peida!
jerry from db
jerry:hello jerry!
lisa from db
lisa:hello lisa!
peida:hello peida!
说明:Callable只有在缓存值不存在时,才会调用,比如第二次调用getCallableCache(u1name)直接返回缓存中的值
guava Cache数据移除:
guava做cache时候数据的移除方式,在guava中数据的移除分为被动移除和主动移除两种。
被动移除数据的方式,guava默认提供了三种方式:
1.基于大小的移除:看字面意思就知道就是按照缓存的大小来移除,
如果即将到达指定的大小,那就会把不常用的键值对从cache中移除。
定义的方式一般为 CacheBuilder.maximumSize(long),还有一种一种可以算权重的方法,
个人认为实际使用中不太用到。就这个常用的来看有几个注意点,
其一,这个size指的是cache中的条目数,不是内存大小或是其他;
其二,并不是完全到了指定的size系统才开始移除不常用的数据的,而是接近这个size的时候系统就会开始做移除的动作;
其三,如果一个键值对已经从缓存中被移除了,你再次请求访问的时候,如果cachebuild是使用cacheloader方式的,
那依然还是会从cacheloader中再取一次值,如果这样还没有,就会抛出异常
2.基于时间的移除:guava提供了两个基于时间移除的方法
expireAfterAccess(long, TimeUnit) 这个方法是根据某个键值对最后一次访问之后多少时间后移除
expireAfterWrite(long, TimeUnit) 这个方法是根据某个键值对被创建或值被替换后多少时间移除
3.基于引用的移除:
这种移除方式主要是基于java的垃圾回收机制,根据键或者值的引用关系决定移除
主动移除数据方式,主动移除有三种方法:
1.单独移除用 Cache.invalidate(key)
2.批量移除用 Cache.invalidateAll(keys)
3.移除所有用 Cache.invalidateAll()
如果需要在移除数据的时候有所动作还可以定义Removal Listener,
但是有点需要注意的是默认Removal Listener中的行为是和移除动作同步执行的,
如果需要改成异步形式,可以考虑使用RemovalListeners.asynchronous(RemovalListener, Executor)
*******************************************************************************************
使用google guava 实现定时缓存功能
http://outofmemory.cn/java/guava/cache/how-to-use-guava-cache
例子1
创建Book.java
[java] view plaincopyprint?
public class Book {
private int id;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public Book(int id) {
super();
this.id = id;
}
public Book() {
super();
}
@Override
public String toString() {
return ""+id;
}
}
创建BookDao.java
[java] view plaincopyprint?
public class BookDao {
public Object executeSQL() {
System.out.println("此处调用了Dao方法。executeSQL");
List<Book> books = new ArrayList<Book>();
for (int i = 0; i < 3; i++) {
Book b = new Book(i);
books.add(b);
}
return books;
}
}
创建BookCache.java
[java] view plaincopyprint?
public class BookCache {
public static Cache<String, List<Book>> cache = CacheBuilder.newBuilder()
.expireAfterAccess(8, TimeUnit.SECONDS).maximumSize(10)
.build();
}
创建运行类。
[java] view plaincopyprint?
public static void main(String[] args) {
try {
System.out.println("第一次调用dao方法,正确状态:应该调用Dao里的方法");
List<Book> books = BookCache.cache.get("points",
new Callable<List<Book>>() {
@Override
public List<Book> call() {
BookDao dao = new BookDao();
List<Book> list = (List<Book>) dao.executeSQL();
if (null == list || list.size() <= 0) {
list = new ArrayList<Book>();
}
return list;
}
});
for (Book book : books) {
System.out.println(book);
}
System.out.println("第二次调用dao方法,正确状态:不调用Dao里的方法");
List<Book> books2 = BookCache.cache.get("points",
new Callable<List<Book>>() {
@Override
public List<Book> call() {
BookDao dao = new BookDao();
List<Book> list = (List<Book>) dao.executeSQL();
if (null == list || list.size() <= 0) {
list = new ArrayList<Book>();
}
return list;
}
});
for (Book book : books2) {
System.out.println(book);
}
} catch (Exception e1) {
e1.printStackTrace();
}
try {
Thread.currentThread();
Thread.sleep(TimeUnit.SECONDS.toMillis(10));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
System.out.println("休息十秒后,第三次调用dao方法,正确状态:调用Dao里的方法");
List<Book> books = BookCache.cache.get("points",
new Callable<List<Book>>() {
@Override
public List<Book> call() {
BookDao dao = new BookDao();
List<Book> list = (List<Book>) dao.executeSQL();
if (null == list || list.size() <= 0) {
list = new ArrayList<Book>();
}
return list;
}
});
for (Book book : books) {
System.out.println(book);
}
System.out.println("第四次调用dao方法,正确状态:不调用Dao里的方法");
List<Book> books2 = BookCache.cache.get("points",
new Callable<List<Book>>() {
@Override
public List<Book> call() {
BookDao dao = new BookDao();
List<Book> list = (List<Book>) dao.executeSQL();
if (null == list || list.size() <= 0) {
list = new ArrayList<Book>();
}
return list;
}
});
for (Book book : books2) {
System.out.println(book);
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
运行结果:
[plain] view plaincopyprint?
第一次调用dao方法,正确状态:应该调用Dao里的方法
此处调用了Dao方法。executeSQL
0
1
2
第二次调用dao方法,正确状态:不调用Dao里的方法
0
1
2
休息十秒后,第三次调用dao方法,正确状态:调用Dao里的方法
此处调用了Dao方法。executeSQL
0
1
2
第四次调用dao方法,正确状态:不调用Dao里的方法
0
1
2
例子2
google guava中有cache包,此包提供内存缓存功能。内存缓存需要考虑很多问题,
包括并发问题,缓存失效机制,内存不够用时缓存释放,缓存的命中率,缓存的移除等等。
当然这些东西guava都考虑到了。
guava中使用缓存需要先声明一个CacheBuilder对象,并设置缓存的相关参数,
然后调用其build方法获得一个Cache接口的实例。请看下面的代码和注释,注意在注释中指定了Cache的各个参数。
public static void main(String[] args) throws ExecutionException, InterruptedException{
//缓存接口这里是LoadingCache,LoadingCache在缓存项不存在时可以自动加载缓存
LoadingCache<Integer,Student> studentCache
//CacheBuilder的构造函数是私有的,只能通过其静态方法newBuilder()来获得CacheBuilder的实例
= CacheBuilder.newBuilder()
//设置并发级别为8,并发级别是指可以同时写缓存的线程数
.concurrencyLevel(8)
//设置写缓存后8秒钟过期
.expireAfterWrite(8, TimeUnit.SECONDS)
//设置缓存容器的初始容量为10
.initialCapacity(10)
//设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
.maximumSize(100)
//设置要统计缓存的命中率
.recordStats()
//设置缓存的移除通知
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
}
})
//build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
.build(
new CacheLoader<Integer, Student>() {
@Override
public Student load(Integer key) throws Exception {
System.out.println("load student " + key);
Student student = new Student();
student.setId(key);
student.setName("name " + key);
return student;
}
}
);
for (int i=0;i<20;i++) {
//从缓存中得到数据,由于我们没有设置过缓存,所以需要通过CacheLoader加载缓存数据
Student student = studentCache.get(1);
System.out.println(student);
//休眠1秒
TimeUnit.SECONDS.sleep(1);
}
System.out.println("cache stats:");
//最后打印缓存的命中率等 情况
System.out.println(studentCache.stats().toString());
}
以上程序的输出如下:
load student 1
Student{id=1, name=name 1}
Student{id=1, name=name 1}
Student{id=1, name=name 1}
Student{id=1, name=name 1}
Student{id=1, name=name 1}
Student{id=1, name=name 1}
Student{id=1, name=name 1}
Student{id=1, name=name 1}
1 was removed, cause is EXPIRED
load student 1
......
Student{id=1, name=name 1}
Student{id=1, name=name 1}
Student{id=1, name=name 1}
Student{id=1, name=name 1}
cache stats:
CacheStats{hitCount=17, missCount=3, loadSuccessCount=3, loadExceptionCount=0, totalLoadTime=1348802, evictionCount=2}
看看到在20此循环中命中次数是17次,未命中3次,
这是因为我们设定缓存的过期时间是写入后的8秒,所以20秒内会失效两次,
另外第一次获取时缓存中也是没有值的,所以才会未命中3次,其他则命中。
guava的内存缓存非常强大,可以设置各种选项,而且很轻量,使用方便。另外还提供了下面一些方法,来方便各种需要:
ImmutableMap<K, V> getAllPresent(Iterable<?> keys) 一次获得多个键的缓存值
put和putAll方法向缓存中添加一个或者多个缓存项
invalidate 和 invalidateAll方法从缓存中移除缓存项
asMap()方法获得缓存数据的ConcurrentMap<K, V>快照
cleanUp()清空缓存
refresh(Key) 刷新缓存,即重新取缓存数据,更新缓存
缓存数据的清空
guava没有提供自动触发清空缓存数据的功能,而是提供了一种手工调用的方式,
使用者需要通过Cache.cleanUp()的方式来清空缓存。
所以一般可以有两种选择,一种是通过某个请求来触发清空动作,
这种相当于按需清空,另一种则是通过定时任务,亦成为调度程序来清空,这种相当于与按时清空
缓存数据的刷新
guava没有提供类似refreshall的方法刷新缓存中的所有值,而只是提供了 LoadingCache.refresh(K)方法,用于刷新某个键值对,
这里有趣的是刷新动作是异步的,也就是在值被彻底刷新之前,如果有人取这个key的值,返回的还是没有刷新的值。
如果你希望定义自己的刷新行为,可以重写 CacheLoader.reload(K, V)方法
LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
.maximumSize(1000)
.refreshAfterWrite(1, TimeUnit.MINUTES)
.build(
new CacheLoader<Key, Graph>() {
public Graph load(Key key) { // no checked exception
return getGraphFromDatabase(key);
}
public ListenableFuture<Graph> reload(final Key key, Graph prevGraph) {
if (neverNeedsRefresh(key)) {
return Futures.immediateFuture(prevGraph);
} else {
// asynchronous!
return ListenableFutureTask.create(new Callable<Graph>() {
public Graph call() {
return getGraphFromDatabase(key);
}
});
}
}
});
例子3
http://www.csdn123.com/html/topnews201408/78/5978.htm
1、CacheLoader方式
代码如下:
1 import java.util.ArrayList;
2 import java.util.List;
3 import java.util.concurrent.ExecutionException;
4 import java.util.concurrent.TimeUnit;
5
6 import org.junit.Test;
7
8 import com.google.cacahe.Person;
9 import com.google.common.cache.CacheBuilder;
10 import com.google.common.cache.CacheLoader;
11 import com.google.common.cache.LoadingCache;
12
13 public class TestGuavaCache {
14
15 @Test
16 public void testUserCacheLoader() throws ExecutionException {
17 // 模拟数据
18 final List<Person> list = new ArrayList<Person>(5);
19 list.add(new Person("1", "zhangsan"));
20 list.add(new Person("2", "lisi"));
21 list.add(new Person("3", "wangwu"));
22
23 // 创建cache
24 LoadingCache<String, Person> cache = CacheBuilder.newBuilder()//
25 .refreshAfterWrite(1, TimeUnit.MINUTES)// 给定时间内没有被读/写访问,则回收。
26 // .expireAfterWrite(5, TimeUnit.SECONDS)//给定时间内没有写访问,则回收。
27 // .expireAfterAccess(3, TimeUnit.SECONDS)// 缓存过期时间为3秒
28 .maximumSize(100).// 设置缓存个数
29 build(new CacheLoader<String, Person>() {
30 @Override
31 /** 当本地缓存命没有中时,调用load方法获取结果并将结果缓存
32 */
33 public Person load(String key) throws ExecutionException {
34 System.out.println(key + " load in cache");
35 return getPerson(key);
36 }
37
38 // 此时一般我们会进行相关处理,如到数据库去查询
39 private Person getPerson(String key) throws ExecutionException {
40 System.out.println(key + " query");
41 for (Person p : list) {
42 if (p.getId().equals(key))
43 return p;
44 }
45 return null;
46 }
47 });
48
49 cache.get("1");
50 cache.get("2");
51 cache.get("3");
52 System.out.println("======= sencond time ==========");
53 cache.get("1");
54 cache.get("2");
55 cache.get("3");
56 }
57 }
执行结果如下:
1 load in cache
1 query
2 load in cache
2 query
3 load in cache
3 query
======= sencond time ==========
第二次获取的时候没有执行获取的方法,而是直接从缓存中获取。
2、Callback方式
代码如下:
1 import java.util.ArrayList;
2 import java.util.List;
3 import java.util.concurrent.Callable;
4 import java.util.concurrent.ExecutionException;
5
6 import org.junit.Test;
7
8 import com.google.cacahe.Person;
9 import com.google.common.cache.Cache;
10 import com.google.common.cache.CacheBuilder;
11
12 public class TestGuavaCache {
13
14
15 @Test
16 public void testUserCallback() throws ExecutionException {
17 // 模拟数据
18 final List<Person> list = new ArrayList<Person>(5);
19 list.add(new Person("1", "zhangsan"));
20 list.add(new Person("2", "lisi"));
21 list.add(new Person("3", "wangwu"));
22
23 final String key = "1";
24 Cache<String, Person> cache2 = CacheBuilder.newBuilder().maximumSize(1000).build();
25 /**
26 * 用缓存中的get方法,当缓存命中时直接返回结果;否则,通过给定的Callable类call方法获取结果并将结果缓存。<br/>
27 * 可以用一个cache对象缓存多种不同的数据,只需创建不同的Callable对象即可。
28 */
29 Person person = cache2.get(key, new Callable<Person>() {
30 public Person call() throws ExecutionException {
31 System.out.println(key + " load in cache");
32 return getPerson(key);
33 }
34
35 // 此时一般我们会进行相关处理,如到数据库去查询
36 private Person getPerson(String key) throws ExecutionException {
37 System.out.println(key + " query");
38 for (Person p : list) {
39 if (p.getId().equals(key))
40 return p;
41 }
42 return null;
43 }
44 });
45 System.out.println("======= sencond time ==========");
46 person = cache2.getIfPresent(key);
47 person = cache2.getIfPresent(key);
48 }
49 }
执行结果如下:
1 load in cache
1 query
======= sencond time ==========
第二次获取后也是直接从缓存中加载。
3、关于移除监听器
通过CacheBuilder.removalListener(RemovalListener),我们可以声明一个监听器,从而可以在缓存被移除时做一些其他的操作。
当缓存被移除时,RemovalListener会获取移除bing通知[RemovalNotification],其中包含移除的key、value和RemovalCause。
示例代码如下:
1 import java.util.concurrent.ExecutionException;
2 import java.util.concurrent.TimeUnit;
3
4 import org.junit.Test;
5
6 import com.google.cacahe.Person;
7 import com.google.common.cache.CacheBuilder;
8 import com.google.common.cache.CacheLoader;
9 import com.google.common.cache.LoadingCache;
10 import com.google.common.cache.RemovalListener;
11 import com.google.common.cache.RemovalNotification;
12
13 public class TestGuavaCache {
14 @Test
15 public void testListener() throws ExecutionException {
16 CacheLoader<String, Person> loader = new CacheLoader<String, Person>() {
17 @Override
18 // 当本地缓存命没有中时,调用load方法获取结果并将结果缓存
19 public Person load(String key) throws ExecutionException {
20 System.out.println(key + " load in cache");
21 return getPerson(key);
22 }
23 // 此时一般我们会进行相关处理,如到数据库去查询
24 private Person getPerson(String key) throws ExecutionException {
25 System.out.println(key + " query");
26 return new Person(key, "zhang" + key);
27 }
28 };
29
30 // remove listener
31 RemovalListener<String, Person> removalListener = new RemovalListener<String, Person>() {
32 public void onRemoval(RemovalNotification<String, Person> removal) {
33 System.out.println("cause:" + removal.getCause() + " key:" + removal.getKey() + " value:"
34 + removal.getValue());
35 }
36 };
37
38 LoadingCache<String, Person> cache = CacheBuilder.newBuilder()//
39 .expireAfterWrite(2, TimeUnit.MINUTES).maximumSize(1024).removalListener(removalListener).build(loader);
40 cache.get("1");// 放入缓存
41 cache.get("1");// 第二次获取(此时从缓存中获取)
42 cache.invalidate("1");// 移除缓存
43 cache.get("1");// 重新获取
44 cache.get("1");// 再次获取(此时从缓存中获取)
45 }
46 }
运行结果如下:
1 1 load in cache
2 1 query
3 cause:EXPLICIT key:1 value:Person [id=1, name=zhang1]
4 1 load in cache
5 1 query
三、其他相关方法
显式插入:该方法可以直接向缓存中插入值,如果缓存中有相同key则之前的会被覆盖。
cache.put(key, value);
显式清除:我们也可以对缓存进行手动清除。
cache.invalidate(key); //单个清除
cache.invalidateAll(keys); //批量清除
cache.invalidateAll(); //清除所有缓存项
基于时间的移除:
expireAfterAccess(long, TimeUnit); 该键值对最后一次访问后超过指定时间再移除
expireAfterWrite(long, TimeUnit) ;该键值对被创建或值被替换后超过指定时间再移除
基于大小的移除:指如果缓存的对象格式即将到达指定的大小,就会将不常用的键值对从cache中移除。
cacheBuilder.maximumSize(long)
size是指cache中缓存的对象个数。当缓存的个数开始接近size的时候系统就会进行移除的操作
缓存清除执行的时间
使用CacheBuilder构建的缓存不会"自动"执行清理和回收工作,也不会在某个缓存项过期后马上清理,也没有诸如此类的清理机制。
它是在写操作时顺带做少量的维护工作(清理);如果写操作太少,读操作的时候也会进行少量维护工作。因为如果要自动地持续清理缓存,
就必须有一个线程,这个线程会和用户操作竞争共享锁。在某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。
高级
Guava Cache实现详解
http://my.oschina.net/u/203952/blog/175976