RxCache

RxCache is a Local Cache that supports Java and Android. Currently, it supports heap memory, off-heap memory, and disk cache.

Github address: github.com/fengzhizi71…

Off-heap memory

Objects can be stored in heap memory, off-heap memory, disk cache, or even distributed cache.

In Java, the opposite of out-of-heap memory is heap memory. Heap memory follows the JVM’s memory management mechanism, while off-heap memory is not subject to this restriction and is managed by the operating system.

There is a distinct difference, or opposite, between off-heap memory and heap memory.

Off-heap memory is more suitable for:

  • Store objects with long life cycles
  • It can be shared between processes, reducing object duplication between JVMS and making split deployment of JVMS easier.
  • Local caching, reducing the response time of disk caching or distributed caching.

The off-heap memory used in RxCache

First, create a DirectBufferConverter that converts objects to and from ByteBuffers and objects to and from byte arrays. Bytebuffer.alloctedirect (capability) is used to allocate out-of-heap memory. Cleaner is a self-defined class for releasing DirectByteBuffer. The specific code can be viewed: github.com/fengzhizi71…

public abstract class DirectBufferConverter<V> {

    public void dispose(ByteBuffer direct) {

        Cleaner.clean(direct);
    }

    public ByteBuffer to(V from) {
        if(from == null) return null;

        byte[] bytes = toBytes(from);
        ByteBuffer.wrap(bytes);
        ByteBuffer bf = ByteBuffer.allocateDirect(bytes.length);
        bf.put(bytes);
        bf.flip();
        return bf;
    }

    abstract public byte[] toBytes(V value);

    abstract public V toObject(byte[] value);

    public V from(ByteBuffer to) {
        if(to == null) return null;

        byte[] bs = new byte[to.capacity()];
        to.get(bs);
        to.flip();
        returntoObject(bs); }}Copy the code

Next, define a ConcurrentDirectHashMap<K, V> to implement the Map interface. It is a generic that supports converting V to ByteBuffer, stored in ConcurrentDirectHashMap’s map.

public abstract class ConcurrentDirectHashMap<K.V> implements Map<K.V> {

    final private Map<K, ByteBuffer> map;

    private final DirectBufferConverter<V> converter = new DirectBufferConverter<V>() {

        @Override
        public byte[] toBytes(V value) {
            return convertObjectToBytes(value);
        }

        @Override
        public V toObject(byte[] value) {
            returnconvertBytesToObject(value); }}; ConcurrentDirectHashMap() { map =new ConcurrentHashMap<>();
    }

    ConcurrentDirectHashMap(Map<K, V> m) {

        map = new ConcurrentHashMap<>();

        for(Entry<K, V> entry : m.entrySet()) { K key = entry.getKey(); ByteBuffer val = converter.to(entry.getValue()); map.put(key, val); }}protected abstract byte[] convertObjectToBytes(V value);

    protected abstract V convertBytesToObject(byte[] value);

    @Override
    public int size(a) {
        return map.size();
    }

    @Override
    public boolean isEmpty(a) {
        return map.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return map.containsKey(key);
    }

    @Override
    public V get(Object key) {
        final ByteBuffer byteBuffer = map.get(key);
        return converter.from(byteBuffer);
    }

    @Override
    public V put(K key, V value) {
        final ByteBuffer byteBuffer = map.put(key, converter.to(value));
        converter.dispose(byteBuffer);
        return converter.from(byteBuffer);
    }

    @Override
    public V remove(Object key) {
        final ByteBuffer byteBuffer = map.remove(key);
        final V value = converter.from(byteBuffer);
        converter.dispose(byteBuffer);
        return value;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> m) {
        for(Entry<? extends K, ? extends V> entry : m.entrySet()) { ByteBuffer byteBuffer = converter.to(entry.getValue()); map.put(entry.getKey(), byteBuffer); }}@Override
    public void clear(a) {
        final Set<K> keys = map.keySet();

        for(K key : keys) { map.remove(key); }}@Override
    public Set<K> keySet(a) {
        return map.keySet();
    }

    @Override
    public Collection<V> values(a) {
        Collection<V> values = new ArrayList<>();

        for (ByteBuffer byteBuffer : map.values())
        {
            V value = converter.from(byteBuffer);
            values.add(value);
        }
        return values;
    }

    @Override
    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> entries = new HashSet<>();

        for (Entry<K, ByteBuffer> entry : map.entrySet()) {
            K key = entry.getKey();
            V value = converter.from(entry.getValue());

            entries.add(new Entry<K, V>() {
                @Override
                public K getKey(a) {
                    return key;
                }

                @Override
                public V getValue(a) {
                    return value;
                }

                @Override
                public V setValue(V v) {
                    return null; }}); }return entries;
    }

    @Override
    public boolean containsValue(Object value) {

        for (ByteBuffer v : map.values()) {
            if (v.equals(value)) {
                return true; }}return false; }}Copy the code

Create ConcurrentStringObjectDirectHashMap, it is the K type String, V is arbitrary Object. Among them, serialization and deserialization use byteKit mentioned in Common Java Byte Encapsulation.

public class ConcurrentStringObjectDirectHashMap extends ConcurrentDirectHashMap<String.Object> {

    @Override
    protected byte[] convertObjectToBytes(Object value) {

        return Bytes.serialize(value);
    }

    @Override
    protected Object convertBytesToObject(byte[] value) {

        returnBytes.deserialize(value); }}Copy the code

Memory level cache is realized based on FIFO and off-heap Memory.

public class DirectBufferMemoryImpl extends AbstractMemoryImpl {

    private ConcurrentStringObjectDirectHashMap cache;
    private List<String> keys;

    public DirectBufferMemoryImpl(long maxSize) {

        super(maxSize);
        cache = new ConcurrentStringObjectDirectHashMap();
        this.keys = new LinkedList<>();
    }

    @Override
    public <T> Record<T> getIfPresent(String key) {

        T result = null;

        if(expireTimeMap.get(key)! =null) {

            if (expireTimeMap.get(key)<0) { // Cached data never expires

                result = (T) cache.get(key);
            } else {

                if (timestampMap.get(key) + expireTimeMap.get(key) > System.currentTimeMillis()) {  // The cached data has not expired

                    result = (T) cache.get(key);
                } else {                     // The cached data has expiredevict(key); }}}returnresult ! =null ? new Record<>(Source.MEMORY,key, result, timestampMap.get(key),expireTimeMap.get(key)) : null;
    }

    @Override
    public <T> void put(String key, T value) {

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

    @Override
    public <T> void put(String key, T value, long expireTime) {

        if (keySet().size()<maxSize) { // There is space in the cache

            saveValue(key,value,expireTime);
        } else {                       // The cache space is insufficient, and one needs to be deleted

            if (containsKey(key)) {

                keys.remove(key);

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

                String oldKey = keys.get(0); // The earliest cached key
                evict(oldKey);               // Remove the first cached data FIFO algorithmsaveValue(key,value,expireTime); }}}private <T> void saveValue(String key, T value, long expireTime) {

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

    @Override
    public Set<String> keySet(a) {

        return cache.keySet();
    }

    @Override
    public boolean containsKey(String key) {

        return cache.containsKey(key);
    }

    @Override
    public void evict(String key) {

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

    @Override
    public void evictAll(a) { cache.clear(); timestampMap.clear(); expireTimeMap.clear(); keys.clear(); }}Copy the code

At this point, you have completed the encapsulation that exists in and out of the heap in the RxCache. In fact, there are many caching frameworks that support off-heap memory, such as Ehcache and MapDB. RxCache currently has MapDB modules.

conclusion

RxCache is a Local Cache that has been used in our project and in my own crawler framework, NetDiscovery. In the future, it will continue to be used as a mature component in other corporate and personal projects.

RxCache series

  1. ReentrantReadWriteLock Read/write lock and its use in RxCache
  2. Retrofit style RxCache and its various cache replacement algorithms
  3. RxCache integrates the Persistence layer frameworks of greenDAO and Room of Android
  4. Build a simple responsive Local Cache for Java and Android

Java and Android technology stack: update and push original technical articles every week, welcome to scan the qr code of the public account below and pay attention to, looking forward to growing and progress with you together.