所謂緩存,就是將程序或系統常常要調用的對象存在內存中,一遍其使用時能夠快速調用,沒必要再去建立新的重複的實例。這樣作能夠減小系統開銷,提升系統效率。 java
AD:WOT2015 互聯網運維與開發者大會 熱銷搶票 算法
Cache 數據庫
所謂緩存,就是將程序或系統常常要調用的對象存在內存中,一遍其使用時能夠快速調用,沒必要再去建立新的重複的實例。這樣作能夠減小系統開銷,提升系統效率。 緩存
緩存主要可分爲二大類: 安全
1、經過文件緩存,顧名思義文件緩存是指把數據存儲在磁盤上,無論你是以XML格式,序列化文件DAT格式仍是其它文件格式; 網絡
2、內存緩存,也就是實現一個類中靜態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);}}這種能不能用?能夠用,可是很是侷限可是這種的就是本地緩存了嗎?答案顯然不是,爲啥呢?一、 沒有緩存大小的設置,沒法限定緩存體的大小以及存儲數據的限制(max size limit);二、 沒有緩存的失效策略(eviction policies);三、 沒有弱鍵引用,在內存佔用吃緊的狀況下,JVM是沒法回收的(weak rererences keys);四、 沒有監控統計(statistics);五、 持久性存儲(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、全量預熱,固定的時間段移除全部,而後再全量預熱適用場景:一、數據更新不頻繁,例如天天晚上3點更新便可的需求;二、數據基本沒有變化,例如全國區域性數據;B、增量預熱(緩存查詢,沒有,則查詢數據庫,有則放入緩存)適用場景:一、 數據更新要求緩存中同步更新的場景集羣內部,緩存的一致性如何保證?若是採用ehcache的話,可使用框架自己的JGroup來實現組內機器之間的緩存同步。若是是採用google的cacheBuilder的話,須要本身實現緩存的同步。A、非實時生效數據:數據的更新不會時時發生,應用啓動的時候更新便可,而後定時程序定時去清理緩存;B、須要實時生效數據:啓動時可預熱也可不預熱,可是緩存數據變動後,集羣之間須要同步LRU是Least Recently Used 的縮寫,翻譯過來就是「最近最少使用」,LRU緩存就是使用這種原理實現,簡單的說就是緩存必定量的數據,當超過設定的閾值時就把一些過時的數據刪除掉, 好比咱們緩存10000條數據,當數據小於10000時能夠隨意添加,當超過10000時就須要把新的數據添加進來,同時要把過時數據刪除,以確保咱們最 大緩存10000條,那怎麼肯定刪除哪條過時數據呢,採用LRU算法實現的話就是將最老的數據刪掉,廢話很少說,下面來講下Java版的LRU緩存實現 架構
Java裏面實現LRU緩存一般有兩種選擇,一種是使用LinkedHashMap,一種是本身設計數據結構,使用鏈表+HashMap app
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
注:此文章屬懶惰的肥兔原創,版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接
若您以爲這篇文章還不錯請點擊下右下角的推薦,有了您的支持才能激發做者更大的寫做熱情,很是感謝。
若有問題,能夠經過lzrabbit@126.com聯繫我。