如何实现一个缓存系统
试想这样一个场景,当你当你的而应用频繁要运用某一个组件的时候,而这个组件因动态原因都放置在网络或者disk上,这个时候必然会涉及性能的问题,碰到这类问题,我们自然而然的就会想到缓存,那么,我们该如何实现一个缓存系统,或者说是怎么从零开始设计一个缓存系统,接下来,就让我们一步步来见证一个缓存系统的实现。
设计前准备工作
ok,这个时候我们脑通打开,高速旋转,做之前,我们要想明白这几件事情
- 缓存到那个级别,即缓存到哪里?
- 如何访问缓存,即如何设计缓存API?
带着这几个问题,让问题先转一下,缓存到什么地方,说白了,就是找个地来存储这些中间组件,而这些地方都有什么呢,常见的一般有内存、disk,设置网络(像云、网盘等)。
然后,我们知道要缓存到这些组件到上述说的这些地方上,那么缓存到这些地方上有什么好处呢?提高访问速度,对这个绝对是我们最想要的。
明白了上述,接下来就是如何缓存常用组件到内存、disk、网络上了。
接口API实现
接下来,我们以实现 内存+disk双缓存设计为例来描述下如何去涉及一个缓存接口的API。
第一步,抽象出一个需要缓存的组件实例
public Class ComponentBean{
public void toString(){}
}
第二步,设计内存缓存接口
内存中的缓存接口设计,首先我们想到的是Map,当然如果系统版本高于19,我们可以用ArrayMap,如果key值设计为Long,我们可以使用LongSparseArray.
- ArrayMap
ArrayMap is a generic key->value mapping data structure that is designed to be more memory efficient than a traditional HashMap. It keeps its mappings in an array data structure – an integer array of hash codes for each item, and an Object array of the key/value pairs.
Added in API level 19
- LongSparseArray
SparseArray mapping longs to Objects. Unlike a normal array of Objects, there can be gaps in the indices. It is intended to be more memory efficient than using a HashMap to map Longs to Objects, both because it avoids auto-boxing keys and its data structure doesn’t rely on an extra entry object for each mapping.
Added in API level 16
本次设计内存缓存,主要基于最常用的Map来设计。
public interface IMemeoryCache{
void put(String key, Reference<ComponentBean>value);
ComponentBean get(String key);
}
上述是一个基本的设计,有存有取,那么接下来,要继续扩展一些基本的接口。试想 ,一个内存缓存系统除了存取之外,应该还有什么操作入口?remove?contain?是的,一个操作接口应该还包含一个删除入口、一个查询是否包含入口、一个能获取到当前缓存所有数据的入口、清空当前缓存数据的入口。
删除入口
删除,参照JDK Map设计,我们可以定义一下接口
ComponentBean remove(String key);
查询是否包含入口
boolean contain(String key);
获取到当前缓存所有数据的入口
这个借口的实现,我们只要能获取到当前缓存中所有key值映射,就可以遍历当前缓存中所有数据了,所以
Collection<String>keys();
清空当前缓存数据的入口
void clear();
最终设计下来,整体应该是:
public interface IMemeoryCache{
void put(String key, Reference<ComponentBean>value);
ComponentBean get(String key);
ComponentBean remove(String key);
boolean contain(String key);
Collection<String>keys();
void clear();
}
当然,如果后续有需求,可以继续扩展 IMemeoryCache接口,骚年,拓展吧。
第三步,设计Disk缓存接口
Disk缓存,骚年们首先想到是什么呢?流操作?对象序列化?定义一个合理的缓存目录?反正,我先想到的是对象序列化,为什么,因为本次示例存储的就是一个对象嘛。
ok,接下来,我们先将我们要存储的对象序列化了
存储的对象序列化
package com.haio.uil;
import android.os.Parcel;
import android.os.Parcelable;public class ComponentBean implements Parcelable {
private int componentId; ComponentBean(Parcel in) { super(); this.componentId = in.readInt(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel paramParcel, int flag) { paramParcel.writeInt(componentId); } public static final Parcelable.Creator<ComponentBean> CREATOR = new Creator<ComponentBean>() { @Override public ComponentBean[] newArray(int size) { return new ComponentBean[size]; } @Override public ComponentBean createFromParcel(Parcel paramParcel) { return new ComponentBean(paramParcel); } };
}
在Android中使用序列化,无非两种途经: Parcelable 和 Serializable。
对象序列化的目的:
1)永久性保存对象,保存对象的字节序列到本地文件中;
2)通过序列化对象在网络中传递对象;
3)通过序列化在进程间传递对象。
要说明一点,本文主讲的对象 并非对象的序列化,所以如何序列化对象就不做重点阐述,但可以给出参考链接
http://developer.android.com/intl/zh-cn/reference/android/os/Parcelable.html#PARCELABLE_WRITE_RETURN_VALUE
http://www.developerphil.com/parcelable-vs-serializable/
- Disk存储接口设计
如Memorycache设计一样,我们先给出一个基本的入口,然后一次扩展
public interface IDiskCache{
File getDirectory();
boolean save(String filename, ComponentBean bean) throws IOException;
File get(String filename);
}
上述是一个基本的设计,有存有取,并且可以获取到当前存储的缓存目录,那么接下来,要继续扩展一些基本的接口。试想 ,一个Disk缓存系统除了存取之外,应该还有什么操作入口?remove?资源释放close?是的,disk cache操作接口应该还包含一个删除入口、清空当前缓存数据的入口、关闭流数据入口(毕竟设计IO操作)。
最终设计应该如下:
public interface IDiskCache{
File getDirectory();
boolean save(String filename, ComponentBean bean) throws IOException;
File get(String filename);
boolean remove(String imageUri);
void close();
void clear();
}
重要的接口设计完了,接下来就是实现了,后续且听下回分解。
缓存示例
###MemoryCache缓存
做一个memoryCache缓存系统,想想该如何设计,照之前的接口设计,我们通过Reference来缓存Object,这样并不能保证所缓存的对象一直存在,比如,通过WeakReference来缓存对象,当GCRoot可以遍历到时,就会回收。所以,如果设计缓存,我们在之前的基础上,加一个强引用缓存(有一定数目限制),这样就可以保证有一部分常用对象的重复利用。
重庆-zhusx(327270607) 11:43:52
如果你想序列化传递的话…整个对象的成员变量都得序列化
这个也就是为什么你之前这么写 会报错.
重庆-zhusx(327270607) 11:45:58
内部类 会把 外面一层的东西都包进去…也就是 所有对象必须序列话,而且外面一层的..的所有变量都序列化…