基础
其主要作用是byte[]的缓存池,可以指定最大的缓存的byte数目。当缓存的byte数目超过指定的最大值时,以LRU策略进行回收。在频繁进行I/O操作时,如果不停地创建byte[],会导致堆内存的极速消耗,因为gc的回收并不是太及时。
原理
用一个有序集合存储缓存的byte[],用另一个集合存储待删的byte[]——在缓存池满了的时候删除该集合中的最前面元素,直到缓存池有空闲为止。
源码及注释
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
/**
* byte[]的缓存池,可以指定最大的缓存的byte数目。当缓存的byte数目超过指定的最大值时,以LRU策略进行回收。
* 用一个集合记录该byte[]的使用顺序,使用另一个集合记录已经使用过的byte[],保证对byte[]的重复利用。
*/
public class ByteArrayPool {
/**
* byte[]待删集合,记录了byte[]的使用顺序。当缓存的byte数目超过指定的最大值时,会回收该list集合中的第一个元素。
* 每一次从变量mBuffersBySize中获取到合适的byte[]时,会将返回值从该集合中删除,因为这个byte[]是最近使用的。
* 每一次回收该集合中第0个元素,保证了回收的byte[]是使用时间最久远的。
*/
private List<byte[]> mBuffersByLastUse = new LinkedList<>();
/**
* byte[]的真正缓存list集合。每一次获取时都是从该集合中获取或者新生成
*/
private List<byte[]> mBuffersBySize = new ArrayList<>(64);
/**
* 当前缓存池中已经有byte数
*/
private int mCurrentSize = 0;
/**
* 本缓存池中最大的缓存byte数
*/
private final int mSizeLimit;
/**
* 哪个byte数组元素少,哪个在前
*/
protected static final Comparator<byte[]> BUF_COMPARATOR = new Comparator<byte[]>() {
@Override
public int compare(byte[] lhs, byte[] rhs) {
return lhs.length - rhs.length;
}
};
public ByteArrayPool(int sizeLimit) {
mSizeLimit = sizeLimit;
}
/**
* @param len.返回的byte[]的最小长度,有可能返回的byte[]的长度大于该len
*/
public synchronized byte[] getBuf(int len) {
for (int i = 0; i < mBuffersBySize.size(); i++) {
byte[] buf = mBuffersBySize.get(i);
if (buf.length >= len) {
mCurrentSize -= buf.length;//返回后,将当前缓存池中缓存的byte数目减少
mBuffersBySize.remove(i);//删掉,保证了一个byte[]数组不会提供给两个客户端使用
//本次使用了该缓存,所以将其从待删集合中删除,这样即使缓存的byte数量超过最大的范围,也不会被删掉。保证了最新的不会被删。
//同时,当一个byte[]被多次使用时,则该byte[]会被存储到该集合的最后端,也不会被立即回收
mBuffersByLastUse.remove(buf);
return buf;
}
}
return new byte[len];//没有合适的或者第一次会直接返回
}
/**
* 将使用过的byte数组返回到缓存池中
*/
public synchronized void returnBuf(byte[] buf) {
if (buf == null || buf.length > mSizeLimit) {
return;
}
mBuffersByLastUse.add(buf);//待删集合,不能对回收的byte[]进行排序
//该二分查找的返回值有两个效果:其一知道查找的有木有,其二如果木有,插入时插入的位置。
int pos = Collections.binarySearch(mBuffersBySize, buf, BUF_COMPARATOR);
if (pos < 0) {
pos = -pos - 1;
}
mBuffersBySize.add(pos, buf);
mCurrentSize += buf.length;
trim();
}
/**
* 回收该回收的部分。
*/
private synchronized void trim() {
while (mCurrentSize > mSizeLimit) {
byte[] buf = mBuffersByLastUse.remove(0);
mBuffersBySize.remove(buf);
mCurrentSize -= buf.length;
}
}
}
主要思路都在注释中。其实其主要思路就是:将最新使用的放在mBuffersByLastUse的最后面,这样回收的就最晚。而且LruCache的实现思路也和这个完全一样。
LruCache
import java.util.LinkedHashMap;
import java.util.Map;
public class LruCache<K, V> {
private final LinkedHashMap<K, V> map;//使用LinkedHashMap的主要原因在于:它的put的顺序与迭代器的取的顺序一致。
/** 最大的缓存数*/
private int size;
private int maxSize;
private int putCount;
private int createCount;
private int evictionCount;
private int hitCount;
private int missCount;
public LruCache(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
this.maxSize = maxSize;
this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
}
/**
* 重新定义最大的缓存大小
*/
public void resize(int maxSize) {
if (maxSize <= 0) {
throw new IllegalArgumentException("maxSize <= 0");
}
synchronized (this) {
this.maxSize = maxSize;
}
trimToSize(maxSize);
}
public final V get(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V mapValue;
synchronized (this) {
mapValue = map.get(key);
if (mapValue != null) {
hitCount++;
return mapValue;
}
missCount++;
}
/*
* 通过key值获取value值。如果该key值对应的有value了,则放弃create()返回的值。
* 因为create()可能耗费的时间比较长,所以create()执行完毕后,key可能对应的有value。
*/
V createdValue = create(key);
if (createdValue == null) {
return null;
}
synchronized (this) {
createCount++;
mapValue = map.put(key, createdValue);
if (mapValue != null) {
// key值有冲突,放弃create()生成的value
map.put(key, mapValue);
} else {
size += safeSizeOf(key, createdValue);
}
}
if (mapValue != null) {
entryRemoved(false, key, createdValue, mapValue);
return mapValue;
} else {
trimToSize(maxSize);
return createdValue;
}
}
/**
* 这里是先将数据加入缓存中,再判断缓存的大小是否超过了限制。
* 这会导致在临界状态下依旧会OOM,可以先移除多余的部分,再将新的加入到缓存中。
*/
public final V put(K key, V value) {
if (key == null || value == null) {
throw new NullPointerException("key == null || value == null");
}
V previous;
synchronized (this) {
putCount++;
size += safeSizeOf(key, value);
previous = map.put(key, value);//返回key对应的value。
if (previous != null) {
size -= safeSizeOf(key, previous);//previous是要被删除的,所以size应减除previous的大小
}
}
if (previous != null) {
entryRemoved(false, key, previous, value);
}
trimToSize(maxSize);
return previous;
}
/**
* 移除存在时间最久的对象,直到缓存的大小不大于最大缓存范围
* 整理整个缓存池,防止超出最大的范围
*/
public void trimToSize(int maxSize) {
while (true) {
K key;
V value;
synchronized (this) {
if (size < 0 || (map.isEmpty() && size != 0)) {
throw new IllegalStateException(getClass().getName()
+ ".sizeOf() is reporting inconsistent results!");
}
if (size <= maxSize || map.isEmpty()) {
break;
}
//因为获取的顺序与存储的顺序一致,所以该句得到的就是存在时间最久的缓存对象。
Map.Entry<K, V> toEvict = map.entrySet().iterator().next();
key = toEvict.getKey();
value = toEvict.getValue();
map.remove(key);
size -= safeSizeOf(key, value);
evictionCount++;
}
entryRemoved(true, key, value, null);
}
}
public final V remove(K key) {
if (key == null) {
throw new NullPointerException("key == null");
}
V previous;
synchronized (this) {
previous = map.remove(key);
if (previous != null) {
size -= safeSizeOf(key, previous);
}
}
if (previous != null) {
entryRemoved(false, key, previous, null);
}
return previous;
}
/**
* oldValue从缓存池中移除时的回调
*/
protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {}
/**
* 根据key值生成value。
*/
protected V create(K key) {
return null;
}
/**
* 对sizeOf()的大小加一层判断而已
*/
private int safeSizeOf(K key, V value) {
int result = sizeOf(key, value);
if (result < 0) {
throw new IllegalStateException("Negative size: " + key + "=" + value);
}
return result;
}
/**
* 获取对应value的大小。一般都需要重写该方法
*/
protected int sizeOf(K key, V value) {
return 1;
}
/**
* trimToSize()传入-1,则所有的缓存对象都会被清除
*/
public final void evictAll() {
trimToSize(-1); // -1 will evict 0-sized elements
}
//剩余代码无意义,略
}