beautiful.jpg

RxCache

RxCache 是一款支持 Java 和 Android 的 Local Cache 。目前,支持堆内存、堆外内存 (off-heap memory)、磁盘缓存。

github 地址:github.com/fengzhizi71…

堆外内存 (off-heap memory)

对象可以存储在 堆内存、堆外内存、磁盘缓存甚至是分布式缓存。

在 Java 中,与堆外内存相对的是堆内存。堆内存遵守 JVM 的内存管理机制,而堆外内存不受到此限制,它由操作系统进行管理。

JVM的内存管理以及堆外内存.jpg

堆外内存和堆内存有明显的区别,或者说有相反的应用场景。

堆外内存更适合:

  • 存储生命周期长的对象
  • 可以在进程间可以共享,减少 JVM 间的对象复制,使得 JVM 的分割部署更容易实现。
  • 本地缓存,减少磁盘缓存或者分布式缓存的响应时间。

RxCache 中使用的堆外内存

首先,创建一个 DirectBufferConverter ,用于将对象和 ByteBuffer 相互转换,以及对象和 byte 数组相互转换。其中,ByteBuffer.allocteDirect(capability) 用于分配堆外内存。Cleaner 是自己定义的一个类,用于释放 DirectByteBuffer。具体代码可以查看:github.com/fengzhizi71…

public abstract class DirectBufferConverter<V> {
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">dispose</span><span class="hljs-params">(ByteBuffer direct)</span> </span>{

    Cleaner.clean(direct);
}

<span class="hljs-function"><span class="hljs-keyword">public</span> ByteBuffer <span class="hljs-title">to</span><span class="hljs-params">(V from)</span> </span>{
    <span class="hljs-keyword">if</span>(from == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;

    <span class="hljs-keyword">byte</span>[] bytes = toBytes(from);
    ByteBuffer.wrap(bytes);
    ByteBuffer bf = ByteBuffer.allocateDirect(bytes.length);
    bf.put(bytes);
    bf.flip();
    <span class="hljs-keyword">return</span> bf;
}

<span class="hljs-keyword">abstract</span> <span class="hljs-keyword">public</span> <span class="hljs-keyword">byte</span>[] toBytes(V value);

<span class="hljs-function"><span class="hljs-keyword">abstract</span> <span class="hljs-keyword">public</span> V <span class="hljs-title">toObject</span><span class="hljs-params">(<span class="hljs-keyword">byte</span>[] value)</span></span>;

<span class="hljs-function"><span class="hljs-keyword">public</span> V <span class="hljs-title">from</span><span class="hljs-params">(ByteBuffer to)</span> </span>{
    <span class="hljs-keyword">if</span>(to == <span class="hljs-keyword">null</span>) <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;

    <span class="hljs-keyword">byte</span>[] bs = <span class="hljs-keyword">new</span> <span class="hljs-keyword">byte</span>[to.capacity()];
    to.get(bs);
    to.flip();
    <span class="hljs-keyword">return</span> toObject(bs);
}

}

复制代码

接下来,定义一个 ConcurrentDirectHashMap<K, V> 实现 Map 接口。它是一个范性,支持将 V 转换成 ByteBuffer 类型,存储到 ConcurrentDirectHashMap 的 map 中。

public abstract class ConcurrentDirectHashMap<K, V> implements Map<K, V> {
<span class="hljs-keyword">final</span> <span class="hljs-keyword">private</span> Map&lt;K, ByteBuffer&gt; map;

<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> DirectBufferConverter&lt;V&gt; converter = <span class="hljs-keyword">new</span> DirectBufferConverter&lt;V&gt;() {

    <span class="hljs-meta">@Override</span>
    <span class="hljs-keyword">public</span> <span class="hljs-keyword">byte</span>[] toBytes(V value) {
        <span class="hljs-keyword">return</span> convertObjectToBytes(value);
    }

    <span class="hljs-meta">@Override</span>
    <span class="hljs-function"><span class="hljs-keyword">public</span> V <span class="hljs-title">toObject</span><span class="hljs-params">(<span class="hljs-keyword">byte</span>[] value)</span> </span>{
        <span class="hljs-keyword">return</span> convertBytesToObject(value);
    }
};

ConcurrentDirectHashMap() {

    map = <span class="hljs-keyword">new</span> ConcurrentHashMap&lt;&gt;();
}

ConcurrentDirectHashMap(Map&lt;K, V&gt; m) {

    map = <span class="hljs-keyword">new</span> ConcurrentHashMap&lt;&gt;();

    <span class="hljs-keyword">for</span> (Entry&lt;K, V&gt; entry : m.entrySet()) {
        K key = entry.getKey();
        ByteBuffer val = converter.to(entry.getValue());
        map.put(key, val);
    }
}

<span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">byte</span>[] convertObjectToBytes(V value);

<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> V <span class="hljs-title">convertBytesToObject</span><span class="hljs-params">(<span class="hljs-keyword">byte</span>[] value)</span></span>;

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> map.size();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">isEmpty</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> map.isEmpty();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">containsKey</span><span class="hljs-params">(Object key)</span> </span>{
    <span class="hljs-keyword">return</span> map.containsKey(key);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> V <span class="hljs-title">get</span><span class="hljs-params">(Object key)</span> </span>{
    <span class="hljs-keyword">final</span> ByteBuffer byteBuffer = map.get(key);
    <span class="hljs-keyword">return</span> converter.from(byteBuffer);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> V <span class="hljs-title">put</span><span class="hljs-params">(K key, V value)</span> </span>{
    <span class="hljs-keyword">final</span> ByteBuffer byteBuffer = map.put(key, converter.to(value));
    converter.dispose(byteBuffer);
    <span class="hljs-keyword">return</span> converter.from(byteBuffer);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> V <span class="hljs-title">remove</span><span class="hljs-params">(Object key)</span> </span>{
    <span class="hljs-keyword">final</span> ByteBuffer byteBuffer = map.remove(key);
    <span class="hljs-keyword">final</span> V value = converter.from(byteBuffer);
    converter.dispose(byteBuffer);
    <span class="hljs-keyword">return</span> value;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">putAll</span><span class="hljs-params">(Map&lt;? extends K, ? extends V&gt; m)</span> </span>{
    <span class="hljs-keyword">for</span> (Entry&lt;? extends K, ? extends V&gt; entry : m.entrySet()) {
        ByteBuffer byteBuffer = converter.to(entry.getValue());
        map.put(entry.getKey(), byteBuffer);
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">clear</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">final</span> Set&lt;K&gt; keys = map.keySet();

    <span class="hljs-keyword">for</span> (K key : keys) {
        map.remove(key);
    }
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Set&lt;K&gt; <span class="hljs-title">keySet</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-keyword">return</span> map.keySet();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Collection&lt;V&gt; <span class="hljs-title">values</span><span class="hljs-params">()</span> </span>{
    Collection&lt;V&gt; values = <span class="hljs-keyword">new</span> ArrayList&lt;&gt;();

    <span class="hljs-keyword">for</span> (ByteBuffer byteBuffer : map.values())
    {
        V value = converter.from(byteBuffer);
        values.add(value);
    }
    <span class="hljs-keyword">return</span> values;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> Set&lt;Entry&lt;K, V&gt;&gt; entrySet() {
    Set&lt;Entry&lt;K, V&gt;&gt; entries = <span class="hljs-keyword">new</span> HashSet&lt;&gt;();

    <span class="hljs-keyword">for</span> (Entry&lt;K, ByteBuffer&gt; entry : map.entrySet()) {
        K key = entry.getKey();
        V value = converter.from(entry.getValue());

        entries.add(<span class="hljs-keyword">new</span> Entry&lt;K, V&gt;() {
            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> K <span class="hljs-title">getKey</span><span class="hljs-params">()</span> </span>{
                <span class="hljs-keyword">return</span> key;
            }

            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> V <span class="hljs-title">getValue</span><span class="hljs-params">()</span> </span>{
                <span class="hljs-keyword">return</span> value;
            }

            <span class="hljs-meta">@Override</span>
            <span class="hljs-function"><span class="hljs-keyword">public</span> V <span class="hljs-title">setValue</span><span class="hljs-params">(V v)</span> </span>{
                <span class="hljs-keyword">return</span> <span class="hljs-keyword">null</span>;
            }
        });
    }

    <span class="hljs-keyword">return</span> entries;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">containsValue</span><span class="hljs-params">(Object value)</span> </span>{

    <span class="hljs-keyword">for</span> (ByteBuffer v : map.values()) {
        <span class="hljs-keyword">if</span> (v.equals(value)) {
            <span class="hljs-keyword">return</span> <span class="hljs-keyword">true</span>;
        }
    }
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">false</span>;
}

}

复制代码

创建 ConcurrentStringObjectDirectHashMap,它的 K 是 String 类型,V 是任意的 Object 对象。其中,序列化和反序列化采用《Java 字节的常用封装》提到的 bytekit

public class ConcurrentStringObjectDirectHashMap extends ConcurrentDirectHashMap<String,Object> {
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">protected</span> <span class="hljs-keyword">byte</span>[] convertObjectToBytes(Object value) {

    <span class="hljs-keyword">return</span> Bytes.serialize(value);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">protected</span> Object <span class="hljs-title">convertBytesToObject</span><span class="hljs-params">(<span class="hljs-keyword">byte</span>[] value)</span> </span>{

    <span class="hljs-keyword">return</span> Bytes.deserialize(value);
}

}

复制代码

基于 FIFO 以及堆外内存来实现 Memory 级别的缓存。

public class DirectBufferMemoryImpl extends AbstractMemoryImpl {
<span class="hljs-keyword">private</span> ConcurrentStringObjectDirectHashMap cache;
<span class="hljs-keyword">private</span> List&lt;String&gt; keys;

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">DirectBufferMemoryImpl</span><span class="hljs-params">(<span class="hljs-keyword">long</span> maxSize)</span> </span>{

    <span class="hljs-keyword">super</span>(maxSize);
    cache = <span class="hljs-keyword">new</span> ConcurrentStringObjectDirectHashMap();
    <span class="hljs-keyword">this</span>.keys = <span class="hljs-keyword">new</span> LinkedList&lt;&gt;();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function">Record&lt;T&gt; <span class="hljs-title">getIfPresent</span><span class="hljs-params">(String key)</span> </span>{

    T result = <span class="hljs-keyword">null</span>;

    <span class="hljs-keyword">if</span>(expireTimeMap.get(key)!=<span class="hljs-keyword">null</span>) {

        <span class="hljs-keyword">if</span> (expireTimeMap.get(key)&lt;<span class="hljs-number">0</span>) { <span class="hljs-comment">// 缓存的数据从不过期</span>

            result = (T) cache.get(key);
        } <span class="hljs-keyword">else</span> {

            <span class="hljs-keyword">if</span> (timestampMap.get(key) + expireTimeMap.get(key) &gt; System.currentTimeMillis()) {  <span class="hljs-comment">// 缓存的数据还没有过期</span>

                result = (T) cache.get(key);
            } <span class="hljs-keyword">else</span> {                     <span class="hljs-comment">// 缓存的数据已经过期</span>

                evict(key);
            }
        }
    }

    <span class="hljs-keyword">return</span> result != <span class="hljs-keyword">null</span> ? <span class="hljs-keyword">new</span> Record&lt;&gt;(Source.MEMORY,key, result, timestampMap.get(key),expireTimeMap.get(key)) : <span class="hljs-keyword">null</span>;
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">put</span><span class="hljs-params">(String key, T value)</span> </span>{

    put(key,value, Constant.NEVER_EXPIRE);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> &lt;T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">put</span><span class="hljs-params">(String key, T value, <span class="hljs-keyword">long</span> expireTime)</span> </span>{

    <span class="hljs-keyword">if</span> (keySet().size()&lt;maxSize) { <span class="hljs-comment">// 缓存还有空间</span>

        saveValue(key,value,expireTime);
    } <span class="hljs-keyword">else</span> {                       <span class="hljs-comment">// 缓存空间不足,需要删除一个</span>

        <span class="hljs-keyword">if</span> (containsKey(key)) {

            keys.remove(key);

            saveValue(key,value,expireTime);
        } <span class="hljs-keyword">else</span> {

            String oldKey = keys.get(<span class="hljs-number">0</span>); <span class="hljs-comment">// 最早缓存的key</span>
            evict(oldKey);               <span class="hljs-comment">// 删除最早缓存的数据 FIFO算法</span>

            saveValue(key,value,expireTime);
        }
    }
}

<span class="hljs-keyword">private</span> &lt;T&gt; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">saveValue</span><span class="hljs-params">(String key, T value, <span class="hljs-keyword">long</span> expireTime)</span> </span>{

    cache.put(key,value);
    timestampMap.put(key,System.currentTimeMillis());
    expireTimeMap.put(key,expireTime);
    keys.add(key);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> Set&lt;String&gt; <span class="hljs-title">keySet</span><span class="hljs-params">()</span> </span>{

    <span class="hljs-keyword">return</span> cache.keySet();
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">boolean</span> <span class="hljs-title">containsKey</span><span class="hljs-params">(String key)</span> </span>{

    <span class="hljs-keyword">return</span> cache.containsKey(key);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">evict</span><span class="hljs-params">(String key)</span> </span>{

    cache.remove(key);
    timestampMap.remove(key);
    expireTimeMap.remove(key);
    keys.remove(key);
}

<span class="hljs-meta">@Override</span>
<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">evictAll</span><span class="hljs-params">()</span> </span>{

    cache.clear();
    timestampMap.clear();
    expireTimeMap.clear();
    keys.clear();
}

}

复制代码

到了这里,已经完成了堆外内存在 RxCache 中的封装。其实,已经有很多缓存框架都支持堆外内存,例如 Ehcache、MapDB 等。RxCache 目前已经有了 MapDB 的模块。

总结

RxCache 是一款 Local Cache,它已经应用到我们项目中,也在我个人的爬虫框架 NetDiscovery 中使用。未来,它会作为一个成熟的组件,不断运用到公司和个人的其他项目中。

  • Java

    Java,是由 Sun Microsystems 公司于 1995 年 5 月推出的 Java 程序设计语言和 Java 平台的总称。用 Java 实现的 HotJava 浏览器(支持 Java applet)显示了 Java 的魅力:跨平台、动态的…

    380 引用 • 6 回帖
感谢    赞同    分享    收藏    关注    反对    举报    ...