Android存储方案对比(SharedPreferences 、 MMKV 、 DataStore)

简介:本文介绍了Android开发中常用的键值对存储方案,包括SharedPreferences、MMKV和DataStore,并且对比了它们在性能、并发处理、易用性和稳定性上的特点。通过实际代码示例,帮助开发者根据项目需求选择最适合的存储方案,提升应用性能和用户体验。

 在Android开发中,键值对存储(Key-Value Strorage)是一种经常用到的轻量级数据存储方案。它用于保存一些简单的配置数据或状态信息,例如用户设置、缓存数据等。

常见的键值对存储方案

SharedPreferences

SharedPreferences是Android系统提供的一种轻量级的持久化存储类,使用键值对的形式保存数据;可以存储的数据类型包括String、int、boolean、float和long;简单易用,但在高并发写操作下性能较差,会造成主线程阻塞问题。

SharedPreferences sharedPreferences = getSharedPreferences("my_preferences", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("username", "John");
editor.putInt("age", 30);
editor.apply();  // apply() 异步保存数据,commit() 是同步操作

特点:

存储方式:键值对(Key-Value Pairs),数据以XML文件储存。

使用方式:通常用于存储简单的用户设置、偏好设置等,适合存储少量的数据。

线程安全:在多线程的环境中操作时,需要特别注意同步问题。SharedPreferences本身是线程安全的,但你在修改数据时,可能需要使用apply()或commit()进行操作。

性能:性能较差,尤其是当存储的数据量较大时,因为它是基于文件的,数据时以文本格式存储的,且需要频繁的磁盘IO操作。

 优点:

使用简单,不丢数据

使用非常方便,能确保数据的一致性,适合不频繁读写一些重要的数据。

缺点:

SP不能保证类型安全

获取数据的时候可能出现ClassCastException异常,因为使用相同的KEY调用put()保存不同类型的数据时会覆盖之前保存的数据类型。

SP加载的数据会一直留在内存中

使用getSharedPreferences()方法加载数据会将数据存储在静态的成员变量中,然后通过静态的ArrayMap缓存每一个SP文件,而每个SP文件内容通过Map缓存键值对数据,这样数据会一直留在内存中,浪费内存。

不支持多线程

SP不支持跨进程通信;代码中可以看到当使用多进程MODE_MULTI_PROCESS操作的时候,会重新读取SP文件内容。

    @Overridepublic SharedPreferences getSharedPreferences(File file, int mode) {SharedPreferences sp;synchronized (ContextImpl.class){final ArrayMap<File, SharedPreferencesImpl> cache = getSharedPreferencesCacheLocked();sp = cache.get(file);if(sp == null){checkMode(mode);if(getApplicationInfo().tragetSdkVersion >= android.os.Build.VERSION_CODES.O){if(isCredentialProtectedStorage() && !getSystemService(UserManager.class).isUserUnlockingOrUnlocked(UserHandle.myUserId())){throw new IllegalStateException("Credential protected storage is not available");}}sp = new SharedPreferencesImpl(file, mode);cache.put(file, sp);return sp;}}if((mode & Context.MODE_MULTE_PROCESS) != 0 || getApplicationInfo().targetSdkVersion < android.os.Build.VERSION_CODES.HONEYCOMB){// If somebody else (some other process) changed the prefs// file behind our back, we reload it. This has been the historical (if undocumented) behavior.sp.startReloadIfChangedUnexpectedly();}return sp;}

读写性能差,可能引起ANR

 读取数据的时候虽然加载文件也是异步加载的,不过sp.get()方法是同步的,如果代码在它加载完成之前就去尝试读取键值对,线程就会阻塞,直到文件加载完成,此时如果在主线程操作的话,就会造成界面卡顿。

写入数据时SP可以通过apply()异步的方式来保存更改避免I/O操作所导致的主线程的耗时,但当Activity启动和关闭的时候会等待这些异步提交完成保存之后,这就相当于把异步操作转换成同步操作了,从而会导致卡顿甚至ANR。当然这些操作也是为了能保证数据安全一致而为之。

具体ANR引起原因可以参考:剖析 SharedPreference apply 引起的 ANR 问题。

MMKV

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

总的来说,MMKV使用mmap内存映射文件,极大提高了读写性能,并且支持多进程读写;完全替代SharedPreferences,有一致的API使用体验;提供分布式存储、数据加密等功能。

依赖配置

implementation 'com.tencent:mmkv-static:1.2.10'

初始化和使用

import com.tencent.mmkv.MMKVclass MyApplication : Application() {override fun onCreate() {super.onCreate()MMKV.initialize(this)}
}fun saveData(key: String, value: String) {val kv = MMKV.defaultMMKV()kv.encode(key, value)
}fun getData(key: String): String? {val kv = MMKV.defaultMMKV()return kv.decodeString(key)
}
MMKV mmkv = MMKV.defaultMMKV();
mmkv.putString("username", "John");
mmkv.putInt("age", 30);
String username = mmkv.getString("username", "default_value");
int age = mmkv.getInt("age", 0);

MMKV源起

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

MMKV原理

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

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

写入优化:考虑到主要使用场景是频繁的写入更新,所以需要有增量更新的能力。因此考虑将增量kv对象序列化后,append到内存末尾。

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

更详细的设计原理可以参考design · Tencent/MMKV Wiki · GitHub文档。

特点:

存储方式:MMKV基于内存映射文件(Memory-Mapped File)实现,数据存储在磁盘上,但是可以直接从内存访问,避免了频繁的磁盘I/O操作。

性能:相比于SharedPreferences,MMKV提供了更高的性能,尤其是在大量数据存储和访问的场景中表现更好。

加密支持:支持加密,可以加密存储的数据,适用于需要加密保护的场景。

线程安全:MMKV是线程安全的,多线程可以同时访问。

易用性:API使用方式与SharedPreferences类似,迁移成本较低。

优点:

支持多进程

如果需要多进程通信,那暂时就只能用MMKV了。

"快"

单进程性能

MMKV &  SharedPreferences & SQLite读写速度对比:

MMKV 在写入性能上远远超越 SharedPreferences & SQLite,在读取性能上也有相近或超越的表现。

多线程性能

MMKV &  SharedPreferences & SQLite读写速度对比:

可见,MMKV无论是在写入性能还是在读取性能都要远远超越SharedPreferences 和 SQLite,MMKV在Android多进程key-value存储组件上是不二之选。

缺点:

写入大数据速度较慢

当使用MMKV写入大的字符串数据时,相比于SP和DataStore会慢一些,但是开发中基本不会写入那么大的字符串。

可能会丢数据

当设备突然断电关机等意外现象时,刚好数据保存在一半的情况下,此时文件就会发生损坏。这种问题是不可避免的,MMKV的底层机制在断电关机之类的操作系统级别的崩溃,没有做备份还原操作,数据就会损失重置;MMKV底层的原理是内存映射,它不是实时的将内存中的数据写入到磁盘中,会有一定的滞后性,MMKV定位于高频写入可能这就是它不实时写入磁盘的原因吧。

而SharedPreferences和DataStore的应对方式是在每次写入新数据之前都对现有文件做一次自动备份,这样在发生意外出现文件损坏之后,它们机会把备份的数据恢复过来。

 DataStore

Google提供的现代化数据存储解决方案。分为Preferences DataStore 和 Proto DataStore两类,前者也是基于键值对的存储,后者基于ProtoBuf。用Kotlin协程和Flow实现异步、响应式编程;类型安全、无业务侵入,支持直接保存对象。

依赖配置

implementation "androidx.datastore:datastore-preferences:1.0.0"

Preferences DataStore

import androidx.datastore.preferences.core.*
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.mapprivate val Context.dataStore by preferencesDataStore("settings")object PreferencesKeys {val EXAMPLE_KEY = stringPreferencesKey("example_key")
}suspend fun saveData(context: Context, value: String) {context.dataStore.edit { preferences ->preferences[PreferencesKeys.EXAMPLE_KEY] = value}
}fun getData(context: Context): Flow<String?> {return context.dataStore.data.map { preferences ->preferences[PreferencesKeys.EXAMPLE_KEY]}
}
// 定义一个 Preferences
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")// 存储数据
val usernameKey = stringPreferencesKey("username")
val ageKey = intPreferencesKey("age")suspend fun saveData(context: Context) {context.dataStore.edit { preferences ->preferences[usernameKey] = "John"preferences[ageKey] = 30}
}// 获取数据
suspend fun readData(context: Context): String? {val preferences = context.dataStore.data.first()return preferences[usernameKey]
}

Proto DataStore

// Proto 文件
message UserSettings {string username = 1;int32 age = 2;
}// Proto DataStore 用法
val userSettingsDataStore: DataStore<UserSettings> = context.createDataStore(fileName = "user_settings.pb",serializer = UserSettingsSerializer
)// 存储数据
suspend fun saveProtoData() {userSettingsDataStore.updateData { currentSettings ->currentSettings.toBuilder().setUsername("John").setAge(30).build()}
}// 获取数据
suspend fun readProtoData(): UserSettings? {return userSettingsDataStore.data.first()
}

特点:

异步操作:DataStore 是完全基于 Kotlin 协程的,操作是异步的,避免了阻塞主线程的问题。

类型安全:支持使用类型安全的 Proto 数据格式来存储结构化的数据,避免了在 SharedPreferences 中手动转换数据类型的麻烦。

迁移支持:从 SharedPreferences 迁移到 DataStore 方便且有帮助,尤其是对于大部分简单的键值对存储需求。

优点:

性能高、不卡顿、不丢数据

DataStroe基于Kotlin协程实现和使用,官方主推性能,主线程读写(不管大小)数据都不卡顿(MMKV读写长字符串时可能会发生卡顿)。

官方站台主推数据存储方案

官方代替SharedPreferences方案,SP有的基础上并优化了性能问题,选择存储方案时应该优先考虑。

缺点:

不支持多进程

暂时不支持多进程。

需要支持KT协程

DataStroe基于Kotlin协程实现和使用,如果你的项目还是纯Java的话,还是用SP忍一忍吧。

总结对比:

特性SharedPreferencesMMKVJetpack DataStore
存储方式键值对,XML 文件键值对,内存映射文件键值对(Preferences),协议缓冲(Proto)
性能较差,尤其是在数据量较大时高效,支持大规模数据高效,支持异步操作
线程安全需要手动同步操作默认线程安全默认线程安全
加密支持不支持支持不支持(需要额外处理)
支持结构化数据存储不支持不支持支持(Proto DataStore)
API 设计简单,传统简单,基于 SharedPreferences 类似更现代,基于 Kotlin 协程和流
使用场景存储简单设置和小型数据高性能存储,尤其适用于需要处理大量数据的场景适用于需要异步操作的场景,支持结构化数据

结论:

  • SharedPreferences:适合存储小型、简单的配置信息,操作简单,适用于老旧项目。
  • MMKV:适用于对性能有较高要求的应用,特别是数据量较大时,性能优越,且支持加密。
  • Jetpack DataStore:推荐用于现代 Android 应用,尤其是需要异步处理或结构化数据存储的场景,支持 Kotlin 协程和流。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/66404.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于 GEE Sentinel-1 数据集提取水体

目录 1 水体提取原理 2 完整代码 3 运行结果 1 水体提取原理 水体提取是地理信息和遥感技术的关键应用之一&#xff0c;对于多个领域都具有重要的应用价值。它有助于更好地管理水资源&#xff0c;保护环境&#xff0c;减少灾害风险&#xff0c;促进可持续发展&#xff0c;以…

w~自动驾驶~合集16

我自己的原文哦~ https://blog.51cto.com/whaosoft/12765612 #SIMPL 用于自动驾驶的简单高效的多智能体运动预测基准 原标题&#xff1a;SIMPL: A Simple and Efficient Multi-agent Motion Prediction Baseline for Autonomous Driving 论文链接&#xff1a;https://ar…

外驱功率管电流型PWM控制芯片CRE6281B1

CRE6281B1 是一款外驱功率管的高度集成的电流型PWM 控制 IC&#xff0c;为高性能、低待机功率、低成本、高效率的隔离型反激式开关电源控制器。在满载时&#xff0c;CRE6281B1工作在固定频率(65kHz)模式。在负载较低时&#xff0c;CRE6281B1采用节能模式&#xff0c;实现较高的…

亚信安全2025年第1期《勒索家族和勒索事件监控报告》

本周态势快速感知 本周全球共监测到勒索事件51起&#xff0c;本周勒索事件数量降至近一年来最低&#xff0c;需注意防范。从整体上看Ransomhub依旧是影响最严重的勒索家族&#xff1b;本周Acrusmedia和Safepay也是两个活动频繁的恶意家族&#xff0c;需要注意防范。本周&#…

linux网络 | http结尾、理解长连接短链接与cookie

前言&#xff1a;本节是http章节的最后一部分&#xff0c;主要解释一些小概念。讲解到了HTTP的方法&#xff0c;表单&#xff0c; 重定向等等。 现在废话不多说&#xff0c; 开始我们的学习吧。 ps&#xff1a;本节内容都是概念&#xff0c; 知道就行&#xff0c; 友友们放心观…

韩国机场WebGIS可视化集合Google遥感影像分析

目录 前言 一、相关基础数据介绍 1、韩国的机场信息 2、空间数据准备 二、Leaflet叠加Google地图 1、叠加google地图 2、空间点的标记及展示 3、韩国机场空间分布 三、相关成果展示 1、务安国际机场 2、有同类问题的机场 四、总结 前言 12月29日8时57分左右务安国际机…

基于Django的农业管理系统

一、系统背景与意义 随着信息技术的不断发展&#xff0c;农业生产也逐渐向智能化、信息化转型。基于Django的农业管理系统正是为了满足这一需求而设计的。该系统通过集成先进的信息技术&#xff0c;旨在提升农业生产效率&#xff0c;优化资源配置&#xff0c;减少人工干预&…

后台管理系统引导功能的实现

引导是软件中经常见到的一个功能&#xff0c;无论是在后台项目还是前台或者是移动端项目中。 那么对于引导页而言&#xff0c;它是如何实现的呢&#xff1f;通常情况下引导页是通过 聚焦 的方式&#xff0c;高亮一块视图&#xff0c;然后通过文字解释的形式来告知用户该功能的作…

Yolov8训练方式以及C#中读取yolov8+onnx模型进行目标检测.NET 6.0

目录 首先你要定义v8的模型特征文件 下方是完整的模型编写函数 然后你要在控件窗体中定义应用这些方法以及函数 一、定义你的标签 二、下方是定义模块和坐标的方法 三、画框 完整推理函数代码块 效果展示 完整源码 训练其实和yolov10差不多 因为v10就是在v8的基础上做了优…

C 语言奇幻之旅 - 第16篇:C 语言项目实战

目录 引言1. 项目规划1.1 需求分析与设计1.1.1 项目目标1.1.2 功能需求1.1.3 技术实现方案 2. 代码实现2.1 模块化编程2.1.1 学生信息模块2.1.2 成绩管理模块 2.2 调试与测试2.2.1 调试2.2.2 测试2.2.4 测试结果 3. 项目总结3.1 代码优化与重构3.1.1 代码优化3.1.2 代码重构 3.…

云安全博客阅读(二)

2024-05-30 Cloudflare acquires BastionZero to extend Zero Trust access to IT infrastructure IT 基础设施的零信任 不同于应用安全&#xff0c;基础设置的安全的防护紧急程度更高&#xff0c;基础设施的安全防护没有统一的方案IT基础设施安全的场景多样&#xff0c;如se…

06-RabbitMQ基础

目录 1.初识MQ 1.1.同步调用 1.2.异步调用 1.3.技术选型 2.RabbitMQ 2.1.安装 2.2.收发消息 2.2.1.交换机 2.2.2.队列 2.2.3.绑定关系 2.2.4.发送消息 2.3.数据隔离 2.3.1.用户管理 2.3.2.virtual host 3.SpringAMQP 3.1.导入Demo工程 3.2.快速入门 3.2.1.消…

【Linux系列】并发与顺序执行:在 Linux 脚本中的应用与选择

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

【机器学习篇】 科技异次元的超强 “魔杖”,开启奇幻新程

一起开启这场旅行吧&#xff0c;关注博主&#xff0c;点赞支持不迷路&#xff0c;下面一同欣赏本篇的美妙吧&#xff01;&#xff01; &#xff01; 博主主页&#xff1a; 羑悻的小杀马特.-CSDN博客 在当今科技飞速发展的时代&#xff0c;机器学习宛如一把来自科技异次元的超强…

联发科MTK6771/MT6771安卓核心板规格参数介绍

MT6771&#xff0c;也被称为Helio P60&#xff0c;是联发科技(MediaTek)推出的一款中央处理器(CPU)芯片&#xff0c;可运行 android9.0 操作系统的 4G AI 安卓智能模块。MT6771芯片采用了12纳米工艺制造&#xff0c;拥有八个ARM Cortex-A73和Cortex-A53核心&#xff0c;主频分别…

ros2笔记-2.5.3 多线程与回调函数

本节体验下多线程。 python示例 在src/demo_python_pkg/demo_python_pkg/下新建文件&#xff0c;learn_thread.py import threading import requestsclass Download:def download(self,url,callback):print(f线程&#xff1a;{threading.get_ident()} 开始下载&#xff1a;{…

人工智能的发展领域之GPU加速计算的应用概述、架构介绍与教学过程

文章目录 一、架构介绍GPU算力平台概述优势与特点 二、注册与登录账号注册流程GPU服务器类型配置选择指南内存和存储容量网络带宽CPU配置 三、创建实例实例创建步骤镜像选择与设置 四、连接实例SSH连接方法远程桌面配置 一、架构介绍 GPU算力平台概述 一个专注于GPU加速计算的…

Unity【Colliders碰撞器】和【Rigibody刚体】的应用——小球反弹效果

目录 Collider 2D 定义&#xff1a; 类型&#xff1a; Rigidbody 2D 定义&#xff1a; 属性和行为&#xff1a; 运动控制&#xff1a; 碰撞检测&#xff1a; 结合使用 实用检测 延伸拓展 1、在Unity中优化Collider 2D和Rigidbody 2D的性能 2、Unity中Collider 2D…

[微服务]redis主从集群搭建与优化

搭建主从集群 单节点Redis的并发能力是有上限的&#xff0c;要进一步提高Redis的并发能力&#xff0c;就需要搭建主从集群&#xff0c;实现读写分离。 1. 主从集群结构 下图就是一个简单的Redis主从集群结构&#xff1a; 如图所示&#xff0c;集群中有一个master节点、两个s…

自动化立体仓库堆垛机SRM控制系统自动控制功能块开发

1、堆垛机SRM控制系统硬件组态如下图 2、堆垛机SRM控制系统HMI屏幕页面如下图 驱动状态显示、堆垛机状态 3、堆垛机SRM控制系统中相关变量定义如下图 其中包含系统控制相关变量:系统急停、系统手动、复位、货叉左极限、货叉左居中 货叉右极限 货叉右居中 货叉编码器位置反…