【第一行代码学习笔记】第七章 数据存储

第7章


数据存储全方案,详解持久化技术

7.1 什么是持久化技术

很多数据都是瞬时数据,存储在内存中可能会因内存被回收而丢失。持久化技术就是可以让数据从瞬时状态变为持久状态。

Android中主要提供了3种方法用于简单实现数据持久化功能:

  1. 文件存储
  2. SharedPreferences存储
  3. 数据库存储

7.2 文件存储

最基本的存储方式,不对内容进行任何格式化处理。适合存储一些简单的文本数据或二进制数据。

7.2.1 将数据存储到文件中

主要就是这个save函数,和Java流比较类似。奈何没有学过Java,看不太懂啊!实在不行先当一段固定语法记住先。

class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }

    // 书上是onDestroy,但是真机上返回并没有销毁程序,强制关闭不知道为什么也不行,于是改成了onPause
    override fun onPause() {
        super.onPause()
        val inputText = binding.editText.text.toString()
        save(inputText)
    } 

    private fun save(inputText: String) {
        try {
            // 第一个参数是保存的文件名,路径默认;第二个参数表示当指定相同文件名时,会覆盖原有文件
            val output = openFileOutput("data", Context.MODE_PRIVATE) 
            val writer = BufferedWriter(OutputStreamWriter(output))
            writer.use { it.write(inputText) }
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }
}

7.2.2 从文件中读取数据

上面用到了openFileOutput函数,这里就用openFileInput啦。大体上是一致的,请看代码:

class MainActivity : AppCompatActivity() {
    ...

        val inputText = load()
        if (inputText.isNotEmpty()) {
            binding.editText.setText(inputText)
            binding.editText.setSelection(inputText.length) // 将光标设置到文本末尾位置
            Toast.makeText(this, "Restoring succeeded", Toast.LENGTH_SHORT).show()
        }
    }

    private fun load(): String {
        val content = StringBuilder()
        try {
            val input = openFileInput("data") // 参数:要读取的文件名
            val reader = BufferedReader(InputStreamReader(input))
            reader.use {
                reader.forEachLine { content.append(it) }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return content.toString()
    }

    ...
}

7.3 SharedPreferences存储

SharedPreferences使用键值对存储数据。支持多种不同的数据类型存储:存int读出来也是int,存string读出来也是string。

7.3.1 存储数据进去

首先需要获取SharedPreferences对象,有以下两种方法:

  1. Context类中的getSharedPreferences()方法
    接收两个参数:第一个参数用于指定SharedPreferences文件的名称,默认路径是/data/data/<package name>/shared_prefs/目录下;第二个参数用于指定操作模式,目前只有MODE_PRIVATE这一种可用(它和直接传入0的效果是一样的),其他均已被废弃。
  2. Activity类中的getPreferences()方法
    它只接受一个指定操作模式的参数,因为它自动将当前Activity的类名作为文件名。

用法也是super easy,请看代码:

class MainActivity : AppCompatActivity() {
    ...

    binding.saveButton.setOnClickListener {
        // 第一步获取Editor对象
        val editor = getSharedPreferences("data", 0).edit() 
        // 第二步存储数据
        editor.putString("name", "nayufeng")
        editor.putInt("age", 21)
        editor.putBoolean("married", false)
        // 第三步apply提交
        editor.apply()
    }

    ...
}

7.3.2 读取数据出来

步骤是获得SharedPreferences对象,然后用getString等命令获取数据(第一个命令是键,第二个参数是默认值,即表示当传入的键找不到对应的值时会返回什么值)

class MainActivity : AppCompatActivity() {
    ...

    binding.restoreButton.setOnClickListener {
        val prefs = getSharedPreferences("data", 0)
        val name = prefs.getString("name", "No such value")
        val age = prefs.getInt("age", 0)
        val married = prefs.getBoolean("married", false)
        
        Log.d("Main", "Name is $name")
        Log.d("Main", "Age is $age")
        Log.d("Main", "Married is $married")
    }

    ...
}

7.4 SQLite数据库存储

7.4.1 创建数据库

SQLiteOpenHelper是一个抽象类,我们需要新建一个帮助类去继承它。将SQL语法部分作为一个字符串传入db.execSQL命令中即可执行。

class MyDatabaseHelper(val context: Context, name: String, versoin: Int) :
        SQLiteOpenHelper(context, name, null, versoin) {

    private val creatBook = "create table Book (" +
            "id integer primary key autoincrement," +
            "author text," +
            "price real," +
            "pages integer," +
            "name text)"

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(creatBook)
        Toast.makeText(context, "Create succeed", Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    }
}

创建的表是不能直接查看的,需要安装一个"Database Navigator"插件。

7.4.2 升级数据库

onUpgrade()方法中先将已存在的表删除,然后就可以调用onCreate()方法了。

class MyDatabaseHelper(val context: Context, name: String, versoin: Int) :
        SQLiteOpenHelper(context, name, null, versoin) {
    ...
    private val createCategory = "create table Category (" +
            "id integer primary key autoincrement," +
            "category_name text," +
            "category_code integer)"

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(creatBook)
        db.execSQL(createCategory)
        Toast.makeText(context, "Create succeed", Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("DROP TABLE IF EXISTS Book")
        db.execSQL("DROP TABLE IF EXISTS Category")
        onCreate(db)
    }
}

7.4.3 添加数据

对数据的操作一共有4种:CRUD

  1. C: create(添加)。SQL命令:insert
  2. R: retrieve(查询)。SQL命令:select
  3. U: upgrade(更新)。SQL命令:update
  4. D: delete(删除)。SQL命令:delete

本节先看如何添加数据。

SQLiteDatabase提供了一个insert()方法,接受3个参数:

  1. 表名:希望向哪张表里添加数据,就传入该表的名字;
  2. 用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般传入null即可;
  3. ContentValues对象:提供了一系列的put()方法重载,用于向ContentValues种添加数据。

看代码:点击addData这个按钮添加数据

    binding.addData.setOnClickListener { 
        val db = dbHelper.writableDatabase
        val values1 = ContentValues().apply { 
            // 组装第一条数据
            put("name", "The Da Vinci Code")
            put("author", "Dan Brown")
            put("pages", 454)
            put("price", 16.96)
        }
        db.insert("Book", null, values1) // 插入第一条数据
        
        val values2 = ContentValues().apply {
            // 组装第二条数据
            put("name", "The Lost Symbol")
            put("author", "Dan Brown")
            put("pages", 510)
            put("price", 19.95)
        }
        db.insert("Book", null, values2) // 插入第二条数据
    }

7.4.4 更新数据

SQLiteDatabase提供了一个update()方法,接受4个参数:

  1. 表名:希望向哪张表里添加数据,就传入该表的名字;
  2. ContentValues对象:更新数据在这里组装进去;
  3. 剩下两个参数用于约束更新某一/几行中的数据,默认更新所有行。
    binding.updateData.setOnClickListener { 
        val db = dbHelper.writableDatabase
        val values = ContentValues()
        values.put("price", 10.99)
        db.update("Book", values, "name = ?", arrayOf("The Da Vinci Code"))
    }

这里update()方法中第三个参数对应的是SQL语句的where部分,表示更新所有name等于 ? 的行,?是占位符,通过第四个参数提供的一个字符串数组为第三个参数种每个占位符指定相应的内容。

7.4.5 删除数据

SQLiteDatabase提供了一个delete()方法,接受3个参数:

  1. 表名:希望向哪张表里添加数据,就传入该表的名字;
  2. 第二、三个参数用于约束删除某一/几行中的数据,默认删除所有行。
    binding.deleteData.setOnClickListener {
        val db = dbHelper.writableDatabase
        db.delete("Book", "pages > ?", arrayOf("500"))
    }

7.4.6 查询数据

query()方法与SQL部分的对应关系:

query()列方法参数对应SQL描 述
tableFROM table_name指定查询的表名
columnsSELECT column1, column2指定查询的列名
selectionWHERE column=value指定where的约束条件
selectionArgs-为where中的占位符提供具体的值
groupbyGROUP BY column指定需要groupby的列
havingHAVING column=value对groupby后的结果进一步约束
orderbyORDER BY column1, column2指定查询结果的排列方式

代码示例:

    binding.queryData.setOnClickListener {
        val db = dbHelper.writableDatabase
        val cursor = db.query("Book", null, null, null, null, null, null) // 就是查看所有数据
        if (cursor.moveToFirst()) {  // 将指针移动到第一行的位置
            do {
                val name = cursor.getString(cursor.getColumnIndex("name"))
                val author = cursor.getString(cursor.getColumnIndex("author"))
                val pages = cursor.getInt(cursor.getColumnIndex("pages"))
                val price = cursor.getDouble(cursor.getColumnIndex("price"))
                Log.d("MainActivity", "book name is $name")
                Log.d("MainActivity", "author is $author")
                Log.d("MainActivity", "pages is $pages")
                Log.d("MainActivity", "price is $price")
            } while (cursor.moveToNext()) // 将指针移动到下一行
        }
        cursor.close()
    }

7.4.7 直接使用SQL操作数据库

我感觉这个更简单一点。示例如下:

  • 添加数据
db.execSQL("INSERT INTO Book (name, author, pages, price) VALUES(?, ?, ?, ?)",
    arrayOf("The Da Vinci Code", "Dan Brown", "454", "16.96")
)
  • 更新数据
db.execSQL("UPDATE Book SET price = ? WHERE name = ?", arrayOf("10.99", "The Da Vinci Code"))
  • 删除数据
db.execSQL("DELETE FROM Book WHERE pages > ?", arrayOf("500"))
  • 查询数据
val cursor = db.rawQuery("SELECT * FROM Book", null)

别的

  • 事务:让一系列操作要么全部完成,要么一个都不会完成。
db.beginTransaction() // 开启事务
db.setTransaction() // 事务已经执行成功
db.endTransaction() // 结束事务
  • 升级数据库

onUpgrade()方法中添加一个if:

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    if (oldVersion <= 1) {
        db.execSQL(createCategory1)
    }
    if (oldVersion <= 2) {
        db.execSQL(createCategory2)
    }
}

这样就可以在保留前一版数据的同时更新新版本了。

  • 简化SharedPreferences的方法:getSharedPreferences.edit
getSharedPreferences("data", Context.MODE_PRIVATE).edit {
    putString("name", "Tom")
    putString("age", 28)
}
  • 简化ContentValues的方法:contentValuesOf()
val values = contentValuesOf("name" to "Game of Thrones", "author" to "George Martin",
    "pages" to 720, "price" to 20.85)
db.insert("Book", null, values)
  • 21
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值