安卓学习笔记(四)数据存储

目录

一. 背景

二. 持久化技术简介

三. 文件存储

(1)简介

(2)将数据存储到文件中

  1. openFileOutput()方法

  2. 保存方法示例

(3)从文件中读取数据

  1. openFileInput()方法

  2. 读取方法示例

四. SharedPreferences存储

 (1)简介

 (2)往SharedPreferences中存储数据

        1.获取SharedPreferences对象

        2. 向SharedPreferences文件中存储数据

        3. 项目示例

 (3)从SharedPreferences中读取数据  

五. MMKV概述

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

 2. MMKV 原理

六. MMKV在Android中的使用

1. 添加依赖

2. 初始化

3. 支持的数据类型

4. CRUD操作

(1)存储与读取数据

(2)删除与查询

(3)不同业务需要区别存储时

(4)业务需要多进程访问时     

   5. SharedPreferences 迁移


一. 背景

任何一个应用程序都在不停地和数据打交道,我们使用程序时所关心的都是里面的数据。为了保证一些关键性的数据不会丢失,需要用到数据持久化技术。

二. 持久化技术简介

1. 瞬时数据:存储在内存当中,有可能会因为程序关闭或其他原因导致内存被回收而丢失的数据。

2. 数据持久化:将内存中的瞬时数据保存到存储设备中,保证即使在手机或计算机关机的情况下,这些数据仍然不会丢失。

3. 数据状态转换:保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的。持久化技术提供了一种机制,可以让数据在瞬时状态和持久状态之间进行转换。

4. Android系统提供的3种持久化技术:文件存储、SharedPreferences存储以及数据库存储。

三. 文件存储

(1)简介

   文件存储。Android中最基本的数据存储方式,它不对存储的内容进行任何格式化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合存储一些简单的文本数据或二进制数据

(2)将数据存储到文件中

  1. openFileOutput()方法

 Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。这个方法接收两个参数:第一个参数是文件名,在文件创建的时候使用。第二个参数是文件的操作模式。

val output = openFileOutput("data", Context.MODE_PRIVATE)
  • 指定的文件名不可以包含路径,所有的文件都默认存储到
  • /data/data/<package name>/files/目录下;  
  • 操作模式可选MODE_PRIVATEMODE_APPEND,默认值为MODE_PRIVATE,表示当指定相同文件名的时候,所写入的内容将会覆盖原文件中的内容;MODE_APPEND则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件。

  2. 保存方法示例

  以下是一段简单的代码示例,展示了如何将一段文本内容保存到文件中:

fun save(inputText: String) {
    try {
        // 1.通过openFileOutput()方法获取一个FileOutputStream对象
        val output = openFileOutput("data", Context.MODE_PRIVATE)
        // 2.借助FileOutputStream对象,构建出一个OutputStreamWriter对象。然后依靠OutputStreamWriter对象,构建一个BufferedWriter对象,通过其将文本内容写入文件中
        val writer = BufferedWriter(OutputStreamWriter(output))
        // 3.内置扩展函数use,在Lambda表达式中的代码全部执行完之后自动将外层的流关闭,无需再编写一个finally语句,手动去关闭流
        writer.use {
            it.write(inputText)
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
}

(3)从文件中读取数据

  1. openFileInput()方法

   Context类中还提供了一个openFileInput()方法,用于从文件中读取数据。这个方法只接收一个参数,即要读取的文件名,然后系统会自动到/data/data/<package name>/files/目录下加载这个文件,并返回一个FileInputStream对象,得到这个对象之后,再通过流的方式就可以将数据读取出来了。

  2. 读取方法示例

 以下是一段简单的代码示例,展示了如何从文件中读取文本数据:

fun load(): String {
    val content = StringBuilder()
    try {
        // 注释1
        val input = openFileInput("data")
        // 注释2
        val reader = BufferedReader(InputStreamReader(input))
        reader.use {
            // 注释3
            reader.forEachLine {
                // 注释4
                content.append(it)
            }
        }
    } catch (e: IOException) {
        e.printStackTrace()
    }
    // 注释5
    return content.toString()
}

注释1:获取一个FileInputStream对象
注释2:借助FileInputStream对象构建出一个InputStreamReader对象,依靠InputStreamReader对象,构建一个BufferedReader对象,通过其将文件中的数据一行行读取出来
注释3:内置扩展函数forEachLine,它会将读到的每行内容都回调到Lambda表达式中,我们在Lambda表达式中完成拼接逻辑即可。
注释4:拼接到StringBuilder对象当中。
注释5:最后将读取的内容返回就可以了

四. SharedPreferences存储

 (1)简介

不同于文件的存储方式,SharedPreferences是使用键值对的方式来存储数据的。也就是说,当保存一条数据的时候,需要给这条数据提供一个对应的键,这样在读取数据的时候就可以通过这个键把相应的值取出来。

而且SharedPreferences还支持多种不同的数据类型存储,如果存储的数据类型是整型,那么读取出来的数据也是整型的;如果存储的数据是一个字符串,那么读取出来的数据仍然是字符串。

 (2)往SharedPreferences中存储数据

        1.获取SharedPreferences对象

 Context类中的getSharedPreferences()方法,此方法接收两个参数。

  • 第一个参数用于指定SharedPreferences文件的名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都是存放在/data/data/<package name>/shared_prefs/目录下的;
  • 第二个参数用于指定操作模式,目前只有默认的MODE_PRIVATE这一种模式可选,它和直接传入0的效果是相同的,表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写。

 Activity类中的getPreferences()方法,该方法只接收一个参数用于指定操作模式。使用这个方法时会自动将当前Activity的类名作为SharedPreferences的文件名。

        2. 向SharedPreferences文件中存储数据

得到了SharedPreferences对象之后,就可以开始存储数据了,主要可以分为3步实现。

(1) 调用SharedPreferences对象的edit()方法获取一个SharedPreferences.Editor对象。

(2) 向SharedPreferences.Editor对象中添加数据,比如添加一个布尔型数据就使用putBoolean()方法,添加一个字符串则使用putString()方法,以此类推。

(3) 调用apply()方法将添加的数据提交,从而完成数据存储操作。

 3. 项目示例

      新建一个SharedPreferencesTest项目,然后修改activity_main.xml中的代码,简单地放置了一个按钮,用于将一些数据存储到SharedPreferences文件当中,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/saveButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save Data"
        />

</LinearLayout>

       然后修改MainActivity中的代码,如下所示:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        saveButton.setOnClickListener {
            val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
            editor.putString("name", "Tom")
            editor.putInt("age", 28)
            editor.putBoolean("married", false)
            editor.apply()
        }
    }

}

可以看到,这里首先给按钮注册了一个点击事件,然后在点击事件中通过getSharedPreferences()方法指定SharedPreferences的文件名为data,并得到了SharedPreferences.Editor对象。接着向这个对象中添加了3条不同类型的数据,最后调用apply()方法进行提交,从而完成了数据存储的操作 .

 (3)从SharedPreferences中读取数据

  SharedPreferences对象中提供了一系列的get方法,用于读取存储的数据,每种get方法都对应了SharedPreferences.Editor中的一种put方法,比如读取一个布尔型数据就使用getBoolean()方法,读取一个字符串就使用getString()方法。

     这些get方法都接收两个参数:

     第一个参数是键,传入存储数据时使用的键就可以得到相应的值了;

     第二个参数是默认值,当传入的键找不到对应的值时会以默认值进行返回。

在SharedPreferencesTest项目的基础上继续开发,修改activity_main.xml中的代码,增加了一个还原数据的按钮,我们希望通过点击这个按钮来从SharedPreferences文件中读取数据,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/saveButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Save Data"
        />

    <Button
        android:id="@+id/restoreButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Restore Data"
        />

</LinearLayout>

 修改MainActivity中的代码,在还原数据按钮的点击事件中首先通过getSharedPreferences()方法得到了SharedPreferences对象,然后分别调用它的getString()getInt()getBoolean()方法,去获取前面所存储的姓名、年龄和是否已婚,如果没有找到相应的值,就会使用方法中传入的默认值来代替,最后通过Log将这些值打印出来。如下所示:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ...
        restoreButton.setOnClickListener {
            val prefs = getSharedPreferences("data", Context.MODE_PRIVATE)
            val name = prefs.getString("name", "")
            val age = prefs.getInt("age", 0)
            val married = prefs.getBoolean("married", false)
            Log.d("MainActivity", "name is $name")
            Log.d("MainActivity", "age is $age")
            Log.d("MainActivity", "married is $married")
        }
    }

}

  

五. MMKV概述

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

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

       GitHub地址:https://github.com/Tencent/MMKV

 2. MMKV 原理

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

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

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

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

六. MMKV在Android中的使用

 1. 添加依赖

​dependencies {
    implementation 'com.tencent:mmkv-static:1.2.2''
}

2. 初始化

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

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

public class MyApp extends Application {
    private static final String TAG = MyApp.class.getSimpleName();
    @Override
    public void onCreate() {
        super.onCreate();
        String rootDir = MMKV.initialize(this);
        Log.i(TAG,"mmkv root: " + rootDir);
    }
}

 3. 支持的数据类型

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

      boolean、int、long、float、double、byte[]。

  •  支持以下Java类和容器:

     String、Set<String>、任何实现了Parcelable的类型。

 4. CRUD操作

   (1)存储与读取数据

1.MMKV会提供一个全局实例,可以直接使用

2.存储数据采用键值对的方法进行存储,使用数据相应的encode()方法

3.读取数据采用decode()方法,传入键作为参数,读取Int类型就用decodeInt(),以此类推

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")));

   (2)删除与查询

  • removeValueForKey()方法移除指定的key
  • removeValueForKeys()方法移除一组key
MMKV kv = MMKV.defaultMMKV();
 
// 移除指定的key
kv.removeValueForKey("bool");
System.out.println("bool: " + kv.decodeBool("bool"));
    
// 移除一组key
kv.removeValuesForKeys(new String[]{"int", "long"});
System.out.println("allKeys: " + Arrays.toString(kv.allKeys()));
 
boolean hasBool = kv.containsKey("bool");

   (3)不同业务需要区别存储时

// 单独创建自己的实例
MMKV mmkv = MMKV.mmkvWithID("MyID");
mmkv.encode("bool", true);

   (4)业务需要多进程访问时     

// 在初始化的时候加上标志位 MMKV.MULTI_PROCESS_MODE
MMKV mmkv = MMKV.mmkvWithID("InterProcessKV", MMKV.MULTI_PROCESS_MODE);
mmkv.encode("bool", true);

   5. SharedPreferences 迁移

  • MMKV 提供了 importFromSharedPreferences() 函数,可以比较方便地迁移数据过来。
  • MMKV 还额外实现了一遍 SharedPreferencesSharedPreferences.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<String> set = new HashSet<String>();
        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();
    }

  • 21
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值