FastKV

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

Maven Central | English

1. 概述

FastKV 是用 Java 编写的高效可靠的 key-value 存储库。

FastKV 有以下特点:

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

2. 使用方法

2.1 导入

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

2.2 初始化

    FastKVConfig.setLogger(FastKVLogger)
    FastKVConfig.setExecutor(Dispatchers.Default.asExecutor())

初始化可以按需设置日志接口和 Executor。

2.3 基本用法

    // FastKV kv = new FastKV.Builder(context, name).build();
    FastKV kv = new FastKV.Builder(path, name).build();

    if(!kv.getBoolean("flag")){
        kv.putBoolean("flag" , true);
    }

    int count = kv.getInt("count");
    if(count < 10){
        kv.putInt("count" , count + 1);
    }

Builder 的构造可传 Context 或者 path。
如果传 Context 的话,会在内部目录的'files'目录下创建'fastkv'目录来作为文件的保存路径。

2.4 存储自定义对象

    FastEncoder<?>[] encoders = new FastEncoder[]{LongListEncoder.INSTANCE};
    FastKV kv = new FastKV.Builder(context, name).encoder(encoders).build();

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

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

除了支持基本类型外,FastKV 还支持写入对象。
如果要写入自定义对象,需在构建 FastKV 实例时传入对象的编码器(实现了 FastEncoder 接口的对象)。
因为 FastKV 实例加载时会执行自动反序列化,所以需要在实例创建时注入编码器。
另外,如果没有注入编码器,调用 putObject 接口时会抛出异常(提醒使用者给 FastKV 实例传入编码器)。

上面 LongListEncoder 就实现了 FastEncoder 接口,代码实现可参考: LongListEncoder

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

2.5 数据加密

如需对数据进行加密,在创建 FastKV 实例时传入 FastCipher 的实现即可。

FastKV kv = FastKV.Builder(path, name)
         .cipher(yourCihper)
         .build()

项目中有举例 Cipher 的实现,可参考:AESCipher

2.6 迁移 SharePreferences 到 FastKV

FastKV 实现了 SharedPreferences 接口,并且提供了迁移 SP 数据的方法。
用法如下:

public class SpCase {
   public static final String NAME = "common_store";
   // 原本的获取 SP 的方法
   // public static final SharedPreferences preferences = AppContext.INSTANCE.getContext().getSharedPreferences(NAME, Context.MODE_PRIVATE);

   // 导入原 SP 数据
   public static final SharedPreferences preferences = FastKV.adapt(AppContext.INSTANCE.getContext(), NAME);
}

2.7 迁移 MMKV 到 FastKV

由于 MMKV 没有实现 'getAll' 接口,所以无法像 SharePreferences 一样一次性迁移。
但是可以封装一个 KV 类,创建 'getInt','getString' ... 等方法,并在其中做适配处理。 可参考:MMKV2FastKV

2.8 多进程

项目提供了支持多进程的实现:MPFastKV
MPFastKV 除了支持多进程读写之外,还实现了 SharedPreferences 的接口,包括支持注册 OnSharedPreferenceChangeListener;
其中一个进程修改了数据,所有的进程都会感知(通过 OnSharedPreferenceChangeListener 回调)。
可参考 MultiProcessTestActivityTestService

需要提醒的是,由于支持多进程需要维护更多的状态,MPFastKV 的写入要比 FastKV 慢不少, 所以在不需要多进程访问的情况下,尽量用 FastKV。

2.9 Kotlin 委托

Kotlin 是兼容 Java 的,所以 Kotlin 下也可以直接用 FastKV 或者 SharedPreferences 的 API。
此外,Kotlin 还提供了“委托属性”这一语法糖,可以用于改进 key-value API 访问。
可参考:KVData

2.10 注意事项

  1. 不同版本之间,不要改变路径和名字,否则会打开不同的文件。
  2. 如果使用了 Cipher(加密),不要更换,否则会打开文件时会解析不了。 不过从没有使用 Cipher 到使用 Cipher 是可以的,FastKV 会先解析未加密的数据,然后在重新加密写入
  3. 同一个 key, 对应的 value 的操作应保持类型一致。 比如,同一个 key, A 处 putString, B 处 getInt, 则无法返回预期的 value。

3. 性能测试

  • 测试数据:搜集 APP 中的 SharePreferences 汇总的部份 key-value 数据(经过随机混淆)得到总共六百多个 key-value。
    分别截取其中一部分,构造正态分布的输入序列,进行多次测试。
  • 测试机型:华为 P30 Pro
  • 测试代码:Benchmark

测试结果如下:

更新:

25 50 100 200 400 600
SP-commit 114 172 411 666 2556 5344
DataStore 231 625 1717 4421 7629 13639
SQLiteKV 192 382 1025 1565 4279 5034
SP-apply 3 9 35 118 344 516
MMKV 4 8 5 8 10 9
FastKV 3 6 4 6 8 10

查询:

25 50 100 200 400 600
SP-commit 1 3 2 1 2 3
DataStore 57 76 115 117 170 216
SQLiteKV 96 161 265 417 767 1038
SP-apply 0 1 0 1 3 3
MMKV 0 1 1 5 8 11
FastKV 0 1 1 3 3 1

每次执行 Benchmark 获取到的结果有所浮动,尤其是 APP 启动后执行多次,部分 KV 会变快(JIT 优化)。
以上数据是取 APP 冷启动后第一次 Benchmark 的数据。

4. 参考链接

相关博客:
https://juejin.cn/post/7018522454171582500

由于提供给 Android 平台的版本和纯 JDK 的版本的差异越来越多,所以分开仓库来维护。
纯 JDK 版本的链接为:
https://github.com/BillyWei01/FastKV-Java

License

See the LICENSE file for license rights and limitations.

Apps
About Me
GitHub: Trinea
Facebook: Dev Tools