在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量。对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理,每次打开应用都去网络获取图片,那么用户可就不乐意了,这里的处理就是指今天要讲的缓存策略(缓存层分为三层:内存层,磁盘层,网络层)。

  关于缓存层的工作,当我们第一次打开应用获取图片时,先到网络去下载图片,然后依次存入内存缓存,磁盘缓存,当我们再一次需要用到刚才下载的这张图片时,就不需要再重复的到网络上去下载,直接可以从内存缓存和磁盘缓存中找,由于内存缓存速度较快,我们优先到内存缓存中寻找该图片,如果找到则运用,如果没有找到(内存缓存大小有限),那么我们再到磁盘缓存中去找。只要我们合理的去协调这三层缓存运用,便可以提升应用性能和用户体验。

1、内存层:(手机内存)

内存缓存相对于磁盘缓存而言,速度要来的快很多,但缺点容量较小且会被系统回收,这里的实现我用到了LruCache。

LruCache这个类是Android3.1版本中提供的,如果你是在更早的Android版本中开发,则需要导入android-support-v4的jar包。

磁盘层:(SD卡)

相比内存缓存而言速度要来得慢很多,但容量很大,这里的实现我用到了DiskLruCache类。

DiskLruCache是非Google官方编写,但获得官方认证的硬盘缓存类,该类没有限定在Android内,所以理论上java应用也可以使用DiskLreCache来缓存。

这是DiskLruCache类的下载地址:http://pan.baidu.com/s/1hq0D53m

网络层:(移动网络,无线网络)

这个就没什么解释的了,就是我们上网用的流量。这里的网络访问实现我用到了开源框架Volley。

开源框架Volley是2013年Google I/O大会发布的,Volley是Android平台上的网络通信库,能使网络通信更快,更简单,更健壮。它的设计目标就是非常适合去进行数据量不大,但通信频繁的网络操作,而对于大数据量的网络操作,比如说下载文件等,Volley的表现就会非常糟糕。

这是Volley的下载地址:http://pan.baidu.com/s/1hq1t2yo

来看下代码实现:

1、由于应用中很多地方需要用到上下文对象,这里我自定义了一个全局的Application,用来提供上下文对象

  1. package com.lcw.rabbit.image.utils;
  2. import android.app.Application;
  3. /**
  4. * Application类,提供全局上下文对象
  5. * @author Rabbit_Lee
  6. *
  7. */
  8. public class MyApplication extends Application {
  9. public static String TAG;
  10. public static MyApplication myApplication;
  11. public static MyApplication newInstance() {
  12. return myApplication;
  13. }
  14. @Override
  15. public void onCreate() {
  16. super.onCreate();
  17. TAG = this.getClass().getSimpleName();
  18. myApplication = this;
  19. }
  20. }

2、Volley请求队列处理类,用来管理Rquest请求对象操作

  1. package com.lcw.rabbit.image;
  2. import com.android.volley.Request;
  3. import com.android.volley.RequestQueue;
  4. import com.android.volley.toolbox.Volley;
  5. import com.lcw.rabbit.image.utils.MyApplication;
  6. /**
  7. * 请求队列处理类
  8. * 获取RequestQueue对象
  9. */
  10. public class VolleyRequestQueueManager {
  11. // 获取请求队列类
  12. public static RequestQueue mRequestQueue = Volley.newRequestQueue(MyApplication.newInstance());
  13. //添加任务进任务队列
  14. public static void addRequest(Request<?> request, Object tag) {
  15. if (tag != null) {
  16. request.setTag(tag);
  17. }
  18. mRequestQueue.add(request);
  19. }
  20. //取消任务
  21. public static void cancelRequest(Object tag){
  22. mRequestQueue.cancelAll(tag);
  23. }
  24. }

3、这里附上2个工具类(生成MD5序列帮助类,DiskLruCache磁盘缓存类)

  1. package com.lcw.rabbit.image.utils;
  2. import java.math.BigInteger;
  3. import java.security.MessageDigest;
  4. import java.security.NoSuchAlgorithmException;
  5. public class MD5Utils {
  6. /**
  7. * 使用md5的算法进行加密
  8. */
  9. public static String md5(String plainText) {
  10. byte[] secretBytes = null;
  11. try {
  12. secretBytes = MessageDigest.getInstance("md5").digest(
  13. plainText.getBytes());
  14. } catch (NoSuchAlgorithmException e) {
  15. throw new RuntimeException("没有md5这个算法!");
  16. }
  17. String md5code = new BigInteger(1, secretBytes).toString(16);// 16进制数字
  18. // 如果生成数字未满32位,需要前面补0
  19. for (int i = 0; i < 32 - md5code.length(); i++) {
  20. md5code = "0" + md5code;
  21. }
  22. return md5code;
  23. }
  24. }
  25. MD5转换类
  1. /*
  2. * Copyright (C) 2011 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. *      http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.lcw.rabbit.image.utils;
  17. import java.io.BufferedInputStream;
  18. import java.io.BufferedWriter;
  19. import java.io.Closeable;
  20. import java.io.EOFException;
  21. import java.io.File;
  22. import java.io.FileInputStream;
  23. import java.io.FileNotFoundException;
  24. import java.io.FileOutputStream;
  25. import java.io.FileWriter;
  26. import java.io.FilterOutputStream;
  27. import java.io.IOException;
  28. import java.io.InputStream;
  29. import java.io.InputStreamReader;
  30. import java.io.OutputStream;
  31. import java.io.OutputStreamWriter;
  32. import java.io.Reader;
  33. import java.io.StringWriter;
  34. import java.io.Writer;
  35. import java.lang.reflect.Array;
  36. import java.nio.charset.Charset;
  37. import java.util.ArrayList;
  38. import java.util.Arrays;
  39. import java.util.Iterator;
  40. import java.util.LinkedHashMap;
  41. import java.util.Map;
  42. import java.util.concurrent.Callable;
  43. import java.util.concurrent.ExecutorService;
  44. import java.util.concurrent.LinkedBlockingQueue;
  45. import java.util.concurrent.ThreadPoolExecutor;
  46. import java.util.concurrent.TimeUnit;
  47. /**
  48. ******************************************************************************
  49. * Taken from the JB source code, can be found in:
  50. * libcore/luni/src/main/java/libcore/io/DiskLruCache.java
  51. * or direct link:
  52. * https://android.googlesource.com/platform/libcore/+/android-4.1.1_r1/luni/src/main/java/libcore/io/DiskLruCache.java
  53. ******************************************************************************
  54. *
  55. * A cache that uses a bounded amount of space on a filesystem. Each cache
  56. * entry has a string key and a fixed number of values. Values are byte
  57. * sequences, accessible as streams or files. Each value must be between {@code
  58. * 0} and {@code Integer.MAX_VALUE} bytes in length.
  59. *
  60. * <p>The cache stores its data in a directory on the filesystem. This
  61. * directory must be exclusive to the cache; the cache may delete or overwrite
  62. * files from its directory. It is an error for multiple processes to use the
  63. * same cache directory at the same time.
  64. *
  65. * <p>This cache limits the number of bytes that it will store on the
  66. * filesystem. When the number of stored bytes exceeds the limit, the cache will
  67. * remove entries in the background until the limit is satisfied. The limit is
  68. * not strict: the cache may temporarily exceed it while waiting for files to be
  69. * deleted. The limit does not include filesystem overhead or the cache
  70. * journal so space-sensitive applications should set a conservative limit.
  71. *
  72. * <p>Clients call {@link #edit} to create or update the values of an entry. An
  73. * entry may have only one editor at one time; if a value is not available to be
  74. * edited then {@link #edit} will return null.
  75. * <ul>
  76. *     <li>When an entry is being <strong>created</strong> it is necessary to
  77. *         supply a full set of values; the empty value should be used as a
  78. *         placeholder if necessary.
  79. *     <li>When an entry is being <strong>edited</strong>, it is not necessary
  80. *         to supply data for every value; values default to their previous
  81. *         value.
  82. * </ul>
  83. * Every {@link #edit} call must be matched by a call to {@link Editor#commit}
  84. * or {@link Editor#abort}. Committing is atomic: a read observes the full set
  85. * of values as they were before or after the commit, but never a mix of values.
  86. *
  87. * <p>Clients call {@link #get} to read a snapshot of an entry. The read will
  88. * observe the value at the time that {@link #get} was called. Updates and
  89. * removals after the call do not impact ongoing reads.
  90. *
  91. * <p>This class is tolerant of some I/O errors. If files are missing from the
  92. * filesystem, the corresponding entries will be dropped from the cache. If
  93. * an error occurs while writing a cache value, the edit will fail silently.
  94. * Callers should handle other problems by catching {@code IOException} and
  95. * responding appropriately.
  96. */
  97. public final class DiskLruCache implements Closeable {
  98. static final String JOURNAL_FILE = "journal";
  99. static final String JOURNAL_FILE_TMP = "journal.tmp";
  100. static final String MAGIC = "libcore.io.DiskLruCache";
  101. static final String VERSION_1 = "1";
  102. static final long ANY_SEQUENCE_NUMBER = -1;
  103. private static final String CLEAN = "CLEAN";
  104. private static final String DIRTY = "DIRTY";
  105. private static final String REMOVE = "REMOVE";
  106. private static final String READ = "READ";
  107. private static final Charset UTF_8 = Charset.forName("UTF-8");
  108. private static final int IO_BUFFER_SIZE = 8 * 1024;
  109. /*
  110. * This cache uses a journal file named "journal". A typical journal file
  111. * looks like this:
  112. *     libcore.io.DiskLruCache
  113. *     1
  114. *     100
  115. *     2
  116. *
  117. *     CLEAN 3400330d1dfc7f3f7f4b8d4d803dfcf6 832 21054832 21054
  118. *     DIRTY 335c4c6028171cfddfbaae1a9c313c52
  119. *     CLEAN 335c4c6028171cfddfbaae1a9c313c52 3934 2342
  120. *     REMOVE 335c4c6028171cfddfbaae1a9c313c52
  121. *     DIRTY 1ab96a171faeeee38496d8b330771a7a
  122. *     CLEAN 1ab96a171faeeee38496d8b330771a7a 1600 234
  123. *     READ 335c4c6028171cfddfbaae1a9c313c52
  124. *     READ 3400330d1dfc7f3f7f4b8d4d803dfcf6
  125. *
  126. * The first five lines of the journal form its header. They are the
  127. * constant string "libcore.io.DiskLruCache", the disk cache's version,
  128. * the application's version, the value count, and a blank line.
  129. *
  130. * Each of the subsequent lines in the file is a record of the state of a
  131. * cache entry. Each line contains space-separated values: a state, a key,
  132. * and optional state-specific values.
  133. *   o DIRTY lines track that an entry is actively being created or updated.
  134. *     Every successful DIRTY action should be followed by a CLEAN or REMOVE
  135. *     action. DIRTY lines without a matching CLEAN or REMOVE indicate that
  136. *     temporary files may need to be deleted.
  137. *   o CLEAN lines track a cache entry that has been successfully published
  138. *     and may be read. A publish line is followed by the lengths of each of
  139. *     its values.
  140. *   o READ lines track accesses for LRU.
  141. *   o REMOVE lines track entries that have been deleted.
  142. *
  143. * The journal file is appended to as cache operations occur. The journal may
  144. * occasionally be compacted by dropping redundant lines. A temporary file named
  145. * "journal.tmp" will be used during compaction; that file should be deleted if
  146. * it exists when the cache is opened.
  147. */
  148. private final File directory;
  149. private final File journalFile;
  150. private final File journalFileTmp;
  151. private final int appVersion;
  152. private final long maxSize;
  153. private final int valueCount;
  154. private long size = 0;
  155. private Writer journalWriter;
  156. private final LinkedHashMap<String, Entry> lruEntries
  157. = new LinkedHashMap<String, Entry>(0, 0.75f, true);
  158. private int redundantOpCount;
  159. /**
  160. * To differentiate between old and current snapshots, each entry is given
  161. * a sequence number each time an edit is committed. A snapshot is stale if
  162. * its sequence number is not equal to its entry's sequence number.
  163. */
  164. private long nextSequenceNumber = 0;
  165. /* From java.util.Arrays */
  166. @SuppressWarnings("unchecked")
  167. private static <T> T[] copyOfRange(T[] original, int start, int end) {
  168. final int originalLength = original.length; // For exception priority compatibility.
  169. if (start > end) {
  170. throw new IllegalArgumentException();
  171. }
  172. if (start < 0 || start > originalLength) {
  173. throw new ArrayIndexOutOfBoundsException();
  174. }
  175. final int resultLength = end - start;
  176. final int copyLength = Math.min(resultLength, originalLength - start);
  177. final T[] result = (T[]) Array
  178. .newInstance(original.getClass().getComponentType(), resultLength);
  179. System.arraycopy(original, start, result, 0, copyLength);
  180. return result;
  181. }
  182. /**
  183. * Returns the remainder of 'reader' as a string, closing it when done.
  184. */
  185. public static String readFully(Reader reader) throws IOException {
  186. try {
  187. StringWriter writer = new StringWriter();
  188. char[] buffer = new char[1024];
  189. int count;
  190. while ((count = reader.read(buffer)) != -1) {
  191. writer.write(buffer, 0, count);
  192. }
  193. return writer.toString();
  194. } finally {
  195. reader.close();
  196. }
  197. }
  198. /**
  199. * Returns the ASCII characters up to but not including the next "\r\n", or
  200. * "\n".
  201. *
  202. * @throws java.io.EOFException if the stream is exhausted before the next newline
  203. *     character.
  204. */
  205. public static String readAsciiLine(InputStream in) throws IOException {
  206. // TODO: support UTF-8 here instead
  207. StringBuilder result = new StringBuilder(80);
  208. while (true) {
  209. int c = in.read();
  210. if (c == -1) {
  211. throw new EOFException();
  212. } else if (c == '\n') {
  213. break;
  214. }
  215. result.append((char) c);
  216. }
  217. int length = result.length();
  218. if (length > 0 && result.charAt(length - 1) == '\r') {
  219. result.setLength(length - 1);
  220. }
  221. return result.toString();
  222. }
  223. /**
  224. * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
  225. */
  226. public static void closeQuietly(Closeable closeable) {
  227. if (closeable != null) {
  228. try {
  229. closeable.close();
  230. } catch (RuntimeException rethrown) {
  231. throw rethrown;
  232. } catch (Exception ignored) {
  233. }
  234. }
  235. }
  236. /**
  237. * Recursively delete everything in {@code dir}.
  238. */
  239. // TODO: this should specify paths as Strings rather than as Files
  240. public static void deleteContents(File dir) throws IOException {
  241. File[] files = dir.listFiles();
  242. if (files == null) {
  243. throw new IllegalArgumentException("not a directory: " + dir);
  244. }
  245. for (File file : files) {
  246. if (file.isDirectory()) {
  247. deleteContents(file);
  248. }
  249. if (!file.delete()) {
  250. throw new IOException("failed to delete file: " + file);
  251. }
  252. }
  253. }
  254. /** This cache uses a single background thread to evict entries. */
  255. private final ExecutorService executorService = new ThreadPoolExecutor(0, 1,
  256. 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
  257. private final Callable<Void> cleanupCallable = new Callable<Void>() {
  258. @Override public Void call() throws Exception {
  259. synchronized (DiskLruCache.this) {
  260. if (journalWriter == null) {
  261. return null; // closed
  262. }
  263. trimToSize();
  264. if (journalRebuildRequired()) {
  265. rebuildJournal();
  266. redundantOpCount = 0;
  267. }
  268. }
  269. return null;
  270. }
  271. };
  272. private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) {
  273. this.directory = directory;
  274. this.appVersion = appVersion;
  275. this.journalFile = new File(directory, JOURNAL_FILE);
  276. this.journalFileTmp = new File(directory, JOURNAL_FILE_TMP);
  277. this.valueCount = valueCount;
  278. this.maxSize = maxSize;
  279. }
  280. /**
  281. * Opens the cache in {@code directory}, creating a cache if none exists
  282. * there.
  283. *
  284. * @param directory a writable directory
  285. * @param appVersion
  286. * @param valueCount the number of values per cache entry. Must be positive.
  287. * @param maxSize the maximum number of bytes this cache should use to store
  288. * @throws java.io.IOException if reading or writing the cache directory fails
  289. */
  290. public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
  291. throws IOException {
  292. if (maxSize <= 0) {
  293. throw new IllegalArgumentException("maxSize <= 0");
  294. }
  295. if (valueCount <= 0) {
  296. throw new IllegalArgumentException("valueCount <= 0");
  297. }
  298. // prefer to pick up where we left off
  299. DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
  300. if (cache.journalFile.exists()) {
  301. try {
  302. cache.readJournal();
  303. cache.processJournal();
  304. cache.journalWriter = new BufferedWriter(new FileWriter(cache.journalFile, true),
  305. IO_BUFFER_SIZE);
  306. return cache;
  307. } catch (IOException journalIsCorrupt) {
  308. //                System.logW("DiskLruCache " + directory + " is corrupt: "
  309. //                        + journalIsCorrupt.getMessage() + ", removing");
  310. cache.delete();
  311. }
  312. }
  313. // create a new empty cache
  314. directory.mkdirs();
  315. cache = new DiskLruCache(directory, appVersion, valueCount, maxSize);
  316. cache.rebuildJournal();
  317. return cache;
  318. }
  319. private void readJournal() throws IOException {
  320. InputStream in = new BufferedInputStream(new FileInputStream(journalFile), IO_BUFFER_SIZE);
  321. try {
  322. String magic = readAsciiLine(in);
  323. String version = readAsciiLine(in);
  324. String appVersionString = readAsciiLine(in);
  325. String valueCountString = readAsciiLine(in);
  326. String blank = readAsciiLine(in);
  327. if (!MAGIC.equals(magic)
  328. || !VERSION_1.equals(version)
  329. || !Integer.toString(appVersion).equals(appVersionString)
  330. || !Integer.toString(valueCount).equals(valueCountString)
  331. || !"".equals(blank)) {
  332. throw new IOException("unexpected journal header: ["
  333. + magic + ", " + version + ", " + valueCountString + ", " + blank + "]");
  334. }
  335. while (true) {
  336. try {
  337. readJournalLine(readAsciiLine(in));
  338. } catch (EOFException endOfJournal) {
  339. break;
  340. }
  341. }
  342. } finally {
  343. closeQuietly(in);
  344. }
  345. }
  346. private void readJournalLine(String line) throws IOException {
  347. String[] parts = line.split(" ");
  348. if (parts.length < 2) {
  349. throw new IOException("unexpected journal line: " + line);
  350. }
  351. String key = parts[1];
  352. if (parts[0].equals(REMOVE) && parts.length == 2) {
  353. lruEntries.remove(key);
  354. return;
  355. }
  356. Entry entry = lruEntries.get(key);
  357. if (entry == null) {
  358. entry = new Entry(key);
  359. lruEntries.put(key, entry);
  360. }
  361. if (parts[0].equals(CLEAN) && parts.length == 2 + valueCount) {
  362. entry.readable = true;
  363. entry.currentEditor = null;
  364. entry.setLengths(copyOfRange(parts, 2, parts.length));
  365. } else if (parts[0].equals(DIRTY) && parts.length == 2) {
  366. entry.currentEditor = new Editor(entry);
  367. } else if (parts[0].equals(READ) && parts.length == 2) {
  368. // this work was already done by calling lruEntries.get()
  369. } else {
  370. throw new IOException("unexpected journal line: " + line);
  371. }
  372. }
  373. /**
  374. * Computes the initial size and collects garbage as a part of opening the
  375. * cache. Dirty entries are assumed to be inconsistent and will be deleted.
  376. */
  377. private void processJournal() throws IOException {
  378. deleteIfExists(journalFileTmp);
  379. for (Iterator<Entry> i = lruEntries.values().iterator(); i.hasNext(); ) {
  380. Entry entry = i.next();
  381. if (entry.currentEditor == null) {
  382. for (int t = 0; t < valueCount; t++) {
  383. size += entry.lengths[t];
  384. }
  385. } else {
  386. entry.currentEditor = null;
  387. for (int t = 0; t < valueCount; t++) {
  388. deleteIfExists(entry.getCleanFile(t));
  389. deleteIfExists(entry.getDirtyFile(t));
  390. }
  391. i.remove();
  392. }
  393. }
  394. }
  395. /**
  396. * Creates a new journal that omits redundant information. This replaces the
  397. * current journal if it exists.
  398. */
  399. private synchronized void rebuildJournal() throws IOException {
  400. if (journalWriter != null) {
  401. journalWriter.close();
  402. }
  403. Writer writer = new BufferedWriter(new FileWriter(journalFileTmp), IO_BUFFER_SIZE);
  404. writer.write(MAGIC);
  405. writer.write("\n");
  406. writer.write(VERSION_1);
  407. writer.write("\n");
  408. writer.write(Integer.toString(appVersion));
  409. writer.write("\n");
  410. writer.write(Integer.toString(valueCount));
  411. writer.write("\n");
  412. writer.write("\n");
  413. for (Entry entry : lruEntries.values()) {
  414. if (entry.currentEditor != null) {
  415. writer.write(DIRTY + ' ' + entry.key + '\n');
  416. } else {
  417. writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
  418. }
  419. }
  420. writer.close();
  421. journalFileTmp.renameTo(journalFile);
  422. journalWriter = new BufferedWriter(new FileWriter(journalFile, true), IO_BUFFER_SIZE);
  423. }
  424. private static void deleteIfExists(File file) throws IOException {
  425. //        try {
  426. //            Libcore.os.remove(file.getPath());
  427. //        } catch (ErrnoException errnoException) {
  428. //            if (errnoException.errno != OsConstants.ENOENT) {
  429. //                throw errnoException.rethrowAsIOException();
  430. //            }
  431. //        }
  432. if (file.exists() && !file.delete()) {
  433. throw new IOException();
  434. }
  435. }
  436. /**
  437. * Returns a snapshot of the entry named {@code key}, or null if it doesn't
  438. * exist is not currently readable. If a value is returned, it is moved to
  439. * the head of the LRU queue.
  440. */
  441. public synchronized Snapshot get(String key) throws IOException {
  442. checkNotClosed();
  443. validateKey(key);
  444. Entry entry = lruEntries.get(key);
  445. if (entry == null) {
  446. return null;
  447. }
  448. if (!entry.readable) {
  449. return null;
  450. }
  451. /*
  452. * Open all streams eagerly to guarantee that we see a single published
  453. * snapshot. If we opened streams lazily then the streams could come
  454. * from different edits.
  455. */
  456. InputStream[] ins = new InputStream[valueCount];
  457. try {
  458. for (int i = 0; i < valueCount; i++) {
  459. ins[i] = new FileInputStream(entry.getCleanFile(i));
  460. }
  461. } catch (FileNotFoundException e) {
  462. // a file must have been deleted manually!
  463. return null;
  464. }
  465. redundantOpCount++;
  466. journalWriter.append(READ + ' ' + key + '\n');
  467. if (journalRebuildRequired()) {
  468. executorService.submit(cleanupCallable);
  469. }
  470. return new Snapshot(key, entry.sequenceNumber, ins);
  471. }
  472. /**
  473. * Returns an editor for the entry named {@code key}, or null if another
  474. * edit is in progress.
  475. */
  476. public Editor edit(String key) throws IOException {
  477. return edit(key, ANY_SEQUENCE_NUMBER);
  478. }
  479. private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException {
  480. checkNotClosed();
  481. validateKey(key);
  482. Entry entry = lruEntries.get(key);
  483. if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER
  484. && (entry == null || entry.sequenceNumber != expectedSequenceNumber)) {
  485. return null; // snapshot is stale
  486. }
  487. if (entry == null) {
  488. entry = new Entry(key);
  489. lruEntries.put(key, entry);
  490. } else if (entry.currentEditor != null) {
  491. return null; // another edit is in progress
  492. }
  493. Editor editor = new Editor(entry);
  494. entry.currentEditor = editor;
  495. // flush the journal before creating files to prevent file leaks
  496. journalWriter.write(DIRTY + ' ' + key + '\n');
  497. journalWriter.flush();
  498. return editor;
  499. }
  500. /**
  501. * Returns the directory where this cache stores its data.
  502. */
  503. public File getDirectory() {
  504. return directory;
  505. }
  506. /**
  507. * Returns the maximum number of bytes that this cache should use to store
  508. * its data.
  509. */
  510. public long maxSize() {
  511. return maxSize;
  512. }
  513. /**
  514. * Returns the number of bytes currently being used to store the values in
  515. * this cache. This may be greater than the max size if a background
  516. * deletion is pending.
  517. */
  518. public synchronized long size() {
  519. return size;
  520. }
  521. private synchronized void completeEdit(Editor editor, boolean success) throws IOException {
  522. Entry entry = editor.entry;
  523. if (entry.currentEditor != editor) {
  524. throw new IllegalStateException();
  525. }
  526. // if this edit is creating the entry for the first time, every index must have a value
  527. if (success && !entry.readable) {
  528. for (int i = 0; i < valueCount; i++) {
  529. if (!entry.getDirtyFile(i).exists()) {
  530. editor.abort();
  531. throw new IllegalStateException("edit didn't create file " + i);
  532. }
  533. }
  534. }
  535. for (int i = 0; i < valueCount; i++) {
  536. File dirty = entry.getDirtyFile(i);
  537. if (success) {
  538. if (dirty.exists()) {
  539. File clean = entry.getCleanFile(i);
  540. dirty.renameTo(clean);
  541. long oldLength = entry.lengths[i];
  542. long newLength = clean.length();
  543. entry.lengths[i] = newLength;
  544. size = size - oldLength + newLength;
  545. }
  546. } else {
  547. deleteIfExists(dirty);
  548. }
  549. }
  550. redundantOpCount++;
  551. entry.currentEditor = null;
  552. if (entry.readable | success) {
  553. entry.readable = true;
  554. journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n');
  555. if (success) {
  556. entry.sequenceNumber = nextSequenceNumber++;
  557. }
  558. } else {
  559. lruEntries.remove(entry.key);
  560. journalWriter.write(REMOVE + ' ' + entry.key + '\n');
  561. }
  562. if (size > maxSize || journalRebuildRequired()) {
  563. executorService.submit(cleanupCallable);
  564. }
  565. }
  566. /**
  567. * We only rebuild the journal when it will halve the size of the journal
  568. * and eliminate at least 2000 ops.
  569. */
  570. private boolean journalRebuildRequired() {
  571. final int REDUNDANT_OP_COMPACT_THRESHOLD = 2000;
  572. return redundantOpCount >= REDUNDANT_OP_COMPACT_THRESHOLD
  573. && redundantOpCount >= lruEntries.size();
  574. }
  575. /**
  576. * Drops the entry for {@code key} if it exists and can be removed. Entries
  577. * actively being edited cannot be removed.
  578. *
  579. * @return true if an entry was removed.
  580. */
  581. public synchronized boolean remove(String key) throws IOException {
  582. checkNotClosed();
  583. validateKey(key);
  584. Entry entry = lruEntries.get(key);
  585. if (entry == null || entry.currentEditor != null) {
  586. return false;
  587. }
  588. for (int i = 0; i < valueCount; i++) {
  589. File file = entry.getCleanFile(i);
  590. if (!file.delete()) {
  591. throw new IOException("failed to delete " + file);
  592. }
  593. size -= entry.lengths[i];
  594. entry.lengths[i] = 0;
  595. }
  596. redundantOpCount++;
  597. journalWriter.append(REMOVE + ' ' + key + '\n');
  598. lruEntries.remove(key);
  599. if (journalRebuildRequired()) {
  600. executorService.submit(cleanupCallable);
  601. }
  602. return true;
  603. }
  604. /**
  605. * Returns true if this cache has been closed.
  606. */
  607. public boolean isClosed() {
  608. return journalWriter == null;
  609. }
  610. private void checkNotClosed() {
  611. if (journalWriter == null) {
  612. throw new IllegalStateException("cache is closed");
  613. }
  614. }
  615. /**
  616. * Force buffered operations to the filesystem.
  617. */
  618. public synchronized void flush() throws IOException {
  619. checkNotClosed();
  620. trimToSize();
  621. journalWriter.flush();
  622. }
  623. /**
  624. * Closes this cache. Stored values will remain on the filesystem.
  625. */
  626. public synchronized void close() throws IOException {
  627. if (journalWriter == null) {
  628. return; // already closed
  629. }
  630. for (Entry entry : new ArrayList<Entry>(lruEntries.values())) {
  631. if (entry.currentEditor != null) {
  632. entry.currentEditor.abort();
  633. }
  634. }
  635. trimToSize();
  636. journalWriter.close();
  637. journalWriter = null;
  638. }
  639. private void trimToSize() throws IOException {
  640. while (size > maxSize) {
  641. //            Map.Entry<String, Entry> toEvict = lruEntries.eldest();
  642. final Map.Entry<String, Entry> toEvict = lruEntries.entrySet().iterator().next();
  643. remove(toEvict.getKey());
  644. }
  645. }
  646. /**
  647. * Closes the cache and deletes all of its stored values. This will delete
  648. * all files in the cache directory including files that weren't created by
  649. * the cache.
  650. */
  651. public void delete() throws IOException {
  652. close();
  653. deleteContents(directory);
  654. }
  655. private void validateKey(String key) {
  656. if (key.contains(" ") || key.contains("\n") || key.contains("\r")) {
  657. throw new IllegalArgumentException(
  658. "keys must not contain spaces or newlines: \"" + key + "\"");
  659. }
  660. }
  661. private static String inputStreamToString(InputStream in) throws IOException {
  662. return readFully(new InputStreamReader(in, UTF_8));
  663. }
  664. /**
  665. * A snapshot of the values for an entry.
  666. */
  667. public final class Snapshot implements Closeable {
  668. private final String key;
  669. private final long sequenceNumber;
  670. private final InputStream[] ins;
  671. private Snapshot(String key, long sequenceNumber, InputStream[] ins) {
  672. this.key = key;
  673. this.sequenceNumber = sequenceNumber;
  674. this.ins = ins;
  675. }
  676. /**
  677. * Returns an editor for this snapshot's entry, or null if either the
  678. * entry has changed since this snapshot was created or if another edit
  679. * is in progress.
  680. */
  681. public Editor edit() throws IOException {
  682. return DiskLruCache.this.edit(key, sequenceNumber);
  683. }
  684. /**
  685. * Returns the unbuffered stream with the value for {@code index}.
  686. */
  687. public InputStream getInputStream(int index) {
  688. return ins[index];
  689. }
  690. /**
  691. * Returns the string value for {@code index}.
  692. */
  693. public String getString(int index) throws IOException {
  694. return inputStreamToString(getInputStream(index));
  695. }
  696. @Override public void close() {
  697. for (InputStream in : ins) {
  698. closeQuietly(in);
  699. }
  700. }
  701. }
  702. /**
  703. * Edits the values for an entry.
  704. */
  705. public final class Editor {
  706. private final Entry entry;
  707. private boolean hasErrors;
  708. private Editor(Entry entry) {
  709. this.entry = entry;
  710. }
  711. /**
  712. * Returns an unbuffered input stream to read the last committed value,
  713. * or null if no value has been committed.
  714. */
  715. public InputStream newInputStream(int index) throws IOException {
  716. synchronized (DiskLruCache.this) {
  717. if (entry.currentEditor != this) {
  718. throw new IllegalStateException();
  719. }
  720. if (!entry.readable) {
  721. return null;
  722. }
  723. return new FileInputStream(entry.getCleanFile(index));
  724. }
  725. }
  726. /**
  727. * Returns the last committed value as a string, or null if no value
  728. * has been committed.
  729. */
  730. public String getString(int index) throws IOException {
  731. InputStream in = newInputStream(index);
  732. return in != null ? inputStreamToString(in) : null;
  733. }
  734. /**
  735. * Returns a new unbuffered output stream to write the value at
  736. * {@code index}. If the underlying output stream encounters errors
  737. * when writing to the filesystem, this edit will be aborted when
  738. * {@link #commit} is called. The returned output stream does not throw
  739. * IOExceptions.
  740. */
  741. public OutputStream newOutputStream(int index) throws IOException {
  742. synchronized (DiskLruCache.this) {
  743. if (entry.currentEditor != this) {
  744. throw new IllegalStateException();
  745. }
  746. return new FaultHidingOutputStream(new FileOutputStream(entry.getDirtyFile(index)));
  747. }
  748. }
  749. /**
  750. * Sets the value at {@code index} to {@code value}.
  751. */
  752. public void set(int index, String value) throws IOException {
  753. Writer writer = null;
  754. try {
  755. writer = new OutputStreamWriter(newOutputStream(index), UTF_8);
  756. writer.write(value);
  757. } finally {
  758. closeQuietly(writer);
  759. }
  760. }
  761. /**
  762. * Commits this edit so it is visible to readers.  This releases the
  763. * edit lock so another edit may be started on the same key.
  764. */
  765. public void commit() throws IOException {
  766. if (hasErrors) {
  767. completeEdit(this, false);
  768. remove(entry.key); // the previous entry is stale
  769. } else {
  770. completeEdit(this, true);
  771. }
  772. }
  773. /**
  774. * Aborts this edit. This releases the edit lock so another edit may be
  775. * started on the same key.
  776. */
  777. public void abort() throws IOException {
  778. completeEdit(this, false);
  779. }
  780. private class FaultHidingOutputStream extends FilterOutputStream {
  781. private FaultHidingOutputStream(OutputStream out) {
  782. super(out);
  783. }
  784. @Override public void write(int oneByte) {
  785. try {
  786. out.write(oneByte);
  787. } catch (IOException e) {
  788. hasErrors = true;
  789. }
  790. }
  791. @Override public void write(byte[] buffer, int offset, int length) {
  792. try {
  793. out.write(buffer, offset, length);
  794. } catch (IOException e) {
  795. hasErrors = true;
  796. }
  797. }
  798. @Override public void close() {
  799. try {
  800. out.close();
  801. } catch (IOException e) {
  802. hasErrors = true;
  803. }
  804. }
  805. @Override public void flush() {
  806. try {
  807. out.flush();
  808. } catch (IOException e) {
  809. hasErrors = true;
  810. }
  811. }
  812. }
  813. }
  814. private final class Entry {
  815. private final String key;
  816. /** Lengths of this entry's files. */
  817. private final long[] lengths;
  818. /** True if this entry has ever been published */
  819. private boolean readable;
  820. /** The ongoing edit or null if this entry is not being edited. */
  821. private Editor currentEditor;
  822. /** The sequence number of the most recently committed edit to this entry. */
  823. private long sequenceNumber;
  824. private Entry(String key) {
  825. this.key = key;
  826. this.lengths = new long[valueCount];
  827. }
  828. public String getLengths() throws IOException {
  829. StringBuilder result = new StringBuilder();
  830. for (long size : lengths) {
  831. result.append(' ').append(size);
  832. }
  833. return result.toString();
  834. }
  835. /**
  836. * Set lengths using decimal numbers like "10123".
  837. */
  838. private void setLengths(String[] strings) throws IOException {
  839. if (strings.length != valueCount) {
  840. throw invalidLengths(strings);
  841. }
  842. try {
  843. for (int i = 0; i < strings.length; i++) {
  844. lengths[i] = Long.parseLong(strings[i]);
  845. }
  846. } catch (NumberFormatException e) {
  847. throw invalidLengths(strings);
  848. }
  849. }
  850. private IOException invalidLengths(String[] strings) throws IOException {
  851. throw new IOException("unexpected journal line: " + Arrays.toString(strings));
  852. }
  853. public File getCleanFile(int i) {
  854. return new File(directory, key + "." + i);
  855. }
  856. public File getDirtyFile(int i) {
  857. return new File(directory, key + "." + i + ".tmp");
  858. }
  859. }
  860. }
  861. DiskLruCache磁盘缓存类

4、图片缓存类,包含(LruCache内存缓存,DiskLruCache磁盘缓存)

  1. package com.lcw.rabbit.image.utils;
  2. import java.io.File;
  3. import java.io.IOException;
  4. import java.io.OutputStream;
  5. import android.content.Context;
  6. import android.content.pm.PackageInfo;
  7. import android.content.pm.PackageManager.NameNotFoundException;
  8. import android.graphics.Bitmap;
  9. import android.graphics.Bitmap.CompressFormat;
  10. import android.graphics.BitmapFactory;
  11. import android.os.Environment;
  12. import android.support.v4.util.LruCache;
  13. import android.util.Log;
  14. import com.android.volley.toolbox.ImageLoader.ImageCache;
  15. import com.lcw.rabbit.image.utils.DiskLruCache.Snapshot;
  16. /**
  17. * 图片缓存帮助类
  18. *
  19. * 包含内存缓存LruCache和磁盘缓存DiskLruCache
  20. *
  21. * @author Rabbit_Lee
  22. *
  23. */
  24. public class ImageCacheUtil implements ImageCache {
  25. private String TAG=ImageCacheUtil.this.getClass().getSimpleName();
  26. //缓存类
  27. private static LruCache<String, Bitmap> mLruCache;
  28. private static DiskLruCache mDiskLruCache;
  29. //磁盘缓存大小
  30. private static final int DISKMAXSIZE = 10 * 1024 * 1024;
  31. public ImageCacheUtil() {
  32. // 获取应用可占内存的1/8作为缓存
  33. int maxSize = (int) (Runtime.getRuntime().maxMemory() / 8);
  34. // 实例化LruCaceh对象
  35. mLruCache = new LruCache<String, Bitmap>(maxSize) {
  36. @Override
  37. protected int sizeOf(String key, Bitmap bitmap) {
  38. return bitmap.getRowBytes() * bitmap.getHeight();
  39. }
  40. };
  41. try {
  42. // 获取DiskLruCahce对象
  43. mDiskLruCache = DiskLruCache.open(getDiskCacheDir(MyApplication.newInstance(), "Rabbit"), getAppVersion(MyApplication.newInstance()), 1, DISKMAXSIZE);
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. }
  48. /**
  49. * 从缓存(内存缓存,磁盘缓存)中获取Bitmap
  50. */
  51. @Override
  52. public Bitmap getBitmap(String url) {
  53. if (mLruCache.get(url) != null) {
  54. // 从LruCache缓存中取
  55. Log.i(TAG,"从LruCahce获取");
  56. return mLruCache.get(url);
  57. } else {
  58. String key = MD5Utils.md5(url);
  59. try {
  60. if (mDiskLruCache.get(key) != null) {
  61. // 从DiskLruCahce取
  62. Snapshot snapshot = mDiskLruCache.get(key);
  63. Bitmap bitmap = null;
  64. if (snapshot != null) {
  65. bitmap = BitmapFactory.decodeStream(snapshot.getInputStream(0));
  66. // 存入LruCache缓存
  67. mLruCache.put(url, bitmap);
  68. Log.i(TAG,"从DiskLruCahce获取");
  69. }
  70. return bitmap;
  71. }
  72. } catch (IOException e) {
  73. e.printStackTrace();
  74. }
  75. }
  76. return null;
  77. }
  78. /**
  79. * 存入缓存(内存缓存,磁盘缓存)
  80. */
  81. @Override
  82. public void putBitmap(String url, Bitmap bitmap) {
  83. // 存入LruCache缓存
  84. mLruCache.put(url, bitmap);
  85. // 判断是否存在DiskLruCache缓存,若没有存入
  86. String key = MD5Utils.md5(url);
  87. try {
  88. if (mDiskLruCache.get(key) == null) {
  89. DiskLruCache.Editor editor = mDiskLruCache.edit(key);
  90. if (editor != null) {
  91. OutputStream outputStream = editor.newOutputStream(0);
  92. if (bitmap.compress(CompressFormat.JPEG, 100, outputStream)) {
  93. editor.commit();
  94. } else {
  95. editor.abort();
  96. }
  97. }
  98. mDiskLruCache.flush();
  99. }
  100. } catch (IOException e) {
  101. e.printStackTrace();
  102. }
  103. }
  104. /**
  105. * 该方法会判断当前sd卡是否存在,然后选择缓存地址
  106. *
  107. * @param context
  108. * @param uniqueName
  109. * @return
  110. */
  111. public static File getDiskCacheDir(Context context, String uniqueName) {
  112. String cachePath;
  113. if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !Environment.isExternalStorageRemovable()) {
  114. cachePath = context.getExternalCacheDir().getPath();
  115. } else {
  116. cachePath = context.getCacheDir().getPath();
  117. }
  118. return new File(cachePath + File.separator + uniqueName);
  119. }
  120. /**
  121. * 获取应用版本号
  122. *
  123. * @param context
  124. * @return
  125. */
  126. public int getAppVersion(Context context) {
  127. try {
  128. PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);
  129. return info.versionCode;
  130. } catch (NameNotFoundException e) {
  131. e.printStackTrace();
  132. }
  133. return 1;
  134. }
  135. }

5、图片缓存管理类

这里的图片加载运用到了Volley自带的ImageLoader,在它的构造方法中需要一个ImageCache对象,在上面的图片缓存类已实现了该接口。

这里向外部提供了一个loadImage的重载方法,一个传入加载图片的宽高,一个默认加载原图,使得外部不再需要关注任何关于缓存的操作。

  1. package com.lcw.rabbit.image;
  2. import android.graphics.Bitmap;
  3. import android.widget.ImageView;
  4. import com.android.volley.VolleyError;
  5. import com.android.volley.toolbox.ImageLoader;
  6. import com.android.volley.toolbox.ImageLoader.ImageCache;
  7. import com.android.volley.toolbox.ImageLoader.ImageContainer;
  8. import com.android.volley.toolbox.ImageLoader.ImageListener;
  9. import com.lcw.rabbit.image.utils.ImageCacheUtil;
  10. /**
  11. * 图片缓存管理类 获取ImageLoader对象
  12. *
  13. * @author Rabbit_Lee
  14. *
  15. */
  16. public class ImageCacheManager {
  17. private static String TAG = ImageCacheManager.class.getSimpleName();
  18. // 获取图片缓存类对象
  19. private static ImageCache mImageCache = new ImageCacheUtil();
  20. // 获取ImageLoader对象
  21. public static ImageLoader mImageLoader = new ImageLoader(VolleyRequestQueueManager.mRequestQueue, mImageCache);
  22. /**
  23. * 获取ImageListener
  24. *
  25. * @param view
  26. * @param defaultImage
  27. * @param errorImage
  28. * @return
  29. */
  30. public static ImageListener getImageListener(final ImageView view, final Bitmap defaultImage, final Bitmap errorImage) {
  31. return new ImageListener() {
  32. @Override
  33. public void onErrorResponse(VolleyError error) {
  34. // 回调失败
  35. if (errorImage != null) {
  36. view.setImageBitmap(errorImage);
  37. }
  38. }
  39. @Override
  40. public void onResponse(ImageContainer response, boolean isImmediate) {
  41. // 回调成功
  42. if (response.getBitmap() != null) {
  43. view.setImageBitmap(response.getBitmap());
  44. } else if (defaultImage != null) {
  45. view.setImageBitmap(defaultImage);
  46. }
  47. }
  48. };
  49. }
  50. /**
  51. * 提供给外部调用方法
  52. *
  53. * @param url
  54. * @param view
  55. * @param defaultImage
  56. * @param errorImage
  57. */
  58. public static void loadImage(String url, ImageView view, Bitmap defaultImage, Bitmap errorImage) {
  59. mImageLoader.get(url, ImageCacheManager.getImageListener(view, defaultImage, errorImage), 0, 0);
  60. }
  61. /**
  62. * 提供给外部调用方法
  63. *
  64. * @param url
  65. * @param view
  66. * @param defaultImage
  67. * @param errorImage
  68. */
  69. public static void loadImage(String url, ImageView view, Bitmap defaultImage, Bitmap errorImage, int maxWidth, int maxHeight) {
  70. mImageLoader.get(url, ImageCacheManager.getImageListener(view, defaultImage, errorImage), maxWidth, maxHeight);
  71. }
  72. }

6、MainActivity类

  1. package com.lcw.rabbit.image;
  2. import android.app.Activity;
  3. import android.content.res.Resources;
  4. import android.graphics.Bitmap;
  5. import android.graphics.BitmapFactory;
  6. import android.os.Bundle;
  7. import android.view.View;
  8. import android.view.View.OnClickListener;
  9. import android.widget.Button;
  10. import android.widget.ImageView;
  11. public class MainActivity extends Activity {
  12. private Button mButton;
  13. private ImageView mImageView;
  14. @Override
  15. protected void onCreate(Bundle savedInstanceState) {
  16. super.onCreate(savedInstanceState);
  17. setContentView(R.layout.activity_main);
  18. mButton = (Button) findViewById(R.id.button);
  19. mImageView= (ImageView) findViewById(R.id.image);
  20. mButton.setOnClickListener(new OnClickListener() {
  21. @Override
  22. public void onClick(View v) {
  23. String url = "http://img.blog.csdn.net/20130702124537953?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdDEyeDM0NTY=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast";
  24. ImageCacheManager.loadImage(url, mImageView, getBitmapFromRes(R.drawable.ic_launcher), getBitmapFromRes(R.drawable.ic_launcher));
  25. }
  26. });
  27. }
  28. public Bitmap getBitmapFromRes(int resId) {
  29. Resources res = this.getResources();
  30. return BitmapFactory.decodeResource(res, resId);
  31. }
  32. }

作者:李晨玮
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

网络图片的获取以及二级缓存策略(Volley框架+内存LruCache+磁盘DiskLruCache)的更多相关文章

  1. 安卓开发笔记——关于图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

    在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量.对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理 ...

  2. Android Volley框架的使用(四)图片的三级缓存策略(内存LruCache+磁盘DiskLruCache+网络Volley)

    在开发安卓应用中避免不了要使用到网络图片,获取网络图片很简单,但是需要付出一定的代价——流量.对于少数的图片而言问题不大,但如果手机应用中包含大量的图片,这势必会耗费用户的一定流量,如果我们不加以处理 ...

  3. Hibernate的二级缓存策略

    Hibernate的二级缓存策略的一般过程如下: 1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次 ...

  4. Java获取Linux和Window系统CPU、内存和磁盘总使用率的情况

    这是一个工具类,获取的内容: CPU使用率:得到的是当前CPU的使用情况,这是算出的是两次500毫秒时间差的CPU使用率 内存使用率:[1 -  剩余的物理内存/(总的物理内存+虚拟内存) ] * 1 ...

  5. 【第五篇】Volley代码修改之图片二级缓存以及相关源码阅读(重写ImageLoader.ImageCache)

    前面http://www.cnblogs.com/androidsuperman/p/8a157b18ede85caa61ca5bc04bba43d0.html 有讲到使用LRU来处理缓存的,但是只是 ...

  6. 【Java EE 学习 48】【Hibernate学习第五天】【抓取策略】【二级缓存】【HQL】

    一.抓取策略. 1.hibernate中提供了三种抓取策略. (1)连接抓取(Join Fetch):这种抓取方式是默认的抓取方式.使用这种抓取方式hibernate会在select中内连接的方式获取 ...

  7. Hibernate一级缓存与二级缓存的区别

    一级缓存: 就是Session级别的缓存.一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中. 如果短时间内这个session(一定要同一个session)又做了同一个操作,那么h ...

  8. 01-08-05【Nhibernate (版本3.3.1.4000) 出入江湖】NHibernate二级缓存:第三方MemCache缓存

    一.准备工作 [1]根据操作系统(位数)选择下载相应版本的MemCache, MemCache的下载和安装,参看: http://www.cnblogs.com/easy5weikai/p/37606 ...

  9. 01-08-02【Nhibernate (版本3.3.1.4000) 出入江湖】二级缓存:NHibernate自带的HashtableProvider

    第一步骤:hibernate.cfg.xml文件补上如下配置: <?xml version="1.0" encoding="utf-8"?> < ...

随机推荐

  1. POJO和JavaBean

    1.POJO POJO(Plain Ordinary Java Object):POJO就是一个简单的普通的Java对象,它不包含业务逻辑或持久逻辑等,但不是JavaBean.EntityBean等, ...

  2. 两行代码玩转SUMO!

    两行代码玩转SUMO! 这篇博客很简单,但是内容很丰富 如何生成如下所示的研究型路网结构? 只需要打开ubuntu终端输入如下代码即可,grid.number代表路口数量,grid.length代表路 ...

  3. (三)Kinect姿势识别

    Kinect给我们内置了许多姿势如举手等,具体可参考枚举KinectGestures.Gestures,也可以通过Kinect姿势管理器,自定义姿势导入(坑较多,内置的基本够用了)也可以根据关节坐标自 ...

  4. fiddler的过滤

    1.User Fiters启用 2.Action Action:Run Filterset now是否运行,Load Filterset加载,Save Filterset保存: 3.Hosts过滤 Z ...

  5. Flask:Flask-script插件

    Flask-Script插件扩展提供向Flask插入外部脚本的功能,包括一个开发用的服务器,一个定制的python shell,设置数据库的脚本,cronjobs,及其它运行在web应用之外的命令行任 ...

  6. MIT线性代数:11.矩阵空间、秩1矩阵和小世界图

  7. 修改tomcat默认使用的jdk版本

    1.windows平台 在csetclasspath.bat文件开头加上如下两句(指定JDK): set JAVA_HOME=C:\Program Files\Java\jdk1.7.0_79  se ...

  8. 手写一个简易的IOC

    这个小项目是我读过一点Spring的源码后,模仿Spring的IOC写的一个简易的IOC,当然Spring的在天上,我写的在马里亚纳海沟,哈哈 感兴趣的小伙伴可以去我的github拉取代码看着玩 地址 ...

  9. permu 莫队 总结

    由于每次询问静态区间里完整值域段的最大大小 貌似很好用莫队转移,所以考虑怎么转移 当给它扩展一个数时,就是给值域添加了一个值 这个值可能已经存在,也可能是新的 有的神仙做法是维护了一个并查集,然而我这 ...

  10. NOIP模拟 18

    这次时间分配不合理,沉迷大模拟无法自拔 虽然A了但是根本就没给T3留时间555 T3如果有时间看看数据范围应该可以想到QJ掉20分的555 T1 引子 打这题的时候感觉自己在做图像处理.. 然后调了各 ...