如何实现一个缓存系统

如何实现一个缓存系统

试想这样一个场景,当你当你的而应用频繁要运用某一个组件的时候,而这个组件因动态原因都放置在网络或者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
内部类 会把 外面一层的东西都包进去…也就是 所有对象必须序列话,而且外面一层的..的所有变量都序列化…

坚持原创技术分享,您的支持将鼓励我继续创作!