精华内容
下载资源
问答
  • MMKV

    2020-11-04 10:19:28
    MMKV的优点?支持的数据类型包含功能使用方式参考地址 MMKV是什么? MMKV是腾讯的开源组件。 MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中...

    MMKV是什么?

    MMKV是腾讯的开源组件。

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

    MMKV的优点?

    1. 数据加密
      使用了 AES CFB-128 算法来加密/解密
    2. 多进程共享
    3. 匿名内存
    4. 性能更强
    5. 支持替代SP,替换简便

    支持的数据类型

    支持以下 Java 语言基础类型:

    1. boolean、int、long、float、double、byte[]
      支持以下 Java 类和容器:

    2. String、Set
      任何实现了Parcelable的类型

    包含功能

    1. 查询和删除
    2. 创建不同的功能实例,区分业务
    3. 创建多进程实例
    4. 自定义目录

    使用方式

    dependencies {
        implementation 'com.tencent:mmkv-static:1.2.4'
        // replace "1.2.4" with any available version
    }
    
    public void onCreate() {
        super.onCreate();
    
        String rootDir = MMKV.initialize(this);
        System.out.println("mmkv root: " + rootDir);
        //……
    }
    
    package com.dfsk.fengon.app.utils.cache;
    
    import android.os.Parcelable;
    
    import com.tencent.mmkv.MMKV;
    
    import java.util.Collections;
    import java.util.Set;
    
    /**
     * 转:https://blog.csdn.net/gpf1320253667/article/details/91352887
     */
    public class SpUtils {
    
        private static SpUtils mInstance;
        private static MMKV mv;
    
        private SpUtils() {
            mv = MMKV.defaultMMKV();
        }
    
        /**
         * 初始化MMKV,只需要初始化一次,建议在Application中初始化
         */
        public static SpUtils getInstance() {
            if (mInstance == null) {
                synchronized (SpUtils.class) {
                    if (mInstance == null) {
                        mInstance = new SpUtils();
                    }
                }
            }
            return mInstance;
        }
    
        /**
         * 保存数据的方法,我们需要拿到保存数据的具体类型,然后根据类型调用不同的保存方法
         *
         * @param key
         * @param object
         */
        public static void encode(String key, Object object) {
    
            if (object==null)return;
    
            if (object instanceof String) {
                mv.encode(key, (String) object);
            } else if (object instanceof Integer) {
                mv.encode(key, (Integer) object);
            } else if (object instanceof Boolean) {
                mv.encode(key, (Boolean) object);
            } else if (object instanceof Float) {
                mv.encode(key, (Float) object);
            } else if (object instanceof Long) {
                mv.encode(key, (Long) object);
            } else if (object instanceof Double) {
                mv.encode(key, (Double) object);
            } else if (object instanceof byte[]) {
                mv.encode(key, (byte[]) object);
            } else {
                mv.encode(key, object.toString());
            }
        }
    
        public static void encodeSet(String key, Set<String> sets) {
            if (sets==null)return;
            mv.encode(key, sets);
        }
    
        public static void encodeParcelable(String key, Parcelable obj) {
            if (obj==null)return;
            mv.encode(key, obj);
        }
    
    
        /**
         * 得到保存数据的方法,我们根据默认值得到保存的数据的具体类型,然后调用相对于的方法获取值
         */
        public static Integer decodeInt(String key) {
            return mv.decodeInt(key, 0);
        }
    
        public static Double decodeDouble(String key) {
            return mv.decodeDouble(key, 0.00);
        }
    
        public static Long decodeLong(String key) {
            return mv.decodeLong(key, 0L);
        }
    
        public static Boolean decodeBoolean(String key) {
            return mv.decodeBool(key, false);
        }
    
        public static Float decodeFloat(String key) {
            return mv.decodeFloat(key, 0F);
        }
    
        public static byte[] decodeBytes(String key) {
            return mv.decodeBytes(key);
        }
    
        public static String decodeString(String key) {
            return mv.decodeString(key, "");
        }
    
        public static Set<String> decodeStringSet(String key) {
            return mv.decodeStringSet(key, Collections.<String>emptySet());
        }
    
        public static Parcelable decodeParcelable(String key) {
            return mv.decodeParcelable(key, null);
        }
    
        /**
         * 移除某个key对
         *
         * @param key
         */
        public static void removeKey(String key) {
            mv.removeValueForKey(key);
        }
    
        /**
         * 清除所有key
         */
        public static void clearAll() {
            mv.clearAll();
        }
    
    }
    
    

    参考地址

    GITHUB:https://github.com/Tencent/MMKV/wiki/android_tutorial_cn
    转:MMKV:用来替代SharePreference的高性能key-value组件

    展开全文
  • MMKV_MMKV

    2020-12-22 14:44:14
    KVUtils忽喷public final class KVUtils {//MMKV.initialize(this); //初始化mmkv// // 增//mmkv.encode("token", token);//// 删//mmkv.removeValueForKey("token"); //删除单个//mmkv.removeValuesForKeys(new ...

    KVUtils

    忽喷

    public final class KVUtils {

    //MMKV.initialize(this); //初始化mmkv

    //    // 增

    //mmkv.encode("token", token);

    //mmkv.removeValueForKey("token");  //删除单个

    //mmkv.removeValuesForKeys(new String[]{"name", "token"}); //删除多个

    改 (在执行一次增操作)

    //mmkv.encode("token", token);

    //mmkv.decodeString("token");

    //    //使用默认的实例

    //    MMKV mmkv1 = MMKV.defaultMMKV();

    //    //创建自己的实例  参数1:库的key, 参数2:库的模式(多进程或单进程)

    //    MMKV mmkv2 = MMKV.mmkvWithID("user", MMKV.MULTI_PROCESS_MODE);

    //String

    //储存String

    public static boolean putString(String key, String value) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.encode(key, value);

    }

    //储存String 带id的

    public static boolean putString(String id, String key, String value) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.encode(key, value);

    }

    //拿出String  没有默认值的

    public static StringgetString(String key) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeString(key);

    }

    //拿出String  defaultValue=默认值

    public static StringgetString(String key, String defaultValue) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeString(key, defaultValue);

    }

    //拿出带id的String  defaultValue=默认值

    public static StringgetString(String id, String key, String defaultValue) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.decodeString(key, defaultValue);

    }

    //int

    //储存int

    public static boolean putint(String key, int value) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.encode(key, value);

    }

    //储存int 带id的

    public static boolean putint(String id, String key, int value) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.encode(key, value);

    }

    //拿出int  没有默认值的

    public static int getint(String key) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeInt(key);

    }

    //拿出int  defaultValue=默认值

    public static int getint(String key, int defaultValue) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeInt(key, defaultValue);

    }

    //拿出带id的int  defaultValue=默认值

    public static int getint(String id, String key, int defaultValue) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.decodeInt(key, defaultValue);

    }

    //Boolean

    //储存Boolean

    public static boolean putBoolean(String key, boolean value) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.encode(key, value);

    }

    //储存Boolean 带id的

    public static boolean putBoolean(String id, String key, boolean value) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.encode(key, value);

    }

    //拿出Boolean  没有默认值的

    public static BooleangetBoolean(String key) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeBool(key);

    }

    //拿出Boolean  defaultValue=默认值

    public static BooleangetBoolean(String key, boolean defaultValue) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeBool(key, defaultValue);

    }

    //拿出带id的Boolean  defaultValue=默认值

    public static BooleangetBoolean(String id, String key, boolean defaultValue) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.decodeBool(key, defaultValue);

    }

    //Byte

    //储存Byte

    public static boolean putByte(String key, byte[] value) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.encode(key, value);

    }

    //储存Byte 带id的

    public static boolean putByte(String id, String key, byte[] value) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.encode(key, value);

    }

    //拿出Byte  没有默认值的

    public static byte[]getByte(String key) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeBytes(key);

    }

    //拿出Byte  defaultValue=默认值

    public static  byte[]getByte(String key, byte[] defaultValue) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeBytes(key, defaultValue);

    }

    //拿出带id的Byte  defaultValue=默认值

    public static  byte[]getByte(String id, String key, byte[] defaultValue) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.decodeBytes(key, defaultValue);

    }

    //Double

    //储存Double

    public static boolean putDouble(String key, double value) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.encode(key, value);

    }

    //储存Double 带id的

    public static boolean putDouble(String id, String key, double value) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.encode(key, value);

    }

    //拿出Double  没有默认值的

    public static double getDouble(String key) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeDouble(key);

    }

    //拿出Double    defaultValue=默认值

    public static  double getDouble(String key, double defaultValue) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeDouble(key, defaultValue);

    }

    //拿出带id的Double    defaultValue=默认值

    public static  double getDouble(String id, String key, double defaultValue) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.decodeDouble(key, defaultValue);

    }

    //Float

    //储存Float

    public static boolean putFloat(String key, float value) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.encode(key, value);

    }

    //储存Float 带id的

    public static boolean putFloat(String id, String key, float value) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.encode(key, value);

    }

    //拿出Float  没有默认值的

    public static float getFloat(String key) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeFloat(key);

    }

    //Float    defaultValue=默认值

    public static  float getFloat(String key, float defaultValue) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeFloat(key, defaultValue);

    }

    //拿出带id的Float    defaultValue=默认值

    public static  float getFloat(String id, String key, float defaultValue) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.decodeFloat(key, defaultValue);

    }

    //Long

    //储存Long

    public static boolean putLong(String key, long value) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.encode(key, value);

    }

    //储存Long 带id的

    public static boolean putLong(String id, String key, long value) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.encode(key, value);

    }

    //拿出Long  没有默认值的

    public static float getLong(String key) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeLong(key);

    }

    //Long    defaultValue=默认值

    public static  float getLong(String key, long defaultValue) {

    MMKV mmkv = MMKV.defaultMMKV();

    return mmkv.decodeLong(key, defaultValue);

    }

    //拿出带id的Long    defaultValue=默认值

    public static  float getLong(String id, String key, long defaultValue) {

    MMKV mmkv = MMKV.mmkvWithID(id);

    return mmkv.decodeLong(key, defaultValue);

    }

    }

    展开全文
  • MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application. It's currently available on Android, iOS/macOS, Win32 and POSIX. MMKV for Android Features...
  • MMKV_MMKV简介

    2020-12-22 14:44:15
    内容来自官网MMKV——基于 mmap 的高性能通用 key-value 组件MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今,在 iOS 微信上使用已有近 ...

    内容来自官网

    MMKV——基于 mmap 的高性能通用 key-value 组件

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

    MMKV 源起

    在微信客户端的日常运营中,时不时就会爆发特殊文字引起系统的 crash,参考文章,文章里面设计的技术方案是在关键代码前后进行计数器的加减,通过检查计数器的异常,来发现引起闪退的异常文字。在会话列表、会话界面等有大量 cell 的地方,希望新加的计时器不会影响滑动性能;另外这些计数器还要永久存储下来——因为闪退随时可能发生。这就需要一个性能非常高的通用 key-value 存储组件,我们考察了 SharedPreferences、NSUserDefaults、SQLite 等常见组件,发现都没能满足如此苛刻的性能要求。考虑到这个防 crash 方案最主要的诉求还是实时写入,而 mmap 内存映射文件刚好满足这种需求,我们尝试通过它来实现一套 key-value 组件。

    MMKV 原理内存准备

    通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。

    数据组织

    数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。

    写入优化

    考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量 kv 对象序列化后,append 到内存末尾。

    空间增长

    使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。

    更详细的设计原理参考 MMKV 原理。

    iOS 指南

    安装引入

    推荐使用 CocoaPods:打开命令行, cd 到你的项目工程目录, 输入 pod repo update 让 CocoaPods 感知最新的 MMKV 版本;

    打开 Podfile, 添加 pod 'MMKV' 到你的 app target 里面;

    在命令行输入 pod install;

    用 Xcode 打开由 CocoaPods 自动生成的 .xcworkspace 文件;

    添加头文件 #import ,就可以愉快地开始你的 MMKV 之旅了。

    更多安装指引参考 iOS Setup。

    快速上手

    MMKV 的使用非常简单,无需任何配置,所有变更立马生效,无需调用 synchronize:

    MMKV *mmkv = [MMKV defaultMMKV];

    [mmkv setBool:YES forKey:@"bool"];

    BOOL bValue = [mmkv getBoolForKey:@"bool"];

    [mmkv setInt32:-1024 forKey:@"int32"];

    int32_t iValue = [mmkv getInt32ForKey:@"int32"];

    [mmkv setObject:@"hello, mmkv" forKey:@"string"];

    NSString *str = [mmkv getObjectOfClass:NSString.class forKey:@"string"];

    性能对比

    循环写入随机的int 1w 次,我们有如下性能对比:

    Android 指南

    安装引入

    推荐使用 Maven:

    dependencies {

    implementation 'com.tencent:mmkv:1.0.11'

    // replace "1.0.11" with any available version

    }

    快速上手

    MMKV 的使用非常简单,所有变更立马生效,无需调用 sync、apply。 在 App 启动时初始化 MMKV,设定 MMKV 的根目录(files/mmkv/),例如在 MainActivity 里:

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    String rootDir = MMKV.initialize(this);

    System.out.println("mmkv root: " + rootDir);

    //……

    }

    MMKV 提供一个全局的实例,可以直接使用:

    import com.tencent.mmkv.MMKV;

    //……

    MMKV kv = MMKV.defaultMMKV();

    kv.encode("bool", true);

    boolean bValue = kv.decodeBool("bool");

    kv.encode("int", Integer.MIN_VALUE);

    int iValue = kv.decodeInt("int");

    kv.encode("string", "Hello from mmkv");

    String str = kv.decodeString("string");

    MMKV for Android

    MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从2015年中至今,在 iOS 微信上使用已有 3 年,其性能和稳定性经过了时间的验证。

    使用指南

    MMKV 的使用非常简单,所有变更立马生效,无需调用 sync、apply。

    配置 MMKV 根目录

    在 App 启动时初始化 MMKV,设定 MMKV 的根目录(files/mmkv/),例如在 MainActivity 里:

    protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    String rootDir = MMKV.initialize(this);

    System.out.println("mmkv root: " + rootDir);

    }

    CRUD 操作MMKV 提供一个全局的实例,可以直接使用:

    import com.tencent.mmkv.MMKV;

    ...

    MMKV kv = MMKV.defaultMMKV();

    kv.encode("bool", true);

    System.out.println("bool: " + kv.decodeBool("bool"));

    kv.encode("int", Integer.MIN_VALUE);

    System.out.println("int: " + kv.decodeInt("int"));

    kv.encode("long", Long.MAX_VALUE);

    System.out.println("long: " + kv.decodeLong("long"));

    kv.encode("float", -3.14f);

    System.out.println("float: " + kv.decodeFloat("float"));

    kv.encode("double", Double.MIN_VALUE);

    System.out.println("double: " + kv.decodeDouble("double"));

    kv.encode("string", "Hello from mmkv");

    System.out.println("string: " + kv.decodeString("string"));

    byte[] bytes = {'m', 'm', 'k', 'v'};

    kv.encode("bytes", bytes);

    System.out.println("bytes: " + new String(kv.decodeBytes("bytes")));

    可以看到,MMKV 在使用上还是比较简单的。删除 & 查询:

    MMKV kv = MMKV.defaultMMKV();

    kv.removeValueForKey("bool");

    System.out.println("bool: " + kv.decodeBool("bool"));

    kv.removeValuesForKeys(new String[]{"int", "long"});

    System.out.println("allKeys: " + Arrays.toString(kv.allKeys()));

    boolean hasBool = kv.containsKey("bool");

    如果不同业务需要区别存储,也可以单独创建自己的实例:

    MMKV* mmkv = MMKV.mmkvWithID("MyID");

    mmkv.encode("bool", true);

    如果业务需要多进程访问,那么在初始化的时候加上标志位 MMKV.MULTI_PROCESS_MODE:

    MMKV* mmkv = MMKV.mmkvWithID("InterProcessKV", MMKV.MULTI_PROCESS_MODE);

    mmkv.encode("bool", true);

    支持的数据类型支持以下 Java 语言基础类型:boolean、int、long、float、double、byte[]

    支持以下 Java 类和容器:String、Set

    SharedPreferences 迁移MMKV 提供了 importFromSharedPreferences() 函数,可以比较方便地迁移数据过来。

    MMKV 还额外实现了一遍 SharedPreferences、SharedPreferences.Editor 这两个 interface,在迁移的时候只需两三行代码即可,其他 CRUD 操作代码都不用改。

    private void testImportSharedPreferences() {

    // SharedPreferences preferences = getSharedPreferences("myData", MODE_PRIVATE);

    MMKV preferences = MMKV.mmkvWithID("myData");

    // 迁移旧数据

    {

    SharedPreferences old_man = getSharedPreferences("myData", MODE_PRIVATE);

    preferences.importFromSharedPreferences(old_man);

    old_man.edit().clear().commit();

    }

    // 跟以前用法一样

    SharedPreferences.Editor editor = preferences.edit();

    editor.putBoolean("bool", true);

    editor.putInt("int", Integer.MIN_VALUE);

    editor.putLong("long", Long.MAX_VALUE);

    editor.putFloat("float", -3.14f);

    editor.putString("string", "hello, imported");

    HashSet set = new HashSet();

    set.add("W"); set.add("e"); set.add("C"); set.add("h"); set.add("a"); set.add("t");

    editor.putStringSet("string-set", set);

    // 无需调用 commit()

    //editor.commit();

    }

    MMKV for Android 多进程设计与实现

    前言

    将 MMKV 迁移到 Android 平台之后,很多同事反馈需要支持多进程访问——这在之前是没有考虑过的(因为 iOS 不支持多进程),需要进行全盘的设计和仔细的实现。

    IPC 选型

    说到 IPC,首要的问题就是架构选型,不同的架构效果大相径庭。

    CS 架构 vs 去中心化架构

    Android 平台第一个想到的就是 ContentProvider:一个单独进程管理数据,数据同步不易出错,简单好用易上手。然而它的问题也很明显,就是一个字慢:启动慢,访问也慢。这个可以说是 Android 下基于 Binder 的 CS 架构组件的通用痛点。至于其他的 CS 架构,例如经典的 socket、PIPE、message queue,因为要至少 2 次的内存拷贝,就更加慢了。

    MMKV 追求的是极致的访问速度,我们要尽可能地避免进程间通信,CS 架构是不可取的。再考虑到 MMKV 底层使用 mmap 实现,采用去中心化的架构是很自然的选择。我们只需要将文件 mmap 到每个访问进程的内存空间,加上合适的进程锁,再处理好数据的同步,就能够实现多进程并发访问。

    挑选进程锁

    然而去中心化的架构实现起来并不简单,Android 是个阉割版的 Linux,IPC 组件的支持比较残缺。例如,说到进程锁第一个想到的就是 pthread 库的 pthread_mutex,创建于共享内存的 pthread_mutex 是可以用作进程锁的,然而 Android 版的 pthread_mutex 并不保证robust,亦即对 pthread_mutex 加了锁的进程被 kill,系统不会进行清理工作,这个锁会一直存在下去,那么其他等锁的进程就会永远饿死。其他的 IPC 组件,例如信号量、条件变量,也有同样问题,Android 为了能够尽快关闭进程,真是无所不用其极。

    找了一圈,能够保证 robust 的,只有已打开的文件描述符,以及基于文件描述符的文件锁和 Binder 组件的死亡通知(是的,Binder 也是依赖这个清理机制运作,打开的文件是 /dev/binder)。

    我们有两个选择:文件锁,优点是天然 robust,缺点是不支持递归加锁,也不支持读写锁升级/降级,需要自行实现。

    pthread_mutex,优点是 pthread 库支持递归加锁,也支持读写锁升级/降级,缺点是不 robust,需要自行清理。

    关于 mutex 清理,有个可能的方案是基于 Binder 死亡通知进行清理:A、B进程相互注册对方的死亡通知,在对方死亡的时候进行清理。但有个比较棘手的场景:只有 A 进程存在,那么他的死亡通知就没人处理,留下一个永远加锁的 mutex。Binder 规定死亡通知不能本进程自行处理,必须由其他进程处理,所以这个问题不好解决。

    综合各种考虑,我们先将文件锁作为一个简单的互斥锁,进行 MMKV 的多进程开发,稍后再回头解决递归锁和读写锁升级/降级的问题。

    多进程实现细节

    首先我们简单回顾一下 MMKV 原来的逻辑。MMKV 本质上是将文件 mmap 到内存块中,将新增的 key-value 统统 append 到内存中;到达边界后,进行重整回写以腾出空间,空间还是不够的话,就 double 内存空间;对于内存文件中可能存在的重复键值,MMKV 只选用最后写入的作为有效键值。那么其他进程为了保持数据一致,就需要处理这三种情况:写指针增长、内存重整、内存增长。但首先还得解决一个问题:怎么让其他进程感知这三种情况?

    状态同步写指针的同步

    我们可以在每个进程内部缓存自己的写指针,然后在写入键值的同时,还要把最新的写指针位置也写到 mmap 内存中;这样每个进程只需要对比一下缓存的指针与 mmap 内存的写指针,如果不一样,就说明其他进程进行了写操作。事实上 MMKV 原本就在文件头部保存了有效内存的大小,这个数值刚好就是写指针的内存偏移量,我们可以重用这个数值来校对写指针。

    内存重整的感知

    考虑使用一个单调递增的序列号,每次发生内存重整,就将序列号递增。将这个序列号也放到 mmap 内存中,每个进程内部也缓存一份,只需要对比序列号是否一致,就能够知道其他进程是否触发了内存重整。

    内存增长的感知

    事实上 MMKV 在内存增长之前,会先尝试通过内存重整来腾出空间,重整后还不够空间才申请新的内存。所以内存增长可以跟内存重整一样处理。至于新的内存大小,可以通过查询文件大小来获得,无需在 mmap 内存另外存放。

    状态同步逻辑用伪码表达大概是这个样子:

    void checkLoadData() {

    if (m_sequence != mmapSequence()) {

    m_sequence = mmapSequence();

    if (m_size != fileSize()) {

    m_size = fileSize();

    // 处理内存增长

    } else {

    // 处理内存重整

    }

    } else if (m_actualSize != mmapActualSize()) {

    auto lastPosition = m_actualSize;

    m_actualSize = mmapActualSize();

    // 处理写指针增长

    } else {

    // 什么也没发生

    return;

    }

    }

    写指针增长

    当一个进程发现 mmap 写指针增长,就意味着其他进程写入了新键值。这些新的键值都 append 在原有写指针后面,可能跟前面的 key 重复,也可能是全新的 key,而原写指针前面的键值都是有效的。那么我们就要把这些新键值都读出来,插入或替换原有键值,并将写指针同步到最新位置。

    auto lastPosition = m_actualSize;

    m_actualSize = mmapActualSize();

    // 处理写指针增长

    auto bufferSize = m_actualSize - lastPosition;

    auto buffer = Buffer(lastPosition, bufferSize);

    map dictionary = decodeMap(buffer);

    for (auto& itr : dictionary) {

    // m_cache 还是有效的

    m_cache[itr.first] = itr.second;

    }

    内存重整

    当一个进程发现内存被重整了,就意味着原写指针前面的键值全部失效,那么最简单的做法是全部抛弃掉,从头开始重新加载一遍。

    // 处理内存重整

    m_actualSize = mmapActualSize();

    auto buffer = Buffer(0, m_actualSize);

    m_cache = decodeMap(buffer);

    内存增长

    正如前文所述,发生内存增长的时候,必然已经先发生了内存重整,那么原写指针前面的键值也是统统失效,处理逻辑跟内存重整一样。

    文件锁

    到这里我们已经完成了数据的多进程同步工作,是时候回头处理锁事了,亦即前面提到的递归锁和锁升级/降级。递归锁

    意思是如果一个进程/线程已经拥有了锁,那么后续的加锁操作不会导致卡死,并且解锁也不会导致外层的锁被解掉。对于文件锁来说,前者是满足的,后者则不然。因为文件锁是状态锁,没有计数器,无论加了多少次锁,一个解锁操作就全解掉。只要用到子函数,就非常需要递归锁。

    锁升级/降级

    锁升级是指将已经持有的共享锁,升级为互斥锁,亦即将读锁升级为写锁;锁降级则是反过来。文件锁支持锁升级,但是容易死锁:假如 A、B 进程都持有了读锁,现在都想升级到写锁,就会陷入相互等待的困境,发生死锁。另外,由于文件锁不支持递归锁,也导致了锁降级无法进行,一降就降到没有锁。

    为了解决这两个难题,需要对文件锁进行封装,增加读锁、写锁计数器。处理逻辑如下表:

    需要注意的地方有两点:加写锁时,如果当前已经持有读锁,那么先尝试加写锁,try_lock 失败说明其他进程持有了读锁,我们需要先将自己的读锁释放掉,再进行加写锁操作,以避免死锁的发生。

    解写锁时,假如之前曾经持有读锁,那么我们不能直接释放掉写锁,这样会导致读锁也解了。我们应该加一个读锁,将锁降级。

    MMKV 多进程性能

    写了个简单的测试,创建两个 Service,测试 MMKV、MultiProcessSharedPreferences、SQLite 多进程读写的性能,具体代码见 git repo。

    测试环境:Pixel 2 XL 64G, Android 8.1.0,单位:ms。每组测试分别循环 1000 次;MultiProcessSharedPreferences 使用 apply() 同步数据;SQLite 打开 WAL 选项。

    展开全文
  • MMKV_MMKV使用教程

    2020-12-22 14:44:14
    本文主要是提供MMKV的代码使用教程,若是想知道更多关于MMKV的信息,能够点击如下连接,查看更多信息:html2.MMKV浅析githubMMKV实际使用的代码案例以下:- (BOOL)application:(UIApplication *)application ...

    本文主要是提供MMKV的代码使用教程,若是想知道更多关于MMKV的信息,能够点击如下连接,查看更多信息:html

    2. MMKV浅析github

    MMKV实际使用的代码案例以下:

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    // Override point for customization after application launch.

    [MMKV setLogLevel:MMKVLogNone];//关闭MMKV控制台打印的自带的辅助日志信息

    [self kvCreateMethod];//建立MMKV的方法

    [self compareMMKVWithNSUesrDefault];//比较MMKV和NSUserDefaults存储数据的效率

    [self cLanguageDataType];//存储和读取C语言数据类型

    [self ocLanguageDataType];//存储和读取OC语言数据类型

    [self kvMigrateFromUserDefaults];//从NSUserDefaults迁移数据到MMKV

    // [[MMKV defaultMMKV] clearAll];

    //totalSize:文件分配的总磁盘空间4k;actualSize:存储文件占据的实际存储空间298byte

    NSLog(@"count:%zu-----totalSize:%zu------actualSize:%zu-----allKeys:%@",[[MMKV defaultMMKV] count],

    [[MMKV defaultMMKV] totalSize],[[MMKV defaultMMKV] actualSize],[[MMKV defaultMMKV] allKeys]);

    //打印结果:count:7-----totalSize:4096------actualSize:298-----allKeys:(double,uint64,uint32,int32,bool,int64,float)

    return YES;

    }复制代码

    建立MMKV的方法:ide

    /*

    //如下四种建立方法是彻底等价的,建立的是同一个MMKV实例

    */

    - (void)kvCreateMethod

    {

    //MMKV文件存储的默认为~/Documents/mmkv

    NSString *basePath = [MMKV mmkvBasePath];

    //修改文件存储的默认路径,在建立MMKV实例以前设置

    [MMKV setMMKVBasePath:@""];

    //第一种建立方法

    MMKV *defaultKV1 = [MMKV defaultMMKV];

    //第二种建立方法

    MMKV *defaultKV3 = [MMKV mmkvWithID:@"mmkv.default"];

    //第三种建立方法

    NSString *string = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

    string = [string stringByAppendingPathComponent:@"mmkv"];

    MMKV *defaultKV4 = [MMKV mmkvWithID:@"mmkv.default" relativePath:string];

    //第四种建立方法

    MMKV *defaultKV2 = [MMKV mmkvWithID:@"mmkv.default" cryptKey:nil relativePath:string];

    }复制代码

    比较MMKV和NSUserDefaults存储数据的效率:

    ui

    /*

    写入10万个数据所用的时间

    MMKV : 198.487043ms

    NSUserDefaults : 13829.244971ms

    */

    - (void)compareMMKVWithNSUesrDefault

    {

    CFAbsoluteTime startTime =CFAbsoluteTimeGetCurrent();

    MMKV *customKV = [MMKV mmkvWithID:@"cn.meicai"];

    for (int i=0; i<100000; i++) {

    [customKV setInt32:i forKey:@"int32"];

    }

    CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);

    NSLog(@"Linked in %f ms", linkTime *1000.0);

    NSLog(@"------%d--------",[customKV getInt32ForKey:@"int32"]);

    // CFAbsoluteTime startTime =CFAbsoluteTimeGetCurrent();

    // NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];

    // for (int i=0; i<100000; i++) {

    // [userDefault setInteger:i forKey:@"int32"];

    // }

    // CFAbsoluteTime linkTime = (CFAbsoluteTimeGetCurrent() - startTime);

    // NSLog(@"Linked in %f ms", linkTime *1000.0);

    // NSLog(@"------%ld--------",(long)[userDefault integerForKey:@"int32"]);

    }

    复制代码

    存储和读取C语言数据类型:

    加密

    /*

    //支持如下 C 语语言基础类型:

    //bool、int3二、int6四、uint3二、uint6四、float、double

    */

    - (void)cLanguageDataType

    {

    MMKV *mmkv = [MMKV defaultMMKV];

    [mmkv setBool:YES forKey:@"bool"];

    NSLog(@"bool:%d", [mmkv getBoolForKey:@"bool"]);//打印结果:bool:1

    [mmkv setInt32:-1024 forKey:@"int32"];

    NSLog(@"int32:%d", [mmkv getInt32ForKey:@"int32"]);//打印结果:int32:-1024

    [mmkv setUInt32:std::numeric_limits::max() forKey:@"uint32"];

    NSLog(@"uint32:%u", [mmkv getUInt32ForKey:@"uint32"]);//打印结果:uint32:4294967295

    [mmkv setInt64:std::numeric_limits::min() forKey:@"int64"];

    NSLog(@"int64:%lld", [mmkv getInt64ForKey:@"int64"]);//打印结果:int64:-9223372036854775808

    [mmkv setUInt64:std::numeric_limits::max() forKey:@"uint64"];

    NSLog(@"uint64:%llu", [mmkv getInt64ForKey:@"uint64"]);//打印结果:uint64:18446744073709551615

    [mmkv setFloat:-3.1415926 forKey:@"float"];

    NSLog(@"float:%f", [mmkv getFloatForKey:@"float"]);//打印结果:float:-3.141593

    [mmkv setDouble:std::numeric_limits::max() forKey:@"double"];

    NSLog(@"double:%f", [mmkv getDoubleForKey:@"double"]);//打印结果:double:17976931348......24858368.000000

    }复制代码

    存储和读取OC语言数据类型:

    spa

    /*

    支持如下 ObjC 类型:

    NSString、NSData、NSDate

    */

    - (void)ocLanguageDataType

    {

    //cryptKey 根据此秘钥建立AES加密器,加密后的数据存取方法不变,和未加密的同样

    MMKV *mmkv = [MMKV mmkvWithID:@"cn.meicai" cryptKey:[@"crypt" dataUsingEncoding:NSUTF8StringEncoding]];

    [mmkv setString:@"hello, mmkv" forKey:@"string"];

    NSLog(@"string:%@ defaultValue:%@", [mmkv getStringForKey:@"string"],[mmkv getStringForKey:@"string111" defaultValue:@"mmmmmmmmmmmmmmmm"]);

    //打印结果:string:hello, mmkv defaultValue:mmmmmmmmmmmmmmmm

    [mmkv setObject:nil forKey:@"string"];

    NSLog(@"string after set nil:%@, containsKey:%d",

    [mmkv getObjectOfClass:NSString.class

    forKey:@"string"],

    [mmkv containsKey:@"string"]);

    //打印结果:string after set nil:(null), containsKey:0

    [mmkv setDate:[NSDate date] forKey:@"date"];

    NSLog(@"date:%@ defaultValue:%@", [mmkv getDateForKey:@"date"],[mmkv getDateForKey:@"date111" defaultValue:[NSDate date]]);

    //打印结果:date:Wed Jun 26 14:39:52 2019 defaultValue:Wed Jun 26 14:39:52 2019

    [mmkv setData:[@"hello, mmkv again and again" dataUsingEncoding:NSUTF8StringEncoding] forKey:@"data"];

    NSData *data = [mmkv getDataForKey:@"data"];

    NSLog(@"data:%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    //打印结果:data:hello, mmkv again and again

    }复制代码

    从NSUserDefaults迁移数据到MMKV:

    /*

    从NSUserDefault迁移数据到MMKV的实例

    */

    - (void)kvMigrateFromUserDefaults

    {

    [[NSUserDefaults standardUserDefaults] setObject:@"hello world" forKey:@"string"];

    [[NSUserDefaults standardUserDefaults] synchronize];

    MMKV *userDefaultKV = [MMKV mmkvWithID:@"NSUserDefault"];

    [userDefaultKV migrateFromUserDefaults:[NSUserDefaults standardUserDefaults]];

    [userDefaultKV enumerateKeys:^(NSString * _Nonnull key, BOOL * _Nonnull stop) {

    if ([key isEqualToString:@"string"]) {

    NSLog(@"string value is : %@",[userDefaultKV getStringForKey:key]);//打印结果:string value is : hello world

    NSLog(@"string value is : %@",[userDefaultKV getObjectOfClass:[NSString class] forKey:key]);//打印结果:string value is : hello world

    NSLog(@"string value is : %@",[userDefaultKV getObjectOfClass:[NSNumber class] forKey:key]);//打印结果:string value is : (null)

    *stop = YES;

    }

    }];

    }复制代码

    展开全文
  • MMKV.zip

    2021-01-10 22:32:37
    MMKV存hello world navive!
  • 一、首先看一下介绍(mmkv官方介绍)MMKV——基于 mmap 的高性能通用 key-value 组件MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。MMKV 源起在微信客户端...
  • MMKV记录

    2020-05-28 14:43:36
    Android 指南 安装引入 推荐使用 Maven: dependencies { implementation 'com.tencent:mmkv-static:1.1.1' // replace "1.1.1" with any available ...在 App 启动时初始化 MMKV,设定 MMKV 的根目录(files/mmkv
  • 什么是mmkvmmkv 是基于 mmap 内存映射的移动端通用 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。https://github.com/tencent/mmkv为什么要替代sharedpreferences?首先 安全性好。...
  • MMKV使用

    2021-03-08 10:39:59
    前述:mmkv是腾讯开源的持久化第三方库,可以用来替代以往的SP,性能大大的好,使用方法如下。 1.添加依赖:implementation 'com.tencent:mmkv-static:1.1.2' 这里使用static可以减少库的大小。 2.在application中...
  • MMKV_MMKV——1.使用

    2020-12-22 14:44:10
    前言MMKV是有腾讯开发的高性能key-value组件,可以完美替代SharedPreferences。项目地址:https://github.com/Tencent/MMKV使用1.安装引入dependencies {implementation 'com.tencent:mmkv-static:1.1.1'// "1.1.1" ...
  • 高性能MMKV

    2019-02-27 14:31:59
    android高性能MMKV 比sharedperference好用 适合初学者学习
  • MMKV_mmkv之基本介绍

    2020-12-22 14:44:14
    MMKV浅析​ MMKV 是微信开源的一个基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。微信团队为了发现记录特殊文字引起微信 iOS 系统的 crash,在关键代码前后进行...
  • Android MMKV

    千次阅读 2020-09-30 10:06:56
    MMKV 是基于 mmap 内存映射的移动端通用 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今,在 iOS 微信上使用已有近 3 年,其性能和稳定性经过了时间的验证。近期已...
  • MMKV_MMKV 原理以及使用

    2020-12-22 14:44:13
    介绍MMKV是基于mmap内存映射的移动端通用key-value组件,底层序列化/反序列化使用protobuf实现,性能高,稳定性强。从2015年中至今,在iOS微信上使用已有近3年,近期移植到Android平台,移动端全平台通用,并全部在...
  • MMKV_浅析 - MMKV 1.1.1

    2020-12-31 05:42:16
    介绍MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application. It's currently available on Android, iOS/macOS, Win32 and POSIX.作为一个精简易用且性能...
  • MMKV_数据持久化-mmkv

    2020-12-29 16:57:08
    MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。近期也已移植到 Android / macOS / Win...
  • MMKV整理学习

    万次阅读 2018-09-26 15:58:08
    MMKV相关知识
  • MMKV的使用

    2020-10-15 23:18:03
    MMKV的使用
  • MMKV简介 腾讯微信团队于2018年9月底宣布开源 MMKV ,这是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,主打高性能和稳定性。近期也已移植到 Android 平台,一并对外开源。 MMKV ...
  • Android组件化架构热卖中组件化群1已经满员,进来的可以加群2 763094035MMKV框架初始化MMKV.initialize(this);public static String initialize(Context context) {//创建存储目录rootDir = context.ge...
  • MMKV原理详解

    2020-09-30 16:19:25
    我们将 MMKV 和 SharedPreferences、SQLite 进行对比, 重复读写操作 1k 次。相关测试代码在Android/MMKV/mmkvdemo/。结果如下图表。 单进程性能 可见,MMKV 在写入性能上远远超越 SharedPreferences &...
  • MMKV基本使用与源码解析

    万次阅读 2020-08-17 14:48:11
    MMKV 概述 1. MMKV——基于 mmap 的高性能通用 key-value 组件 MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能...
  • 应用MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application. It's currently available on Android, iOS/macOS, Win32 and POSIXmmap优点:1. 减少memory ...
  • 一、前言:MMKV 是腾讯开源的一款基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强,从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。GitHub地址:...
  • MMKV基本使用

    千次阅读 2020-03-30 16:38:46
    首先在app创建时初始化: public class BaseApplication extends Application { @Override public void onCreate() { ... MMKV.initialize(this); //初始化mmkv } } 然后创建MMKV: //使用默认...
  • MMKV的介绍MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今在微信上使用,其性能和稳定性经过了时间的验证。关于mmap内存映射mmap是一种...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 539
精华内容 215
关键字:

MMKV