所谓缓存,就是将程序或系统经常要调用的对象存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实例。这样做可以减少系统开销,提高系统效率。
AD:WOT2015 互联网运维与开发者大会 热销抢票
Cache
所谓缓存,就是将程序或系统经常要调用的对象存在内存中,一遍其使用时可以快速调用,不必再去创建新的重复的实例。这样做可以减少系统开销,提高系统效率。
缓存主要可分为二大类:
一、通过文件缓存,顾名思义文件缓存是指把数据存储在磁盘上,不管你是以XML格式,序列化文件DAT格式还是其它文件格式;
二、内存缓存,也就是实现一个类中静态Map,对这个Map进行常规的增删查.
代码如下 :
- package lhm.hcy.guge.frameset.cache;
- import java.util.*;
- //Description: 管理缓存
- //可扩展的功能:当chche到内存溢出时必须清除掉最早期的一些缓存对象,这就要求对每个缓存对象保存创建时间
- public class CacheManager {
- private static HashMap cacheMap = new HashMap();
- //单实例构造方法
- private CacheManager() {
- super();
- }
- //获取布尔值的缓存
- public static boolean getSimpleFlag(String key){
- try{
- return (Boolean) cacheMap.get(key);
- }catch(NullPointerException e){
- return false;
- }
- }
- public static long getServerStartdt(String key){
- try {
- return (Long)cacheMap.get(key);
- } catch (Exception ex) {
- return 0;
- }
- }
- //设置布尔值的缓存
- public synchronized static boolean setSimpleFlag(String key,boolean flag){
- if (flag && getSimpleFlag(key)) {//假如为真不允许被覆盖
- return false;
- }else{
- cacheMap.put(key, flag);
- return true;
- }
- }
- public synchronized static boolean setSimpleFlag(String key,long serverbegrundt){
- if (cacheMap.get(key) == null) {
- cacheMap.put(key,serverbegrundt);
- return true;
- }else{
- return false;
- }
- }
- //得到缓存。同步静态方法
- private synchronized static Cache getCache(String key) {
- return (Cache) cacheMap.get(key);
- }
- //判断是否存在一个缓存
- private synchronized static boolean hasCache(String key) {
- return cacheMap.containsKey(key);
- }
- //清除所有缓存
- public synchronized static void clearAll() {
- cacheMap.clear();
- }
- //清除某一类特定缓存,通过遍历HASHMAP下的所有对象,来判断它的KEY与传入的TYPE是否匹配
- public synchronized static void clearAll(String type) {
- Iterator i = cacheMap.entrySet().iterator();
- String key;
- ArrayList arr = new ArrayList();
- try {
- while (i.hasNext()) {
- java.util.Map.Entry entry = (java.util.Map.Entry) i.next();
- key = (String) entry.getKey();
- if (key.startsWith(type)) { //如果匹配则删除掉
- arr.add(key);
- }
- }
- for (int k = 0; k < arr.size(); k++) {
- clearOnly(arr.get(k));
- }
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- }
- //清除指定的缓存
- public synchronized static void clearOnly(String key) {
- cacheMap.remove(key);
- }
- //载入缓存
- public synchronized static void putCache(String key, Cache obj) {
- cacheMap.put(key, obj);
- }
- //获取缓存信息
- public static Cache getCacheInfo(String key) {
- if (hasCache(key)) {
- Cache cache = getCache(key);
- if (cacheExpired(cache)) { //调用判断是否终止方法
- cache.setExpired(true);
- }
- return cache;
- }else
- return null;
- }
- //载入缓存信息
- public static void putCacheInfo(String key, Cache obj, long dt,boolean expired) {
- Cache cache = new Cache();
- cache.setKey(key);
- cache.setTimeOut(dt + System.currentTimeMillis()); //设置多久后更新缓存
- cache.setValue(obj);
- cache.setExpired(expired); //缓存默认载入时,终止状态为FALSE
- cacheMap.put(key, cache);
- }
- //重写载入缓存信息方法
- public static void putCacheInfo(String key,Cache obj,long dt){
- Cache cache = new Cache();
- cache.setKey(key);
- cache.setTimeOut(dt+System.currentTimeMillis());
- cache.setValue(obj);
- cache.setExpired(false);
- cacheMap.put(key,cache);
- }
- //判断缓存是否终止
- public static boolean cacheExpired(Cache cache) {
- if (null == cache) { //传入的缓存不存在
- return false;
- }
- long nowDt = System.currentTimeMillis(); //系统当前的毫秒数
- long cacheDt = cache.getTimeOut(); //缓存内的过期毫秒数
- if (cacheDt <= 0||cacheDt>nowDt) { //过期时间小于等于零时,或者过期时间大于当前时间时,则为FALSE
- return false;
- } else { //大于过期时间 即过期
- return true;
- }
- }
- //获取缓存中的大小
- public static int getCacheSize() {
- return cacheMap.size();
- }
- //获取指定的类型的大小
- public static int getCacheSize(String type) {
- int k = 0;
- Iterator i = cacheMap.entrySet().iterator();
- String key;
- try {
- while (i.hasNext()) {
- java.util.Map.Entry entry = (java.util.Map.Entry) i.next();
- key = (String) entry.getKey();
- if (key.indexOf(type) != -1) { //如果匹配则删除掉
- k++;
- }
- }
- } catch (Exception ex) {
- ex.printStackTrace();
- }
- return k;
- }
- //获取缓存对象中的所有键值名称
- public static ArrayList getCacheAllkey() {
- ArrayList a = new ArrayList();
- try {
- Iterator i = cacheMap.entrySet().iterator();
- while (i.hasNext()) {
- java.util.Map.Entry entry = (java.util.Map.Entry) i.next();
- a.add((String) entry.getKey());
- }
- } catch (Exception ex) {} finally {
- return a;
- }
- }
- //获取缓存对象中指定类型 的键值名称
- public static ArrayList getCacheListkey(String type) {
- ArrayList a = new ArrayList();
- String key;
- try {
- Iterator i = cacheMap.entrySet().iterator();
- while (i.hasNext()) {
- java.util.Map.Entry entry = (java.util.Map.Entry) i.next();
- key = (String) entry.getKey();
- if (key.indexOf(type) != -1) {
- a.add(key);
- }
- }
- } catch (Exception ex) {} finally {
- return a;
- }
- }
- }
- package lhm.hcy.guge.frameset.cache;
- public class Cache {
- private String key;//缓存ID
- private Object value;//缓存数据
- private long timeOut;//更新时间
- private boolean expired; //是否终止
- public Cache() {
- super();
- }
- public Cache(String key, Object value, long timeOut, boolean expired) {
- this.key = key;
- this.value = value;
- this.timeOut = timeOut;
- this.expired = expired;
- }
- public String getKey() {
- return key;
- }
- public long getTimeOut() {
- return timeOut;
- }
- public Object getValue() {
- return value;
- }
- public void setKey(String string) {
- key = string;
- }
- public void setTimeOut(long l) {
- timeOut = l;
- }
- public void setValue(Object object) {
- value = object;
- }
- public boolean isExpired() {
- return expired;
- }
- public void setExpired(boolean b) {
- expired = b;
- }
- }
- //测试类,
- class Test {
- public static void main(String[] args) {
- System.out.println(CacheManager.getSimpleFlag("alksd"));
- // CacheManager.putCache("abc", new Cache());
- // CacheManager.putCache("def", new Cache());
- // CacheManager.putCache("ccc", new Cache());
- // CacheManager.clearOnly("");
- // Cache c = new Cache();
- // for (int i = 0; i < 10; i++) {
- // CacheManager.putCache("" + i, c);
- // }
- // CacheManager.putCache("aaaaaaaa", c);
- // CacheManager.putCache("abchcy;alskd", c);
- // CacheManager.putCache("cccccccc", c);
- // CacheManager.putCache("abcoqiwhcy", c);
- // System.out.println("删除前的大小:"+CacheManager.getCacheSize());
- // CacheManager.getCacheAllkey();
- // CacheManager.clearAll("aaaa");
- // System.out.println("删除后的大小:"+CacheManager.getCacheSize());
- // CacheManager.getCacheAllkey();
- }
- }
java中的本地缓存,工作后陆续用到,一直想写,一直无从下手,最近又涉及到这方面的问题了,梳理了一下。自己构造单例、guava、ehcache基本上涵盖了目前的大多数行为了。为什么要有本地缓存?在系统中,有些数据,数据量小,但是访问十分频繁(例如国家标准行政区域数据),针对这种场景,需要将数据搞到应用的本地缓存中,以提升系统的访问效率, 减少无谓的数据库访问(数据库访问占用数据库连接,同时网络消耗比较大),但是有一点需要注意,就是缓存的占用空间以及缓存的失效策略。为什么是本地缓存,而不是分布式的集群缓存?目前的数据,大多是业务无关的小数据缓存,没有必要搞分布式的集群缓存,目前涉及到订单和商品的数据,会直接走DB进行请求,再加上分布式缓存的构建,集群维护成本比较高,不太适合紧急的业务项目。这里介绍一下缓存使用的三个阶段(摘自info架构师文档)本地缓存在那个区域?目前考虑的是占用了JVM的heap区域,再细化一点的就是heap中的old区,目前的数据量来看,都是一些小数据,加起来没有几百兆,放 在heap区域最快最方便。后期如果需要放置在本地缓存的数据大的时候,可以考虑在off-heap区域(direct-memory 或者 big-memory),但是off-heap区域的话,需要考虑对象的序列化(因为off-heap区域存储的是二进制的数据),另外一个的话就是 off-heap的GC问题。其实,如果真的数据量比较大,那其实就可以考虑搞一个集中式的缓存系统,可以是单机,也可以是集群,来承担缓存的作用。搞一个单例模式,里面有个Map的变量来放置数据关于单例模式,一个既简单又复杂的模式(http://iamzhongyong.iteye.com/blog/1539642)非常典型的代码如下:public class SingletonMap {//一个本地的缓存Mapprivate Map localCacheStore = new HashMap();//一个私有的对象,非懒汉模式private static SingletonMap singletonMap = new SingletonMap();//私有构造方法,外部不可以new一个对象private SingletonMap(){}//静态方法,外部获得实例对象public static SingletonMap getInstance(){return singletonMap;}//获得缓存中的数据public Object getValueByKey(String key){return localCacheStore.get(key);}//向缓存中添加数据public void putValue(String key , Object value){localCacheStore.put(key, value);}}这种能不能用?可以用,但是非常局限但是这种的就是本地缓存了吗?答案显然不是,为啥呢?1、 没有缓存大小的设置,无法限定缓存体的大小以及存储数据的限制(max size limit);2、 没有缓存的失效策略(eviction policies);3、 没有弱键引用,在内存占用吃紧的情况下,JVM是无法回收的(weak rererences keys);4、 没有监控统计(statistics);5、 持久性存储(persistent store);所以,这种就直接废掉了。。。引入EhCache来构建缓存(详细介绍: http://raychase.iteye.com/blog/1545906)EhCahce的核心类:A、CacheManager:Cache的管理类;B、Cache:具体的cache类信息,负责缓存的get和put等操作C、CacheConfiguration :cache的配置信息,包含策略、最大值等信息D、Element:cache中单条缓存数据的单位典型的代码如下:public static void main(String[] args) {//EhCache的缓存,是通过CacheManager来进行管理的CacheManager cacheManager = CacheManager.getInstance();//缓存的配置,也可以通过xml文件进行CacheConfiguration conf = new CacheConfiguration();conf.name("cache_name_default");//设置名字conf.maxEntriesLocalHeap(1000);//最大的缓存数量conf.memoryStoreEvictionPolicy(MemoryStoreEvictionPolicy.LRU);//设置失效策略//创建一个缓存对象,并把设置的信息传入进去Cache localCache = new Cache(conf);//将缓存对象添加到管理器中cacheManager.addCache(localCache);localCache.put(new Element("iamzhongyong", new Date()));System.out.println(localCache.getSize());System.out.println(localCache.getStatistics().toString());System.out.println(localCache.getName());System.out.println(localCache.get("iamzhongyong").toString());System.out.println(localCache.get("iamzhongyong").getObjectValue());}当然,Cache的配置信息,可以通过配置文件制定了。。。优点:功能强大,有失效策略、最大数量设置等,缓存的持久化只有企业版才有,组件的缓存同步,可以通过jgroup来实现缺点:功能强大的同时,也使其更加复杂引入guava的cacheBuilder来构建缓存这个非常强大、简单,通过一个CacheBuilder类就可以满足需求。缺点就是如果要组件同步的话,需要自己实现这个功能。典型的代码如下:public class GuavaCacheBuilderTest {public static void main(String[] args) throws Exception{GuavaCacheBuilderTest cache = new GuavaCacheBuilderTest();cache.getNameLoadingCache("bixiao");}public void getNameLoadingCache(String name) throws Exception{LoadingCache cache = CacheBuilder.newBuilder().maximumSize(20)//设置大小,条目数.expireAfterWrite(20, TimeUnit.SECONDS)//设置失效时间,创建时间.expireAfterAccess(20, TimeUnit.HOURS) //设置时效时间,最后一次被访问.removalListener(new RemovalListener() { //移除缓存的监听器public void onRemoval(RemovalNotification notification) {System.out.println("有缓存数据被移除了");}}).build(new CacheLoader(){ //通过回调加载缓存@Overridepublic String load(String name) throws Exception {return name + "-" + "iamzhongyong";}});System.out.println(cache.get(name));//cache.invalidateAll();}}缓存预热怎么搞?A、全量预热,固定的时间段移除所有,然后再全量预热适用场景:1、数据更新不频繁,例如每天晚上3点更新即可的需求;2、数据基本没有变化,例如全国区域性数据;B、增量预热(缓存查询,没有,则查询数据库,有则放入缓存)适用场景:1、 数据更新要求缓存中同步更新的场景集群内部,缓存的一致性如何保证?如果采用ehcache的话,可以使用框架本身的JGroup来实现组内机器之间的缓存同步。如果是采用google的cacheBuilder的话,需要自己实现缓存的同步。A、非实时生效数据:数据的更新不会时时发生,应用启动的时候更新即可,然后定时程序定时去清理缓存;B、需要实时生效数据:启动时可预热也可不预热,但是缓存数据变更后,集群之间需要同步LRU是Least Recently Used 的缩写,翻译过来就是“最近最少使用”,LRU缓存就是使用这种原理实现,简单的说就是缓存一定量的数据,当超过设定的阈值时就把一些过期的数据删除掉, 比如我们缓存10000条数据,当数据小于10000时可以随意添加,当超过10000时就需要把新的数据添加进来,同时要把过期数据删除,以确保我们最 大缓存10000条,那怎么确定删除哪条过期数据呢,采用LRU算法实现的话就是将最老的数据删掉,废话不多说,下面来说下Java版的LRU缓存实现
Java里面实现LRU缓存通常有两种选择,一种是使用LinkedHashMap,一种是自己设计数据结构,使用链表+HashMap
LRU Cache的LinkedHashMap实现
LinkedHashMap自身已经实现了顺序存储,默认情况下是按照元素的添加顺序存储,也可以启用按照访问顺序存储,即最近读取的数据放在最前 面,最早读取的数据放在最后面,然后它还有一个判断是否删除最老数据的方法,默认是返回false,即不删除数据,我们使用LinkedHashMap实 现LRU缓存的方法就是对LinkedHashMap实现简单的扩展,扩展方式有两种,一种是inheritance,一种是delegation,具体 使用什么方式看个人喜好
//LinkedHashMap的一个构造函数,当参数accessOrder为true时,即会按照访问顺序排序,最近访问的放在最前,最早访问的放在后面 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; } //LinkedHashMap自带的判断是否删除最老的元素方法,默认返回false,即不删除老数据 //我们要做的就是重写这个方法,当满足一定条件时删除老数据 protected boolean removeEldestEntry(Map.Entry<K,V> eldest) { return false; }LRU缓存LinkedHashMap(inheritance)实现
采用inheritance方式实现比较简单,而且实现了Map接口,在多线程环境使用时可以使用 Collections.synchronizedMap()方法实现线程安全操作
package cn.lzrabbit.structure.lru; import java.util.LinkedHashMap; import java.util.Map; /** * Created by liuzhao on 14-5-15. */ public class LRUCache2<K, V> extends LinkedHashMap<K, V> { private final int MAX_CACHE_SIZE; public LRUCache2(int cacheSize) { super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true); MAX_CACHE_SIZE = cacheSize; } @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_CACHE_SIZE; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (Map.Entry<K, V> entry : entrySet()) { sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue())); } return sb.toString(); } }这样算是比较标准的实现吧,实际使用中这样写还是有些繁琐,更实用的方法时像下面这样写,省去了单独见一个类的麻烦
final int cacheSize = 100; Map<String, String> map = new LinkedHashMap<String, String>((int) Math.ceil(cacheSize / 0.75f) + 1, 0.75f, true) { @Override protected boolean removeEldestEntry(Map.Entry<String, String> eldest) { return size() > cacheSize; } };
LRU缓存LinkedHashMap(delegation)实现
delegation方式实现更加优雅一些,但是由于没有实现Map接口,所以线程同步就需要自己搞定了
package cn.lzrabbit.structure.lru; import java.util.LinkedHashMap; import java.util.Map; import java.util.Set; /** * Created by liuzhao on 14-5-13. */ public class LRUCache3<K, V> { private final int MAX_CACHE_SIZE; private final float DEFAULT_LOAD_FACTOR = 0.75f; LinkedHashMap<K, V> map; public LRUCache3(int cacheSize) { MAX_CACHE_SIZE = cacheSize; //根据cacheSize和加载因子计算hashmap的capactiy,+1确保当达到cacheSize上限时不会触发hashmap的扩容, int capacity = (int) Math.ceil(MAX_CACHE_SIZE / DEFAULT_LOAD_FACTOR) + 1; map = new LinkedHashMap(capacity, DEFAULT_LOAD_FACTOR, true) { @Override protected boolean removeEldestEntry(Map.Entry eldest) { return size() > MAX_CACHE_SIZE; } }; } public synchronized void put(K key, V value) { map.put(key, value); } public synchronized V get(K key) { return map.get(key); } public synchronized void remove(K key) { map.remove(key); } public synchronized Set<Map.Entry<K, V>> getAll() { return map.entrySet(); } public synchronized int size() { return map.size(); } public synchronized void clear() { map.clear(); } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (Map.Entry entry : map.entrySet()) { sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue())); } return sb.toString(); } }LRU Cache的链表+HashMap实现
注:此实现为非线程安全,若在多线程环境下使用需要在相关方法上添加synchronized以实现线程安全操作
package cn.lzrabbit.structure.lru; import java.util.HashMap; /** * Created by liuzhao on 14-5-12. */ public class LRUCache1<K, V> { private final int MAX_CACHE_SIZE; private Entry first; private Entry last; private HashMap<K, Entry<K, V>> hashMap; public LRUCache1(int cacheSize) { MAX_CACHE_SIZE = cacheSize; hashMap = new HashMap<K, Entry<K, V>>(); } public void put(K key, V value) { Entry entry = getEntry(key); if (entry == null) { if (hashMap.size() >= MAX_CACHE_SIZE) { hashMap.remove(last.key); removeLast(); } entry = new Entry(); entry.key = key; } entry.value = value; moveToFirst(entry); hashMap.put(key, entry); } public V get(K key) { Entry<K, V> entry = getEntry(key); if (entry == null) return null; moveToFirst(entry); return entry.value; } public void remove(K key) { Entry entry = getEntry(key); if (entry != null) { if (entry.pre != null) entry.pre.next = entry.next; if (entry.next != null) entry.next.pre = entry.pre; if (entry == first) first = entry.next; if (entry == last) last = entry.pre; } hashMap.remove(key); } private void moveToFirst(Entry entry) { if (entry == first) return; if (entry.pre != null) entry.pre.next = entry.next; if (entry.next != null) entry.next.pre = entry.pre; if (entry == last) last = last.pre; if (first == null || last == null) { first = last = entry; return; } entry.next = first; first.pre = entry; first = entry; entry.pre = null; } private void removeLast() { if (last != null) { last = last.pre; if (last == null) first = null; else last.next = null; } } private Entry<K, V> getEntry(K key) { return hashMap.get(key); } @Override public String toString() { StringBuilder sb = new StringBuilder(); Entry entry = first; while (entry != null) { sb.append(String.format("%s:%s ", entry.key, entry.value)); entry = entry.next; } return sb.toString(); } class Entry<K, V> { public Entry pre; public Entry next; public K key; public V value; } }LinkedHashMap的FIFO实现
FIFO是First Input First Output的缩写,也就是常说的先入先出,默认情况下LinkedHashMap就是按照添加顺序保存,我们只需重写下removeEldestEntry方法即可轻松实现一个FIFO缓存,简化版的实现代码如下
final int cacheSize = 5; LinkedHashMap<Integer, String> lru = new LinkedHashMap<Integer, String>() { @Override protected boolean removeEldestEntry(Map.Entry<Integer, String> eldest) { return size() > cacheSize; } };调用示例
测试代码
View Code运行结果
View Code
注:此文章属懒惰的肥兔原创,版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接
若您觉得这篇文章还不错请点击下右下角的推荐,有了您的支持才能激发作者更大的写作热情,非常感谢。
如有问题,可以通过[email protected]联系我。