当前位置:   article > 正文

腾讯开源 K-V 组件 MMKV 源码浅析_腾讯mmkv是用什么语言写的

腾讯mmkv是用什么语言写的

突然对 MMKV 的实现非常感兴趣,因此写下此文。由于本人经验不足,如果文中内容有误请路过的大佬加以指正,下面开始正文:

MMKV 是腾讯于 2018 年 9 月 20 日开源的一个 K-V 组件,下面是官方对它的介绍:

MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Windows 平台,一并开源。

想学习更多Android知识,或者获取MMKV相关视频资料请加入Android技术开发交流2群:862625886。 有面试资源系统整理分享,Java语言进阶和Kotlin语言与Android相关技术内核,APP开发框架知识, 360°Android App全方位性能优化。Android前沿技术,高级UI、Gradle、RxJava、小程序、Hybrid、 移动架构师专题项目实战环节、React Native、等技术教程!架构师课程、NDK模块开发、 Flutter等全方面的 Android高级实践技术讲解。还有在线答疑

从上面的介绍,可以发现它与 Android 中的 SharedPreferences 是极其相似的,但是它的性能却远超于 SharedPreferences。根据官方的宣传,写入随机 int 1000次,下面是它们两者的性能对比:

可以发现,它相比 SP 的性能提升不是一点半点,接近了 100 倍!那么它是如何达到这么高的效率的呢?相信很多人已经从上面官方对它的介绍中找到了原因——mmap

没错,又是 mmap,Binder 的底层原理用到了它,腾讯和美团的日志库用到了它,如今腾讯的 K-V 组件也用到了它,关于 mmap 的介绍及基本使用可以看我之前的一篇笔记:内存映射—— mmap() 函数的使用

那么今天就让我们来研究一下 MMKV 的具体实现。

注意:

1. MMKV 的核心内容使用 C++ 编写,同时涉及了很多 JNI 的使用,因此这篇文章并不是纯 Java。

2. 由于笔者还是大二学生,C++ 功底一般,本文纯属因兴趣开始的研究,因此只能尽量以理解具体流程为主,如果有误请路过的大佬们指出

如果你还不知道什么是 JNI 和 NDK,可以看我之前的另一篇博客:NDK的基本使用,这一篇就够了

初始化

MMKV 在使用前,需要在 Application 中进行初始化:

  1. protected void onCreate(Bundle savedInstanceState) {
  2. super.onCreate(savedInstanceState);
  3. String rootDir = MMKV.initialize(this);
  4. }

那么我们先进入 initialize 方法看看具体做了什么:

  1. public static String initialize(Context context) {
  2. String root = context.getFilesDir().getAbsolutePath() + "/mmkv";
  3. return initialize(root);
  4. }
  • 可以看到,这里指定 rootDir 为 context.getFilesDir().getAbsolutePath() + "/mmkv";,然后调用了 initialize(rootDir)

下面我们看到 initialize(rootDir):

    1. public static String initialize(String rootDir) {
    2. MMKV.rootDir = rootDir;
    3. jniInitialize(MMKV.rootDir);
    4. return rootDir;
    5. }

     

它调用了一个 Native 方法 jniInitialize,下面我们看到它的具体实现:

  1. extern "C" JNIEXPORT JNICALL void
  2. Java_com_tencent_mmkv_MMKV_jniInitialize(JNIEnv *env, jobject obj, jstring rootDir) {
  3. if (!rootDir) {
  4. return;
  5. }
  6. const char *kstr = env->GetStringUTFChars(rootDir, nullptr);
  7. if (kstr) {
  8. MMKV::initializeMMKV(kstr);
  9. env->ReleaseStringUTFChars(rootDir, kstr);
  10. }
  11. }

可以看到,首先它将 Java 中的 rootDir 转换为了一个 char* kstr,在不为 null 的情况下调用了 MMKV 类的 static 方法 initializeMMKV。下面来到 initializeMMKV:

  1. void MMKV::initializeMMKV(const std::string &rootDir) {
  2. static pthread_once_t once_control = PTHREAD_ONCE_INIT;
  3. pthread_once(&once_control, initialize);
  4. g_rootDir = rootDir;
  5. char *path = strdup(g_rootDir.c_str());
  6. mkPath(path);
  7. free(path);
  8. MMKVInfo("root dir: %s", g_rootDir.c_str());
  9. }
  •  

这里可以看到,这里首先将 rootDir 赋值给了这个 static 变量 g_rootDir,之后将 rootDir 复制了一份 path,通过 path 来将 rootDir 这个目录在硬盘中创建出来。

为什么要复制一份而不直接将 rootDir 传进去呢?我们不妨看看 mkPath 的代码:

  1. bool mkPath(char *path) {
  2. struct stat sb = {};
  3. bool done = false;
  4. char *slash = path;
  5. while (!done) {
  6. slash += strspn(slash, "/");
  7. slash += strcspn(slash, "/");
  8. done = (*slash == '\0');
  9. *slash = '\0';
  10. if (stat(path, &sb) != 0) {
  11. if (errno != ENOENT || mkdir(path, 0777) != 0) {
  12. MMKVWarning("%s : %s", path, strerror(errno));
  13. return false;
  14. }
  15. } else if (!S_ISDIR(sb.st_mode)) {
  16. MMKVWarning("%s: %s", path, strerror(ENOTDIR));
  17. return false;
  18. }
  19. *slash = '/';
  20. }
  21. return true;
  22. }

可以看到,它从前向后一层层进行判断是否目录是否存在,若不存在则调用 mkdir 函数来进行目录的创建,在这个过程中会将传入的路径的一些字段置为 '\0' ,因此为了保证原来的 rootDir 不会被改变,因此复制了一份 char * 传入。

那么到这里,初始化的过程就结束了,主要的步骤就是一些变量的赋值以及目录的创建。

MMKV 对象的获取

MMKV 中有一种叫做 mmapID 的 String 用于区别存储,类似于 SP 中的 name。通过 MMKV.mmkvWithId 即可获取对应 MMKV 对象,下面我们可以看一下其具体实现:

通过 mmapID 获取

这里以 mmkvWithID(String mmapID, int mode) 进行举例,其实其他的重载都是类似的,都调用了 getMMKVWithID 这个 Native 方法,只是传入的参数不同。

  1. private native static long
  2. getMMKVWithID(String mmapID, int mode, String cryptKey, String relativePath);

上面是它的 Java 定义,下面我们看到它的具体实现:

  1. extern "C" JNIEXPORT JNICALL jlong Java_com_tencent_mmkv_MMKV_getMMKVWithID(
  2. JNIEnv *env, jobject obj, jstring mmapID, jint mode, jstring cryptKey, jstring relativePath) {
  3. MMKV *kv = nullptr;
  4. if (!mmapID) {
  5. return (jlong) kv;
  6. }
  7. string str = jstring2string(env, mmapID);
  8. bool done = false;
  9. if (cryptKey) {
  10. string crypt = jstring2string(env, cryptKey);
  11. if (crypt.length() > 0) {
  12. if (relativePath) {
  13. string path = jstring2string(env, relativePath);
  14. kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, &crypt, &path);
  15. } else {
  16. kv = MMKV::mmkvWithID(str, DEFAULT_MMAP_SIZE, (MMKVMode) mode, &crypt, nullptr);
  17. }
  18. done = true;
  19. }
  20. }
  21. if (!done) {
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/721455
推荐阅读
相关标签
  

闽ICP备14008679号