存储框架封装:LruCacheUtils+DiskLruCacheUtils+责任链设计模式+DeepLink
- 一.存储框架实现思路?
- 1.缓存策略
- 2.LRU算法
- 3.LruCache内存缓存原理
- 4.DiskLruCache磁盘缓存原理
- 5.使用单例模式实现LRUCacheUtils
- 5.使用单例模式实现DiskLRUCacheUtils
- 二.什么是责任链设计模式?
一.存储框架实现思路?
思路来源与图片的三级缓存,即:内存->磁盘->网络
对于部分数据(如:静态数据或者配置信息)我们可能同样需要进行缓存来提升效率,
具体实现思路如下:
1、使用多级缓存完成资源的复用 如:内存 -> 磁盘 ->…
2、使用责任链设计模式,可以通过自定义添加链节点完成多级缓存
3、使用LruCache完成内存部分存储
4、使用DiskLruCache完成磁盘部分存储
1.缓存策略
缓存策略主要包含缓存的添加、获取和删除三类操作。删除缓存是因为不管是内存缓存还是硬盘缓存,它们的缓存大小都是有限的。当缓存满了之后,再想添加缓存就需要删除一些旧的缓存。
2.LRU算法
Android的三级缓存主要的就是内存缓存和硬盘缓存。这两种缓存机制的实现都应用到了LRU算法。
LRU(Least Recently Used)缓存算法是近期最少使用算法,它的核心思想是当缓存满时,会优先淘汰那些近期最少使用的缓存对象。
3.LruCache内存缓存原理
LruCache是Android 3.1提供的一个缓存类,在Android中可以直接使用LruCache实现内存缓存。而硬盘缓存DisLruCache目前还不是Android SDK的一部分,但Android官方文档推荐使用该算法来实现硬盘缓存。
LruCache的核心思想就是维护一个缓存对象列表,这个队列就是由LinkedHashMap维护的。列表的排列方式按照访问顺序实现,即一直没访问的对象,将放在队尾,即将被淘汰,而最近访问的对象将放在队头,最后被淘汰。
LinkedHashMap由数组+双向链表实现。其中双向链表的结构可以实现访问顺序和插入顺序,使得LinkedHashMap中的<key,value>对按照一定顺序排列起来。
LinkedHashMap通过构造函数来指定双向链表的结构是访问顺序还是插入顺序。
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) {
super(initialCapacity, loadFactor);
this.accessOrder = accessOrder;
}
accessOrder为true是访问顺序,为false是插入顺序。
比如,当设置accessOrder为true时:
public static final void main(String[] args) {
LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>(0, 0.75f, true);
map.put(0, 0);
map.put(1, 1);
map.put(2, 2);
map.put(3, 3);
map.put(4, 4);
map.put(5, 5);
map.put(6, 6);
map.get(1);
map.get(2);
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ":" + entry.getValue());
}
}
输出结果:
0:0
3:3
4:4
5:5
6:6
1:1
2:2
即最近访问的最后输出,这正好满足LRU缓存算法的思想。LruCache的实现就是利用了LinkedHashMap的这种数据结构
4.DiskLruCache磁盘缓存原理
磁盘读写也是用的LRU算法。但是这个和内存的LRU算法有一点小区别。为什么呢?因为内存缓存是我们运行的时候,程序加载内存里面的资源,可以直接通过一个LinkedHashMap去实现。但是磁盘不同,我总不可能吧所有磁盘的资源读出来然后加载在内存里面吧,这样的话,肯定会引发oom了。那么Glide是怎么做磁盘的LRU的呢?
Glide 是使用一个日志清单文件来保存这种顺序,DiskLruCache 在 APP 第一次安装时会在缓存文件夹下创建一个 journal 日志文件来记录图片的添加、删除、读取等等操作,后面每次打开 APP 都会读取这个文件,把其中记录下来的缓存文件名读取到 LinkedHashMap 中,后面每次对图片的操作不仅是操作这个 LinkedHashMap 还要记录在 journal 文件中. journal 文件内容如下图:
data/data/应用包名/cache/。。。。。
日志文件:
5.使用单例模式实现LRUCacheUtils
**********kotlin单例模式(掌握)
java版本
public class LRUCacheUtils<V> {
int maxSize= (int) (Runtime.getRuntime().totalMemory()/8);
private LRUCacheUtils(){
lruCache=new LruCache<String, V>(maxSize);
}
private static volatile LRUCacheUtils instance=null;
public static LRUCacheUtils getInstance(){
if (null==instance){
synchronized (LRUCacheUtils.class){
if (null==instance){
instance=new LRUCacheUtils();
}
}
}
return instance;
}
LruCache<String,V> lruCache=null;
/**
* 按Key存储值
* @param key
* @param value
*/
public void putValue(String key,V value){
lruCache.put(key,value);
}
/**
* 按Key获取值
* @param key
* @return
*/
public V getValue(String key){
return lruCache.get(key);
}
/**
* 按Key删除指定值
* @param key
*/
public void removeValue(String key){
lruCache.remove(key);
}
/**
* 清空
*/
public void clear(){
lruCache.evictAll();
}
}
kotlin版本
/**
* @Author : yaotianxue
* @Time : On 2023/5/23 07:59
* @Description : LRUCacheUtils
*/
class LRUCacheUtils<V> {
var maxSize = (Runtime.getRuntime().totalMemory() / 8).toInt()//内存的1/8
var lruCache:LruCache<String,V> = LruCache<String,V>(maxSize)//lruCache
//双重锁单例模式
companion object{
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
LRUCacheUtils<Any>()
}
}
/**
* 按Key存储值
*/
fun putValue(key:String,value:V){
lruCache.put(key,value)
}
/**
* 按Key获取值
*/
fun getValue(key:String): V? {
return lruCache.get(key)
}
/**
* 按key删除
*/
fun removeValue(key:String){
lruCache.remove(key)
}
/**
* 清空
*/
fun clear(){
lruCache.evictAll()
}
}
5.使用单例模式实现DiskLRUCacheUtils
java版本
public final class DiskLRUCacheUtils<V> {
private DiskLruCache diskLruCache;
private static DiskLRUCacheUtils instance=new DiskLRUCacheUtils();
/**
* 容量上限200M
*/
private static final int MAX_SIZE=200*1024*1024;
private DiskLRUCacheUtils(){
/**
* 如下 初始化DiskLruCache
*/
String diskCachePath = Environment.getExternalStorageDirectory().getAbsolutePath()+ File.separator+"bawei6diskcache";
File file=new File(diskCachePath);
if (!file.exists()){
file.mkdirs();
}
try {
diskLruCache = DiskLruCache.open(file, 1, 1, MAX_SIZE);
} catch (IOException e) {
e.printStackTrace();
}
}
public static DiskLRUCacheUtils getInstance(){
return instance;
}
public void putValue(String key, V data) {
String mKey= MD5.encrypt(key);
OutputStream outputStream = null;
DiskLruCache.Editor edit=null;
try {
edit = diskLruCache.edit(mKey);
if (edit!=null){
//对象转byte数组
byte[] bytes= ObjUtils.obj2ByteArray(data);
outputStream = edit.newOutputStream(0);
outputStream.write(bytes);
edit.commit();
diskLruCache.flush();
}
} catch (IOException e) {
e.printStackTrace();
if (edit!=null){
try {
edit.abort();
} catch (IOException ex) {
ex.printStackTrace();
}
}
}finally {
if (outputStream!=null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public V getValue(String key) {
InputStream is=null;
try {
List<Byte> data = new ArrayList<>();
String mKey = MD5.encrypt(key);
DiskLruCache.Snapshot snapShot = diskLruCache.get(mKey);
if (snapShot != null) {
is = snapShot.getInputStream(0);
byte[] bytes = new byte[2048];
int len;
while ((len = is.read(bytes)) != -1) {
for (int i = 0; i < len; i++) {
data.add(bytes[i]);
}
}
bytes = new byte[data.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = data.get(i);
}
return ObjUtils.byteArray2Object(bytes);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if (is!=null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
public void removeValue(String key) {
String mKey=MD5.encrypt(key);
try {
diskLruCache.remove(mKey);
} catch (IOException e) {
e.printStackTrace();
}
}
public void clear() {
try {
diskLruCache.delete();
} catch (IOException e) {
e.printStackTrace();
}
}
}
kotlin版本
/**
* @Author : yaotianxue
* @Time : On 2023/5/23 08:09
* @Description : DiskLRUCacheUtils
*/
class DiskLRUCacheUtils<V> {
//磁盘缓存路径
private var diskCachePath:String = Environment.getExternalStorageDirectory().absolutePath + File.separator +"2010baweidiskcache"
//磁盘缓存对象
var diskCache: DiskLruCache
//初始化
init {
var file = File(diskCachePath)
if(!file.exists()){//文件夹不存在
file.mkdirs()//创建文件夹
}
diskCache = DiskLruCache.open(file,1,1, MAX_SIZE.toLong())
}
companion object{
const val MAX_SIZE = 200*1024*1024
val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED){
DiskLRUCacheUtils<Any>()
}
}
/**
* 按Key存储值
*/
fun putValue(key:String,data:V){
var mKey = MD5.encrypt(key)//使用工具类将keyMD5加密
var editor = diskCache.edit(mKey)
editor.let {
var arrays = ObjUtils.obj2ByteArray(data)//对象转数组
var outputStream = editor.newOutputStream(0)
outputStream.write(arrays)
editor.commit()
diskCache.flush()
outputStream.close()
}
}
/**
* 按Key获取值
*/
fun getValue(key:String): V? {
var mKey = MD5.encrypt(key)//使用工具类将keyMD5加密
var snapshot = diskCache.get(mKey)//根据键获得snapshot对象
var inputStream = snapshot.getInputStream(0)//获得输入流
var bytes = ByteArray(2048)
var outputStream = ByteArrayOutputStream()//字节数组输出流
var len = 0
//inputStream读取数据到outputStream
while ((inputStream.read(bytes).also { len = it })!=-1){
outputStream.write(bytes)
}
return ObjUtils.byteArray2Object(outputStream.toByteArray())
}
/**
* 按key删除
*/
fun removeValue(key:String){
var mKey = MD5.encrypt(key)//使用工具类将keyMD5加密
diskCache.remove(mKey)
}
/**
* 清空
*/
fun clear(){
diskCache.delete()
}
}
测试使用
//测试
var diskLRUCacheUtils = DiskLRUCacheUtils.instance.putValue("111","我是测试数据")
var str = DiskLRUCacheUtils.instance.getValue("111")
Toast.makeText(this,"$str",Toast.LENGTH_LONG).show()
二.什么是责任链设计模式?
顾名思义,责任链模式(Chain of Responsibility
Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求
的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。
在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不
能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
实现参考链接:https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html