日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java比较常用的缓存技术_常用缓存技术

發布時間:2023/12/10 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java比较常用的缓存技术_常用缓存技术 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

熱數據緩存

這是使用緩存最頻繁最直接的方式,即我們把需要頻繁訪問DB的數據加載到內存里面,以提高響應速度。通常我們的做法是使用一個ConcuccrentHashMap來記錄一天當中每個請求的次數,每天凌晨取出昨天訪問最頻繁的K個請求(K取多少個取決你的可用內存有多少),從DB中讀取這些請求的返回結果放到一個ConcuccrentHashMap容器中,然后把所有請求計數清0,重新開始計數。

LRU緩存

熱數據緩存適用于那些熱數據比較明顯且穩定的業務場景,而對于那些熱數據不穩定的應用場景我們需要發明一種動態的熱數據識別方式。我們都知道常用的內存換頁算法有2種:LFU和LRU。

LFU(Least Frequently Used)是把那些最近最不經常使用的頁面置換出去,這跟上面講的熱數據緩存是一個道理,缺點有2個:

需要維護一個計數器,記住每個頁面的使用次數。

上一個時間段頻繁使用的,在下一個時間段不一定還頻繁。

LRU(Least Recently Used)策略是把最近最長時間未使用的頁面置換出去。實現起來很簡單,只需要一個鏈表結構,每次訪問一個元素時把它移到鏈表的尾部,當鏈表已滿需要刪除元素時就刪除頭部的元素,因為頭部的元素就是最近最長時間未使用的元素。

1 importjava.util.ArrayList;2 importjava.util.Collection;3 importjava.util.LinkedHashMap;4 importjava.util.Map;5 importjava.util.concurrent.locks.ReadWriteLock;6 importjava.util.concurrent.locks.ReentrantReadWriteLock;7

8 /**

9 * 利用LinkedHashMap實現一個定長容量的,先進先出的隊列。當指定按訪問順序排序時,就實際上是一個最近最少使用LRU隊列
10 *
11 * 根據鏈表中元素的順序可以分為:按插入順序的鏈表,和按訪問順序(調用get方法)的鏈表。
12 * 默認是按插入順序排序,如果指定按訪問順序排序,那么調用get方法后,會將這次訪問的元素移至鏈表尾部。
13 * 不斷訪問可以形成按訪問順序排序的鏈表。
14 * 可以重寫removeEldestEntry方法返回true值指定插入元素時移除最老的元素。
15 *16 * @Author:zhangchaoyang17 * @Since:2014-9-518 * @Version:1.019 */

20 public class LRUCache extends LinkedHashMap{21

22 private static final long serialVersionUID = -2045058079564141163L;23

24 private final intmaxCapacity;25

26 //本類中設置裝載因子實際沒有意義,因為容量超過maxCapacity時就會把元素移除掉

27 private static final float DEFAULT_LOAD_FACTOR =1f;28

29 private final ReadWriteLock lock = newReentrantReadWriteLock();30

31 public LRUCache(intmaxCapacity) {32 super(maxCapacity, DEFAULT_LOAD_FACTOR, true);//第3個參數false表示維持插入順序,這樣最早插入的將最先被移除。true表示維持訪問順序,調用get方法后,會將這次訪問的元素移至鏈表尾部,刪除老元素時會刪除表頭元素。

33 this.maxCapacity =maxCapacity;34 }35

36 @Override37 protected boolean removeEldestEntry(java.util.Map.Entryeldest) {38 return size() > maxCapacity;//到達maxCapacity時就移除老元素,這樣實現定長的LinkedHashMap

39 }40

41 @Override42 public booleancontainsKey(Object key) {43 try{44 lock.readLock().lock();45 return super.containsKey(key);46 } finally{47 lock.readLock().unlock();48 }49 }50

51 @Override52 publicV get(Object key) {53 try{54 lock.readLock().lock();55 return super.get(key);56 } finally{57 lock.readLock().unlock();58 }59 }60

61 @Override62 publicV put(K key, V value) {63 try{64 lock.writeLock().lock();65 return super.put(key, value);66 } finally{67 lock.writeLock().unlock();68 }69 }70

71 public intsize() {72 try{73 lock.readLock().lock();74 return super.size();75 } finally{76 lock.readLock().unlock();77 }78 }79

80 public voidclear() {81 try{82 lock.writeLock().lock();83 super.clear();84 } finally{85 lock.writeLock().unlock();86 }87 }88

89 public Collection>getAll() {90 try{91 lock.readLock().lock();92 return new ArrayList>(super.entrySet());93 } finally{94 lock.readLock().unlock();95 }96 }97 }

View Code

TimeOut緩存

Timeout緩存常用于那些跟用戶關聯的請求數據,比如用戶在翻頁查看一個列表數據時,他第一次看N頁的數據時,服務器是從DB中讀取的相應數據,當他看第N+1頁的數據時應該把第N頁的數據放入緩存,因為用戶可能呆會兒還會回過頭來看第N頁的數據,這時候服務器就可以直接從緩存中獲取數據。如果用戶在5分鐘內還沒有回過頭來看第N頁的數據,那么我們認為他再看第N頁的概率就非常低了,此時可以把第N頁的數據從緩存中移除,實際上相當于我們為緩存設置了一個超時時間。

我想了一種Timeout緩存的實現方法。還是用ConcurrentHashMap來存放key-value,另建一棵小頂堆,每個節點上存放key以及key的到期時間,建堆時依據到期時間來建。開一個后臺線程不停地掃描堆頂元素,拿當前的時間戳去跟堆頂的到期時間比較,如果當前時間晚于堆頂的到期時間則刪除堆頂,把堆頂里存放的key從ConcurrentHashMap中刪除。刪除堆頂的時間復雜度為$O(log_2{N})$,具體步驟如下:

用末元素替換堆頂元素root

臨時保存root節點。從上往下遍歷樹,用子節點中較小那個替換父節點。最后把root放到葉節點上

下面的代碼是直接基于java中的java.util.concurrent.Delayed實現的,Delayed是不是基于上面的小頂堆的思想我也沒去深入研究。

TimeoutCache.java

1 importjava.io.IOException;2 importjava.util.concurrent.ConcurrentHashMap;3 importjava.util.concurrent.ConcurrentMap;4 importjava.util.concurrent.DelayQueue;5 importjava.util.concurrent.TimeUnit;6

7 importorg.apache.commons.logging.Log;8 importorg.apache.commons.logging.LogFactory;9

10 /**

11 * 可以為每個元素設置存活時間的緩存容器12 *13 * @Author:orisun14 * @Since:2015-10-915 * @Version:1.016 */

17 public class TimeoutCache{18

19 private static final Log logger = LogFactory.getLog(TimeoutCache.class);20 private ConcurrentMap cacheObjMap = new ConcurrentHashMap();21 private DelayQueue>> queue = new DelayQueue>>();22 privateThread daemonThread;23

24 publicTimeoutCache() {25 Runnable daemonTask = newRunnable() {26 public voidrun() {27 daemonCheck();28 }29 };30 daemonThread = newThread(daemonTask);31 daemonThread.setDaemon(true);32 daemonThread.setName("TimeoutCache Daemon Check");33 daemonThread.start(); //啟動后臺線程,對容器中的元素不停地進行輪循,將過期的元素移除出出去

34 }35

36 private voiddaemonCheck() {37 logger.info("check timeout element of cache started");38 for(;;) {39 try{40 DelayItem> delayItem = queue.take();//如果所有元素都沒有超時,該行代碼會阻塞

41 if (delayItem != null) {42 Pair pair =delayItem.getItem();43 cacheObjMap.remove(pair.first, pair.second); //超時對象,從容器中移除

44 }45 } catch(InterruptedException e) {46 logger.error("take timeout element from cache failed", e);47 break; //檢測到中斷時就退出循環

48 }49 }50 logger.info("check timeout element of cache stopped.");51 }52

53 /**

54 * 以覆蓋的方式向緩存中添加對象,緩存以的形式存在.
55 * 注意:value如果是List,則它不是由通過List.subList()得來的56 * 。因為List.subList()返回的是一個RandomAccessSubList實例57 * ,在反序列化時ObjectOutputStream.writeObject(RandomAccessSubList)會出錯58 *59 *@paramkey60 *@paramvalue61 *@paramtime62 * 對象在緩存中的生存時間63 *@paramunit64 * 時間單位65 */

66 public void put(K key, V value, longtime, TimeUnit unit) {67 V oldValue =cacheObjMap.put(key, value);68 if (oldValue != null)69 queue.remove(key);70

71 long nanoTime =TimeUnit.NANOSECONDS.convert(time, unit);72 queue.put(new DelayItem>(new Pair(key, value),73 nanoTime));74 }75

76 /**

77 * 根據key從緩存中取得對應的value,如果key不存在則返回null
78 * 取出的是value的深拷貝79 *80 *@paramkey81 *@return

82 */

83 @SuppressWarnings("unchecked")84 publicV get(K key) {85 try{86 return(V) JavaSerializer.deepCopy(cacheObjMap.get(key));87 } catch (ClassNotFoundException |IOException e) {88 e.printStackTrace();89 return null;90 }91 }92

93 }

View Code

DelayItem.java

1 importjava.util.concurrent.Delayed;2 importjava.util.concurrent.TimeUnit;3 importjava.util.concurrent.atomic.AtomicLong;4

5 /**

6 *7 * @Author:orisun8 * @Since:2015-10-99 * @Version:1.010 */

11 public class DelayItem implementsDelayed {12

13 private static final long ORIGIN = System.nanoTime();//記錄進入隊列的時刻

14 private static final AtomicLong sequencer = new AtomicLong(0);15 private final longsequenceNumber;16 private final longtime;17 private finalT item;18

19 final static longnow() {20 return System.nanoTime() -ORIGIN;21 }22

23 /**

24 *25 *@paramsubmit26 * 隊列中的元素類型27 *@paramtimeout28 * 元素在隊列中存活的時間,單位:毫秒29 */

30 public DelayItem(T submit, longtimeout) {31 this.time = now() + timeout;//出隊時刻

32 this.item = submit;//入隊元素

33 this.sequenceNumber = sequencer.getAndIncrement();//在隊列中的編號

34 }35

36 publicT getItem() {37 return this.item;38 }39

40 @Override41 public longgetDelay(TimeUnit unit) {42 long d = unit.convert(time -now(), TimeUnit.NANOSECONDS);43 returnd;44 }45

46 @Override47 public intcompareTo(Delayed other) {48 if (other == this)49 return 0;50 if (other instanceofDelayItem) {51 DelayItem> x = (DelayItem>) other;52 long diff = time -x.time;53 if (diff < 0)54 return -1;55 else if (diff > 0)56 return 1;57 else if (sequenceNumber < x.sequenceNumber) //如果是同時進入隊列的,則先進者先出

58 return -1;59 else

60 return 1;61 }62 long d = (getDelay(TimeUnit.NANOSECONDS) -other63 .getDelay(TimeUnit.NANOSECONDS));64 return (d == 0) ? 0 : ((d < 0) ? -1 : 1);65 }66 }

View Code

JavaSerializer.java

1 importjava.io.ByteArrayInputStream;2 importjava.io.ByteArrayOutputStream;3 importjava.io.IOException;4 importjava.io.ObjectInputStream;5 importjava.io.ObjectOutputStream;6

7 public classJavaSerializer {8

9 public static Object deepCopy(Object obj) throwsIOException,10 ClassNotFoundException {11 //將該對象序列化成流,因為寫在流里的是對象的一個拷貝,而原對象仍然存在于JVM里面。所以利用這個特性可以實現對象的深拷貝

12 ByteArrayOutputStream bos = newByteArrayOutputStream();13 ObjectOutputStream oos = newObjectOutputStream(bos);14 oos.writeObject(obj);//要寫入ObjectOutputStream的話必須實現Serializable接口15 //將流序列化成對象

16 ByteArrayInputStream bis = newByteArrayInputStream(bos.toByteArray());17 ObjectInputStream ois = newObjectInputStream(bis);18 returnois.readObject();19 }20 }

View Code

Redis省內存的技巧

redis自帶持久化功能,當它決定要把哪些數據換出內存寫入磁盤時,使用的也是LRU算法。同時redis也有timeout機制,但它不像上面的TimeoutCache.java類一樣開個無限循環的線程去掃描到期的元素,而是每次get元素時判斷一個該元素有沒有到期,所以redis中一個元素的存活時間遠遠超出了設置的時間是很正常的。

本節想講的重點其實是redis省內存的技巧,這也是實踐中經常遇到的問題,因為內存總是很昂貴的,運維大哥總是很節約的。在我們的推薦系數中使用Redis來存儲信息的索引,沒有使用Lucene是因為Lucene不支持分布式,但是省內存的技巧都是從Lucene那兒學來的。

首先,如果你想為redis節省內存那你就不能再用類型的key-value結構,必須全部將它們序列化成二進制的形式。我寫了一個工具類,實現各種數據類型和byte[]的互相置換。

DataTransform.java

1 importjava.nio.ByteBuffer;2 importjava.util.ArrayList;3 importjava.util.List;4

5 /**

6 * 各種數據類型的相互轉換
7 *

  • 8 *
  • {@code<{@code>>} 右移,符號位不動10 *
  • {@code>>>} 循環右移,符號位要跟著移,高位用0填充11 *
12 * 位移運算只對32位和64位值有意義。位移運算返回一個新值,但是不改變原值。13 *14 * @Author:zhangchaoyang15 * @Since:2014-7-916 * @Version:17 */

18 public classDataTransform {19

20 private static final char[] Digit = { '0', '1', '2', '3', '4', '5', '6',21 '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};22

23 /**

24 * byte數組轉換成int25 *26 *@parambRefArr27 * byte數組28 *@paramLowEndian29 * byte數組是否按小端字節序存儲30 *@returnint值31 *@throwsArgumentException32 * byte數組長度超過4時拋出該異常33 */

34 public static int bytesToInt(byte[] bRefArr, booleanLowEndian)35 throwsArgumentException {36 int len =bRefArr.length;37 if (len > 4) {38 throw new ArgumentException("字節數組長度不能超過4");39 }40

41 int iOutcome = 0;42 bytebLoop;43 for (int i = 0; i < len; i++) {44 bLoop =bRefArr[i];45 intshift;46 if(LowEndian) {47 shift =i;48 } else{49 shift = len - 1 -i;50 }51 iOutcome += (bLoop & 0xFF) << (8 * shift);//之所以要跟0xFF進行與運行是為了把bLoop轉換成int,去除符號位的影響

52 }53 returniOutcome;54 }55

56 /**

57 * byte數組轉換成long58 *59 *@parambRefArr60 * byte數組61 *@paramLowEndian62 * byte數組是否按小端字節序存儲63 *@returnlong值64 *@throwsArgumentException65 * byte數組長度超過8時拋出該異常66 */

67 public static long bytesToLong(byte[] bRefArr, booleanLowEndian)68 throwsArgumentException {69 int len =bRefArr.length;70 if (len > 8) {71 throw new ArgumentException("字節數組長度不能超過8");72 }73

74 long iOutcome = 0;75 bytebLoop;76 for (int i = 0; i < len; i++) {77 bLoop =bRefArr[i];78 intshift;79 if(LowEndian) {80 shift =i;81 } else{82 shift = len - 1 -i;83 }84 iOutcome += (bLoop & 0xFFL) << (8 * shift);//之所以要跟0xFFL進行與運行是為了把bLoop轉換成long,去除符號位的影響

85 }86 returniOutcome;87 }88

89 /**

90 * byte數組轉換成double91 *92 *@parambRefArr93 * byte數組94 *@paramLowEndian95 * byte數組是否按小端字節序存儲96 *@returndouble值97 *@throwsArgumentException98 * byte數組長度超過8時拋出該異常99 */

100 public static double bytesToDouble(byte[] bRefArr, booleanLowEndian)101 throwsArgumentException {102 long l =bytesToLong(bRefArr, LowEndian);103 returnDouble.longBitsToDouble(l);104 }105

106 /**

107 * int轉換為byte數組,采用大端字節序會更快一些108 *109 *@paramnumber110 * int數111 *@paramLowEndian112 * byte數組是否按小端字節序存儲113 *@returnbyte數組114 */

115 public static byte[] intToBytes(int number, booleanLowEndian) {116 int len = 4;117 byte[] rect = new byte[len];118 for (int i = 0; i < len; i++) {119 rect[i] = (byte) (number >>> (len - 1 - i) * 8);120 }121 if(LowEndian) {122 for (int i = 0; i < len / 2; i++) {123 byte swap =rect[i];124 rect[i] = rect[len - i - 1];125 rect[len - i - 1] =swap;126 }127 }128 returnrect;129 }130

131 /**

132 * 仿照Lucene的可變長度整型:最高位表示是否還有字節要讀取,低七位就是就是具體的有效位,添加到結果數據中.
133 * 比如00000001 最高位表示0,那么說明這個數就是一個字節表示,有效位是后面的七位0000001,值為1。10000010 00000001134 * 第一個字節最高位為1135 * ,表示后面還有字節,第二位最高位0表示到此為止了,即就是兩個字節,那么具體的值注意,是從最后一個字節的七位有效數放在最前面,依次放置136 * ,最后是第一個自己的七位有效位,所以這個數表示 0000001 0000010,換算成整數就是130。
137 * 用VInt來表示Integer.MAX_VALUE時需要5個字節.138 *139 *@paramnum140 *@return

141 */

142 public static byte[] vintToByte(intnum) {143 ByteBuffer buffer = ByteBuffer.allocate(32);144 while ((num & ~0x7F) != 0) {145 buffer.put((byte) ((num & 0x7F) | 0x80));146 num >>>= 7;//等價于num=num>>>7;

147 }148 buffer.put((byte) num);149 byte[] rect = new byte[buffer.position()];150 buffer.flip();151 buffer.get(rect);152 returnrect;153 }154

155 public static byte[] vintArrToByteArr(int[] arr) {156 ByteBuffer buffer = ByteBuffer.allocate(32 *arr.length);157 for (intele : arr) {158 byte[] brr =vintToByte(ele);159 buffer.put(brr);160 }161 byte[] rect = new byte[buffer.position()];162 buffer.flip();163 buffer.get(rect);164 returnrect;165 }166

167 /**

168 * 仿照Lucene的可變長度整型169 *170 *@see#vintToByte171 *@parambytes172 *@return

173 */

174 public static int byteToVInt(byte[] bytes) {175 int i = 0;176 byte b = bytes[i++];177 int num = b & 0x7F;178 for (int shift = 7; (b & 0x80) != 0; shift += 7) {179 b = bytes[i++];180 num |= (b & 0x7F) <

185 public static int[] byteArrToVIntArr(byte[] bytes) {186 List list = new ArrayList();187 int i = 0;188 while (i

204 /**

205 * 仿照Lucene的可變長度整型206 *207 *@see#vintToByte208 *@paramnum209 *@return

210 */

211 public static byte[] vlongToByte(longnum) {212 ByteBuffer buffer = ByteBuffer.allocate(64);213 while ((num & ~0x7F) != 0) {214 buffer.put((byte) ((num & 0x7F) | 0x80));215 num >>>= 7;216 }217 buffer.put((byte) num);218 byte[] rect = new byte[buffer.position()];219 buffer.flip();220 buffer.get(rect);221 returnrect;222 }223

224 /**

225 * 仿照Lucene的可變長度整型226 *227 *@see#vintToByte228 *@parambytes229 *@return

230 */

231 public static long byteToVLong(byte[] bytes) {232 int i = 0;233 byte b = bytes[i++];234 long num = b & 0x7FL;235 for (int shift = 7; (b & 0x80) != 0; shift += 7) {236 b = bytes[i++];237 num |= (b & 0x7FL) <

242 /**

243 * long轉換為byte數組244 *245 *@paramnumber246 * long數247 *@paramLowEndian248 * byte數組是否按小端字節序存儲249 *@returnbyte數組,長度為8250 */

251 public static byte[] longToBytes(long number, booleanLowEndian) {252 int len = 8;253 byte[] rect = new byte[len];254 for (int i = 0; i < len; i++) {255 rect[i] = (byte) (number >>> (len - 1 - i) * 8);256 }257 if(LowEndian) {258 for (int i = 0; i < len / 2; i++) {259 byte swap =rect[i];260 rect[i] = rect[len - i - 1];261 rect[len - i - 1] =swap;262 }263 }264 returnrect;265 }266

267 /**

268 * double轉換為byte數組269 *270 *@paramnumber271 * double數值272 *@paramLowEndian273 * byte數組是否按小端字節序存儲274 *@returnbyte數組,長度為8275 */

276 public static byte[] doubleToBytes(double number, booleanLowEndian) {277 long l =Double.doubleToLongBits(number);278 returnlongToBytes(l, LowEndian);279 }280

281 /**

282 * IP轉換成int值,int在全域上和IP是一一對應的283 *284 *@paramip285 *@return

286 *@throwsArgumentException287 * IP范圍超界時拋出該異常288 */

289 public static int ip2int(String ip) throwsArgumentException {290 String[] arr = ip.trim().split("\\.");291 int part1 = Integer.parseInt(arr[0]);292 int part2 = Integer.parseInt(arr[1]);293 int part3 = Integer.parseInt(arr[2]);294 int part4 = Integer.parseInt(arr[3]);295 if (part1 >= 0 && part1 < 256 && part2 >= 0 && part2 < 256

296 && part3 >= 0 && part3 < 256 && part4 >= 0 && part4 < 256) {297 //左移,正數左移之后有可能把最高位變為1,從而成為負數

298 int rect = part1 << 24;299 rect += part2 << 16;300 rect += part3 << 8;301 rect +=part4;302 returnrect;303 } else{304 throw new ArgumentException("IP范圍超界");305 }306 }307

308 /**

309 * int值轉換成IP,int在全域上和IP是一一對應的310 *311 *@paramnumber312 *@return

313 */

314 public static String int2ip(intnumber) {315 StringBuilder sb = newStringBuilder();316 int part1 = number >>> 24;//右移,如果是負數最高位的1會向右移,且最高位變為0

317 int part2 = (0x00ff0000 & number) >>> 16;//位移的優先級高于與運算的優先級

318 int part3 = (0x0000ff00 & number) >>> 8;319 int part4 = 0x000000ff &number;320 sb.append(String.valueOf(part1));321 sb.append(".");322 sb.append(String.valueOf(part2));323 sb.append(".");324 sb.append(String.valueOf(part3));325 sb.append(".");326 sb.append(String.valueOf(part4));327 returnsb.toString();328 }329

330 /**

331 * 一個將字節轉化為十六進制ASSIC碼的函數332 *333 *@paramib334 *@return

335 */

336 public static String byteHEX(byteib) {337 char[] ob = new char[2];338 ob[0] = Digit[(ib >>> 4) & 0X0F];339 ob[1] = Digit[ib & 0X0F];340 String s = newString(ob);341 returns;342 }343

344 public static String byteHEX(byte[] bytes) {345 StringBuilder sb = newStringBuilder();346 for (byteib : bytes) {347 char[] ob = new char[2];348 ob[0] = Digit[(ib >>> 4) & 0X0F];349 ob[1] = Digit[ib & 0X0F];350 String s = newString(ob);351 sb.append(s);352 }353 returnsb.toString();354 }355

356 /**

357 * 把一個byte表示成二進制的字符串字面值358 *359 *@paramib360 *@return

361 */

362 public static String byteLiteral(byteib) {363 StringBuilder sb = newStringBuilder();364 for (int i = 7; i >= 0; i--) {365 int v = (ib >>> i) & 0x01;366 if (v == 0) {367 sb.append("0");368 } else{369 sb.append("1");370 }371 }372 returnsb.toString();373 }374

375 public static String byteLiteral(byte[] ib) {376 StringBuilder sb = newStringBuilder();377 for (int i = 0; i < ib.length; i++) {378 sb.append(byteLiteral(ib[i]));379 }380 returnsb.toString();381 }382 }

View Code

請留意一下上述代碼中出現了VInt和VLong兩種類型,具體看注釋。

倒排索引常見的形式為:term --> ?[infoid1,infoid2,infoid3...],針對這種形式的索引我們看下如何節省內存。首先value要采用redis中的list結構,而且是list而非list(想省內存就要杜絕使用String,上面已經說過了)。假如infoid是個int,置換成byte[]就要占4個字節,而絕大部分情況下infoid都1000萬以內的數字,因此使用VInt只需要3個字節。內存還可以進一步壓縮。鏈表的第1個infoid我們存儲它的VInt形式,后面的infoid與infoid1相減,差值也是個1000萬以內的數字而且有可能非常小,我們采用VInt存儲這個差值最多需要3個字節,有可能只需要1個字節。訪問鏈表中的任意一個元素時都需要先把首元素取出來。

另一種常見的索引形式為:infoid --> infoDetail,infoDetail中包含很多字段,譬如city、valid、name等,通常情況下人們會使用Redis的hash結構來存儲實體,而我們現在要做的就是把infoDetail這個實體序列化成盡可能短的字節流。首先city代表城市,本來是個String類型,而city這個東西是可以窮舉的,我們事先對所有city進行編號,在redis中只存儲city編號即可。valid表示信息是否過期是個bool類型,在java中存儲一個bool也需要1個字節,這顯然很浪費,本來一個bit就夠了嘛,同時city又用不滿一個int,所以可以讓valid跟city擠一擠,把city左移一位,把valid塞到city的末位上去。

1 importjava.nio.ByteBuffer;2

3 /**

4 *5 *@Author:orisun6 *@Since:2016-5-147 *@Version:1.08 */

9 public classInfo {10

11 private intcity;12 private booleanvalid;13 privateString name;14

15 public byte[] serialize() {16 ByteBuffer buffer = ByteBuffer.allocate(10);17 int cv = (city << 1) + (valid ? 1 : 0);18 byte[] cv_b = DataTransform.intToBytes(cv, false);19 buffer.put(cv_b);20 buffer.put(name.getBytes());21 byte[] rect = new byte[buffer.position()];22 buffer.flip();23 buffer.get(rect);24 returnrect;25 }26

27 public static Info deserialize(byte[] value) {28 if (value == null || value.length <= 4) {29 return null;30 }31 Info inst = newInfo();32 try{33 int cv = DataTransform.bytesToInt(new byte[] { (byte) value[0],34 value[1], value[2], value[3] }, false);35 inst.setValid(cv % 2 != 0);36 inst.setCity(cv >> 1);37 inst.setName(new String(value, 4, value.length - 4));38 } catch(ArgumentException e) {39 e.printStackTrace();40 }41 returninst;42 }43

44 public intgetCity() {45 returncity;46 }47

48 public void setCity(intcity) {49 this.city =city;50 }51

52 public booleanisValid() {53 returnvalid;54 }55

56 public void setValid(booleanvalid) {57 this.valid =valid;58 }59

60 publicString getName() {61 returnname;62 }63

64 public voidsetName(String name) {65 this.name =name;66 }67

68 public static voidmain(String[] args) {69 Info inst1 = newInfo();70 inst1.setCity(100);71 inst1.setValid(true);72 inst1.setName("pc");73 Info inst2 =Info.deserialize(inst1.serialize());74 assert inst1.getCity() ==inst2.getCity();75 assertinst1.getName().equals(inst2.getName());76 assert inst1.isValid() ^inst2.isValid();77 }78 }

View Code

總結

以上是生活随笔為你收集整理的java比较常用的缓存技术_常用缓存技术的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。