盒子
盒子
文章目录
  1. 缓存的作用
  2. 缓存的常用场景
  3. 缓存的类型
  4. 内存缓存
    1. 内存缓存的实现方式
    2. 内存缓存策略
    3. 自己实现内存缓存
  5. 小结

App开发中的内存缓存

缓存对于老司机来说应该不陌生,对于初学者来说接触的应该不多。

缓存的作用

  • 优化加载速度
  • 减少频繁的网络请求,减少并发,减少服务器压力
  • 节省用户流量

缓存的常用场景

  1. 图片请求框架应该是最常见的了,UILPicassoGlide,Fresco等基本上都是2级缓存(磁盘,内存)
  2. 接口数据量大,且更新不是特别频繁的数据,可以进行缓存。例如:电商App主页
  3. 当然也可以做永久缓存,代替SharedPreference存储相关数据

    缓存的类型

Android中常见的缓存类型基本上就2种

  • 内存缓存
  • 磁盘缓存

内存缓存

本篇我们先讨论内存缓存MemoryCahce

内存缓存的实现方式

内存缓存的实现很简单,本质就是存在一个变量,变量处于内存中,所以我们管它叫内存缓存,常见实现方式为Map存储变量,Android 的SDK目前已经内置了内存缓存LruCache,在V4包中也有这样一个类,区别是V4包中多了一个方法,其他一样。查看这个类的源码会发现内存存储变量的容器就是一个Map

内存缓存策略

缓存的容量会有一个最大值限制,不可能无限缓存变量,当内存满的时候,我们需要移出一些旧的数据,怎么判定移除哪一个数据,这就需要一个算法来计算,我们称之为策略,策略有多种

  • FIFO FirstInFirstOut,先进先出算法,当内存满时,移除最先存入队列entry
  • Largest,最大使用算法,当内存满时,移除占空间最大的entry
  • LFU LeastFrequentlyUsed,最低使用频率算法,当内存满时,移除使用频率最低的entry
  • LRU LeastRecentlyUsed,近期最少使用算法,当内存满时,移除最近使用频率最低的entry(LFU基础上更加完善)

Android SDK中的LruCache看名字就知道是实现了LRU策略

自己实现内存缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
public abstract class PolicyMemoryCache<K, V> implements MemoryCache<K, V> {
public static final int MAX_NORMAL_CACHE_SIZE_IN_MB = 16;
public static final int SIZE_UNIT_MB = 1024 * 1024;
public static final int MAX_NORMAL_CACHE_SIZE = MAX_NORMAL_CACHE_SIZE_IN_MB * SIZE_UNIT_MB;
private final int sizeLimit;
public static final String TAG = "memory cache";
private final AtomicInteger cacheSize;
protected final Map<K, V> mMap = Collections.synchronizedMap(new LinkedHashMap<K, V>());
public PolicyMemoryCache(int sizeLimit) {
this.sizeLimit = sizeLimit;
cacheSize = new AtomicInteger();
if (sizeLimit > MAX_NORMAL_CACHE_SIZE) {
Log.w(TAG, "You set too large memory cache size (more than %1$d Mb)" + MAX_NORMAL_CACHE_SIZE_IN_MB);
}
}
@Override
public boolean put(K key, V value) {
boolean putSuccessfully = false;
// Try to add value to hard cache
int valueSize = safeSizeOf(key, value);
int sizeLimit = getSizeLimit();
int curCacheSize = cacheSize.get();
if (valueSize < sizeLimit) {
while (curCacheSize + valueSize > sizeLimit) {
K removeKey = removeNext();
if (removeKey == null) {
break;
}
V v = mMap.remove(removeKey);
if (v != null) {
curCacheSize = cacheSize.addAndGet(-safeSizeOf(removeKey, v)); //cacheSize--
}
}
mMap.put(key, value);
cacheSize.addAndGet(valueSize);
putSuccessfully = true;
}
return putSuccessfully;
}
@Override
public V remove(K key) {
V v = mMap.remove(key);
if (v != null) {
cacheSize.addAndGet(-safeSizeOf(key, v)); //cacheSize--
}
return v;
}
@Override
public V get(K key) {
return mMap.get(key);
}
@Override
public void clear() {
mMap.clear();
cacheSize.set(0);
}
protected int getSizeLimit() {
return sizeLimit;
}
@Override
public Collection<K> keys() {
synchronized (mMap) {
return new HashSet<K>(mMap.keySet());
}
}
protected int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
@Override
public void print(){
/*Iterator<Map.Entry<K, V>> iterator = mMap.entrySet().iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next().getValue());
}*/
System.out.println("mMap:"+mMap);
}
protected int sizeOf(K key, V value) {
int size = 1;
if (value instanceof String) {
size = ((String) value).getBytes().length;
}else if (value instanceof Bitmap) {
Bitmap bitmapValue = (Bitmap) value;
//size = bitmapValue.getRowBytes() * bitmapValue.getHeight();
size = (int) sizeOf(bitmapValue);
}else if (value instanceof Serializable) {
Serializable serializableValue =(Serializable) value;
size = (int) sizeOf(serializableValue);
}else if (value instanceof Iterable) {
for (Object item : ((Iterable) value)) {
size += sizeOf(key, (V) item);
}
}
return size;
}
public static long sizeOf(Serializable serial){
if (serial == null) {
return 0;
}
long size = -1;
ByteArrayOutputStream baos = null;
ObjectOutputStream oos = null;
try {
baos = new ByteArrayOutputStream();
oos = new ObjectOutputStream(baos);
oos.writeObject(serial);
oos.flush();
size = baos.size();
} catch (FileNotFoundException e) {
e.printStackTrace();
throw new SizeCalculateException(e.getMessage());
} catch (NotSerializableException e) {
e.printStackTrace();
throw new SizeCalculateException("Cache object does not implement serializable interface");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
oos.close();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return size;
}
public static long sizeOf(Bitmap bitmap) {
if (bitmap == null) {
return 0;
}
long size = -1;
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
size = baos.size();
} finally {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return size;
}
protected abstract K removeNext();
}

这是一个基类,实现了变量的存储,取出,变量大小计算,抽象了removeNext函数返回需要移除的entry的Key,由子类实现策略

下面看看Largest策略的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class LargestMemoryCache<K, V> extends PolicyMemoryCache<K, V> {
private final Map<K, Integer> valueSizes = Collections.synchronizedMap(new HashMap<K, Integer>());
public LargestMemoryCache(int sizeLimit) {
super(sizeLimit);
}
@Override
public boolean put(K key, V value) {
if (super.put(key, value)) {
valueSizes.put(key, safeSizeOf(key, value));
return true;
} else {
return false;
}
}
@Override
public V remove(K key) {
valueSizes.remove(key);
return super.remove(key);
}
@Override
public void clear() {
valueSizes.clear();
super.clear();
}
@Override
protected K removeNext() {
Integer maxSize = null;
K largestKey = null;
Set<Entry<K, Integer>> entries = valueSizes.entrySet();
synchronized (valueSizes) {
for (Entry<K, Integer> entry : entries) {
if (largestKey == null) {
maxSize = entry.getValue();
largestKey = entry.getKey();
} else {
Integer size = entry.getValue();
if (size > maxSize) {
maxSize = size;
largestKey = entry.getKey();
}
}
}
}
valueSizes.remove(largestKey);
return largestKey;
}
}

在put变量的时候使用Map存储了每个变量的大小,在removeNext中计算出当前存储变量中size最大的一个Entry的key。

再看看LFU的策略实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public class LFUMemoryCache<K, V> extends PolicyMemoryCache<K, V> {
private final Map<K, Integer> usingCounts = Collections.synchronizedMap(new HashMap<K, Integer>());
public LFUMemoryCache(int sizeLimit) {
super(sizeLimit);
}
@Override
public boolean put(K key, V value) {
if (super.put(key, value)) {
usingCounts.put(key, 0);
return true;
} else {
return false;
}
}
@Override
public V get(K key) {
V value = super.get(key);
// Increment usage count for value if value is contained in hardCache
if (value != null) {
Integer usageCount = usingCounts.get(key);
if (usageCount != null) {
usingCounts.put(key, usageCount + 1);
}
}
return value;
}
@Override
public V remove(K key) {
usingCounts.remove(key);
return super.remove(key);
}
@Override
public void clear() {
usingCounts.clear();
super.clear();
}
@Override
protected K removeNext() {
Integer minUsageCount = null;
K leastUsedKey = null;
Set<Entry<K, Integer>> entries = usingCounts.entrySet();
synchronized (usingCounts) {
for (Entry<K, Integer> entry : entries) {
if (leastUsedKey == null) {
leastUsedKey = entry.getKey();
minUsageCount = entry.getValue();
} else {
Integer lastValueUsage = entry.getValue();
if (lastValueUsage < minUsageCount) {
minUsageCount = lastValueUsage;
leastUsedKey = entry.getKey();
}
}
}
}
usingCounts.remove(leastUsedKey);
return leastUsedKey;
}
}

同样也是用一个Map来保存每个变量的使用次数,在每次get的时候,进行+1,removeNext函数中计算出使用次数最少的Entry的Key

小结

内存缓存基本就这些,没什么难的知识,无非就是策略的实现会复杂一些,以上只是一种实现方式,也有别的实现方式,欢迎交流

转载请指明出处RobinBlog:http://robinx.net/2017/01/03/App开发中的内存缓存/