FastKV

Project Url: BillyWei01/FastKV
Introduction: FastKV is an efficient and reliable key-value storage component.
More: Author   ReportBugs   OfficialWebsite   
Tags:

Maven CentralEnglish Doc

1. 概述

FastKV 是用 Java 编写的高效可靠的 key-value 存储库。
可以用于各种有 JVM 环境的运行平台,比如 Android。

FastKV 有以下特点:

  1. 读写速度快
    • 二进制编码,编码后的体积相对 XML 等文本编码要小很多;
    • 增量编码:FastKV 记录了各个 key-value 相对文件的偏移量(包括失效的 key-value), 从而在更新数据时可以直接在指定的位置写入数据。
    • 默认用 mmap 的方式记录数据,更新数据时直接写入到内存即可,没有 IO 阻塞。
  2. 支持多种写入模式
    • 除了 mmap 这种非阻塞的写入方式,FastKV 也支持常规的阻塞式写入方式, 并且支持同步阻塞和异步阻塞(分别类似于 SharePreferences 的 commit 和 apply)。
  3. 支持多种类型
    • 支持常用的 boolean/int/float/long/double/String 等基础类型。
    • 支持 ByteArray (byte[])。
    • 支持存储自定义对象。
    • 内置 Set的编码器 (兼容 SharePreferences)。
  4. 支持多进程
    • 项目提供了支持多进程的存储类(MPFastKV)。
    • 支持监听文件内容变化,其中一个进程修改文件,所有进程皆可感知。
  5. 方便易用
    • FastKV 提供了了丰富的 API 接口,开箱即用。
    • 提供的接口其中包括 getAll()和 putAll()方法, 所以很方便迁移 SharePreferences 等框架的数据到 FastKV, 当然,迁移 FastKV 的数据到其他框架也很方便。
  6. 稳定可靠
    • 通过 double-write 等方法确保数据的完整性。
    • 在 API 抛 IO 异常时提供降级处理。
  7. 代码精简
    • FastKV 由纯 Java 实现,编译成 jar 包后体积只有几十 K。

2. 使用方法

2.1 导入

FastKV 已发布到 Maven 中央仓库,分别发布了两个 jar 包,按需添加依赖即可。

其中一个包含封装了 SharePreferences 接口和支持多进程:

dependencies {
    implementation 'io.github.billywei01:fastkv:1.1.0'
}

另一个仅 Java 接口(不包含 Android SDK, 可在 Java 环境下调用),不支持多进程:

dependencies {
    implementation 'io.github.billywei01:fastkv-java:1.1.0'
}

2.2 初始化

    FastKVConfig.setLogger(FastKVLogger)
    FastKVConfig.setExecutor(ChannelExecutorService(4))

初始化可以按需设置日志回调和 Executor。 建议传入自己的线程池,以复用线程。

日志接口提供三个级别的回调,按需实现即可。

    public interface Logger {
        void i(String name, String message);

        void w(String name, Exception e);

        void e(String name, Exception e);
    }

2.3 数据读写

  • 基本用法
      FastKV kv = new FastKV.Builder(path, name).build();
      if(!kv.getBoolean("flag")){
          kv.putBoolean("flag" , true);
      }
    
  • 存储自定义对象
    FastKV.Encoder<?>[] encoders = new FastKV.Encoder[]{LongListEncoder.INSTANCE};
    FastKV kv = new FastKV.Builder(path, name).encoder(encoders).build();

    String objectKey = "long_list";
    List<Long> list = new ArrayList<>();
    list.add(100L);
    list.add(200L);
    list.add(300L);
    kv.putObject(objectKey, list, LongListEncoder.INSTANCE);

    List<Long> list2 = kv.getObject("long_list");

除了支持基本类型外,FastKV 还会支持写入对象,只需在构建 FastKV 实例时传入对象的编码器即可。 编码器为实现 FastKV.Encoder 的对象。 比如上面的 LongListEncoder 的实现如下:

public class LongListEncoder implements FastKV.Encoder<List<Long>> {
    public static final LongListEncoder INSTANCE = new LongListEncoder();

    @Override
    public String tag() {
        return "LongList";
    }

    @Override
    public byte[] encode(List<Long> obj) {
        return new PackEncoder().putLongList(0, obj).getBytes();
    }

    @Override
    public List<Long> decode(byte[] bytes, int offset, int length) {
        PackDecoder decoder = PackDecoder.newInstance(bytes, offset, length);
        List<Long> list = decoder.getLongList(0);
        decoder.recycle();
        return (list != null) ? list : new ArrayList<>();
    }
}

编码对象涉及序列化/反序列化。
这里推荐笔者的另外一个框架:https://github.com/BillyWei01/Packable

2.4 用法 For Android

相对于常规用法,Android 平台主要是多了 SharePreferences API, 以及支持 Kotlin,支持多进程等。
具体参考:用法 For Android

3. 性能测试

  • 测试数据:搜集 APP 中的 SharePreferenses 汇总的部份 key-value 数据(经过随机混淆)得到总共四百多个 key-value。
        由于日常使用过程中部分 key-value 访问多,部分访问少,所以构造了一个正态分布的访问序列。<br>
        此过程循环多遍,分别求其总耗时。
    
  • 比较对象: SharePreferences/DataStore/MMKV。
  • 测试机型:荣耀 20S。

测试代码:Benchmark

测试结果:

写入(ms) 读取(ms)
SharePreferences 1182 2
DataStore 33277 2
MMKV 29 10
FastKV 19 1
  • SharePreferences 提交用的是 apply, 耗时依然不少。
  • DataStore 写入很慢。
  • MMKV 的读取比 SharePreferences/DataStore 要慢一些,写入则比之快许多。
  • FastKV 无论读取还是写入都比其他方式要快。

现实中通常情况下不会有此差距,因为一般而言不会几百个 key-value 写到同一个文件,此处仅为显示极端情况下的对比,读者可自行调整参数看对比数据。

原理

参考:实现要点

License

See the LICENSE file for license rights and limitations.

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools