IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> MMKV 原理剖析1 -> 正文阅读

[移动开发]MMKV 原理剖析1

前言

MMKV开源地址

MMKV内部有很多精妙的设计思想,本文作为第一章仅仅介绍大致架构

我们看下MMKV优势:

  1. 序列化的文件体积更小(采用protobuf变长编码实现)
  2. 多进程感知和同步(主要针对android端,采用文件锁实现同步,文件头部写版本号偏移信息等)
  3. 写入时IO操作更快(追加写入和mmap)

protobuf变长编码

假设我们有一个int(32位)类型需要存储,那么我们真的有必要4字节进行存储?

假设我们有两个如下变量:

int a0 = 0x1; //对应二进制  0000  0001
int a1 = 0x2 //对应二进制   0000  0010
int a2 = 0xff; 对应二进制   1111  1111 

假设我们存储都以上非常小的数据真的需要完整存4字节?以上a0 a1 a2只需一字节即可满足需求。

Googleprotobuf库就采用了变长编码。
其大致思想每一个字节第一位表示后面是否还需要一字节去表示一个数字。

举个例子:

int c0 = 2;   //对应二进制  0000 0000 0000 0000 0000 0000 0000 0010
int c1 = 300F; //对应二进制 0000 0000 0000 0000 0000 0001 0010 1100

0x2 在原始存储结构和protobuf存储结构差异:
在这里插入图片描述

300 在原始存储结构和protobuf存储结构差异(您看到官方文档可能会看到大小端转化,这里仅仅为了说明思想):
在这里插入图片描述

你会发现300这个数字在protobuf中只需要两个字节。

这里你可能会思考对于负数怎么处理?
比如-1对应的二进制为11111111这种时候变长编码反而成为一种负担。

对于负数Protobuf提供了一个Zigtag编码思想来处理,因为本文重点不是在这所以简单介绍下:

Zigtag算法思想:
假设我们有一个函数f(x)可以将负数转化为对应正数并且保证负数越小转化为的正数也是越小,再用生产数字进行变长编码即可。接收方收到这个类型的变量进行逆函数操作即可

举个例子
f(-1)=1 ,f(-2)=3
-1会转化为1,然后进行变长编码
-2回转为3,然后在进行变长编码

protobuf中使用sint32sint64就是Zigtag算法,所以合理使用数据类型也是优化哦

如果你想了解更多可参阅下面文献:

  1. https://www.jianshu.com/p/73c9ed3a4877
  2. https://developers.google.com/protocol-buffers/docs/encoding
  3. https://www.cnblogs.com/en-heng/p/5570609.html

在MMKV中的源码中就是使用了对应的protobuf源码实现,下面我简单看下即可

//CodedOutputData.cpp
void CodedOutputData::writeRawVarint32(int32_t value) {
    while (true) {
        if ((value & ~0x7f) == 0) {
            this->writeRawByte(static_cast<uint8_t>(value));
            return;
         } else {
            this->writeRawByte(static_cast<uint8_t>((value & 0x7F) | 0x80));
            value = logicalRightShift32(value, 7);
        }
    }
}
//CodedInputData.cpp
int32_t CodedInputData::readRawVarint32() {
    int8_t tmp = this->readRawByte();
    if (tmp >= 0) {
        return tmp;
    }
    int32_t result = tmp & 0x7f;
    if ((tmp = this->readRawByte()) >= 0) {
        result |= tmp << 7;
    } else {
        result |= (tmp & 0x7f) << 7;
        if ((tmp = this->readRawByte()) >= 0) {
            result |= tmp << 14;
        } else {
            result |= (tmp & 0x7f) << 14;
            if ((tmp = this->readRawByte()) >= 0) {
                result |= tmp << 21;
            } else {
                result |= (tmp & 0x7f) << 21;
                result |= (tmp = this->readRawByte()) << 28;
                if (tmp < 0) {
                    // discard upper 32 bits
                    for (int i = 0; i < 5; i++) {
                        if (this->readRawByte() >= 0) {
                            return result;
                        }
                    }
                    throw invalid_argument("InvalidProtocolBuffer malformed varint32");
                }
            }
        }
    }
    return result;
}

追加写入

Android使用原始SP作为持久化方案时,默认更新一个数据会导致整个文件重写。而MMKV的方案时追缴新的内容在文件末端。

举个例子:
我们本地有一个文件xx.xml存储数据

在这里插入图片描述
假设我们现在需要修改key1内容为hello 在SP下会完整重写整个文件进行覆盖,这种方式简单粗暴容易管理但是效率很低。

在这里插入图片描述
在MMKV下
在这里插入图片描述
MMKV在末尾追缴一个同key数据,读取时以后者为准。在多次更新后MMKV会进行文件整理操作。

状态同步

我们假设我们有多个进程使用MMKV,A进程使用MMKV更新数据,B进程进行读取那么是否可以读取最新的数据?
MMKV会在更新时会在文件头部写版本号和有效内容的大小,另一个进程读取数据只需要判断版本号和有效内容,假设版本号不对那么只需要移动文件指针读取新数据即可。
在这里插入图片描述
在这里插入图片描述

零拷贝

我们读取文件都需要内核拷贝,而MMKV利用linux mmap进行文件映射来减少内核拷贝操作。
关于这块可以参考博其他文章:https://fanmingyi.blog.csdn.net/article/details/114762889

在这里插入图片描述

进程同步

我们假设A进程和B进程同时写入MMKV文件怎么办?MMKV采用文件锁的方式解决。
很多读者会看到官方说本想pthread_mutex进行同步但是android是linux阉割版本所以采用文件锁。

这里简单介绍linux多线程知识,假设我们有两个线程A和B,如果有一个互斥量pthread_mutex_t被线程A持有,
但是线程A突然意外死亡,会导致pthread_mutex_t不会释放,介于此linux提供了一个相关api解决了这个问题。

代码如下:

#include <iostream>
#include <pthread.h>
#include <unistd.h>

pthread_mutex_t mutex;
using namespace std;
void * childThreadRun(void *arg){
    pthread_mutex_lock(&mutex);
    cout<<"Child thread is dead"<<endl;
    return 0;
}


int main() {



    pthread_mutexattr_t  arr;
    pthread_mutexattr_init(&arr);
    //提供一个死亡线程自动释放互斥锁功能
    pthread_mutexattr_setrobust(&arr,PTHREAD_MUTEX_ROBUST);
    pthread_mutex_init(&mutex,&arr);

    pthread_t tid;
    pthread_create(&tid,NULL,childThreadRun,NULL);

    sleep(3);

    int result= pthread_mutex_lock(&mutex);

    cout<<"result "<<result<<endl;

    std::cout << "Hello, World!" << std::endl;
    return 0;
}

在Android中是没有pthread_mutexattr_setrobust这个函数,假设a进程出现这类情况会导致整个MMKV瘫痪

MMKV文档介绍的锁升级是利用文件锁做的一层封装,具体后面在分析把

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 11:48:06  更:2021-07-25 11:49:44 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/20 4:29:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码