美文网首页
数据持久化存储总结几种方式

数据持久化存储总结几种方式

作者: 啦啦哇哈哈 | 来源:发表于2018-10-27 22:56 被阅读0次

    一、文件存储

    文件存储不对存储内容进行任何格式化处理,所有数据原封不动地保存到文件当中,适合存储一些简单的文本数据或者二进制数据,对于复杂数据,如果想使用文件存储,就要定义一套自己的格式规范,方便之后将数据从文件中解析出来。

    将数据存储在文件中

    Context类提供了一个openFileOutput()方法,可以用于将数据存储到指定文件中,这个方法接收两个参数,第一个是文件名,在文件创建的时候就使用这个名称,文件名不可以包含路径,所有文件都有默认的保存路径,后面会介绍。第二个参数是文件的操作模式,主要有两种模式可选,MODE_PRIVATEMODE_APPEND,前者是默认的操作模式,表示当指定同样的文件名时候,所写入的内容会覆盖原文件的内容,而后者则表示如果文件存在,就往文件里面追加内容,不创建新的文件。

    openFileOutput()方法返回一个FileOutputStream对象,得到这个对象之后,就可以使用Java流的方式将数据写入文件中了。我们新建一个FilePersistenceTest项目来举例,这个例子将会演示,如何把我们在EditText中输入的文本保存在一个文件里面,那么首先xml布局中添加一个EditText:

    然后写MainActivity,如下:


    我们再来捋一下,所有的一切能完成文件存储,都在Context类提供的这个openFileOutput,他返回了一个文件输出流对象,FileOutputStream,也就是给了我们这个管子,让我们把水送出去。只要按照Java流的方法进行包装和使用即可。我们看一下结果:


    我们输入了个I want to play,然后按下Back键,根据我们的设计,OnDestroy方法此时被执行,这段文字应该已经保存在文件中了,文件都是默认存储到/data/data/<package name>/files/目录下面的。通过Tools->Android->Android Device Monitor工具可以查看。进入File Explorer标签页,找到data/data/com.example.filepersistencetest/files目录,就可以看到一个data文件了,遇到的一个尴尬的问题是,现在在AS3.0版本之后,Android Device Monitor被弃用了。。然后用AVD Manager里面show on Disk ,打开的data里面又没有这样的目录。。很迷,所以可能出现变化了。查了半天没查到,先搁置,知道怎么写再说。

    更新。。解决方案,在睡了一觉起来之后,找到了AS3.0之后怎么查看手机或模拟器的目录的入口,在AS的右下角:


    需要先打开一个设备,才能进行文件的查看,然后进去之后,终于找到了data/data/<package name>/files

    打开之后就是我刚刚在EditText里面输入的内容。

    从文件中读取数据

    既然能把数据保存下来,那我们还要想办法在下次启动的时候让这些数据还原到EditText中去。Context类还提供了一个openFileInput()方法,用于从文件中读取数据到输入流,他接受一个参数,要读取的文件名,系统会自动在默认目录下寻找目标文件加载,并返回一个FileInputStream对象,得到这个对象,和上一节类似的用同样的方式,Java流的方法,我们完善MainActivity如下:



    运行之后,上次输入的内容回来了,而且还提示了一个Toast。

    二、SharedPreferences

    文件存储不适合保存一些复杂的文本数据,这节另一种数据持久化的方式,比文件存储更加简单易用,而且可以方便的对某一数据进行读写操作。

    SharedPreferences,它是用键值对的方法存储数据,保存一条数据时候,需要给这个数据提供一个对应的键,读取数据时候就可以使用这个键取出值,还支持多种不同的数据类型存储,存储整型则读取出来为整型,存储字符串,则读取出来为字符串。

    将数据存储到SharedPreferences中

    首先需要获取SharedPreferences对象,共三种方法可以获取:

    1.Context类中的getSharedPreferences方法

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

    2.Activity类中的getPreferences方法

    这个方法和上面那个方法很类似,但只接受一个操作模式参数,文件名会自动命名为当前活动的类名。

    3.PreferenceManager类的getDefaultSharedPreferences方法

    静态方法,接收一个Context参数,并自动使用当前应用程序的包名作为前缀命名SharedPreferences文件。

    得到这个对象之后,就可以开始向其中存储数据了,分为三步实现:

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

    2.向Editor对象中添加数据,putxxx()方法,比如putString(),putBoolean()由类型决定。这些所有的putxxx方法,都是两个参数,第一个是键,第二个是值。

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

    新建一个SharedPreferencesTest项目,我们想放置一个按钮,通过按下按钮,让数据存储在SharedPreferences文件当中,修改布局文件如下:


    修改主活动java代码如下:


    获取对象->调用edit方法->获取Editor对象->调用各种put方法,传入键和值->调用apply方法提交数据->完成存储操作。运行之后按按钮,查看目录可以看到生成了一个data.xml查看发现用xml格式已经记录了我们传入的数据。

    从SharedPreferences中读取数据

    SharedPreferences对象提供了一系列的getxxx方法,对于存储的数据进行读取,每种get方法都对应的上面put方法放入的数据。如getString(),getBoolean()等,这些方法接收两个参数,第一个参数是键,传入存储数据put时候使用的键就可以获取对应的值,第二个参数是默认值,如果传入的键找不到对应的值,会以这个默认值进行返回。实例如下,修改xml布局,添加一个button,按下后会读取文件,同时再添加一个TextView,我们把读取的数据显示在TextView里面,当然也可以用Toast显示读取的数据,都一样,此处重点在于使用各种get方法:

    运行之后,按第一个键存储,按第二个键打印到TextView里面:


    这就是SharedPreferences的基本用法了。下面我们实现一个记住密码的功能。

    实现记住密码功能

    在广播那个BroadcastBestPractice项目里面已经写过一个登录界面了,我们就打开那个项目,在其基础上进行记住密码功能的实现。在activity_login.xml中加一个CheckBox,这是一个复选框控件,通过点击方式来进行选中和取消,用这个控件表示用户是否需要记住密码:

    下面修改LoginActivity的代码:


    运行之后,在登录界面输入账号密码进入主界面,然后在主界面强制下线,回到登录界面时候账号密码已经在其中了。

    书上还提醒了一下,这块只是个简单示例,在实际项目中把密码以明文形式存储在SharedPreferences文件中是很不安全的,应该加入一些加密算法来保护。

    三、Android内置轻量级数据库——SQLite

    Android系统内置了一个轻量级的关系型数据库,运算速度快,占用内存小,适合在移动设备上使用。前面学过的文件存储和SharedPreferences适合于保存一些简单的数据和键值对。要存储大量复杂的关系型数据时候,就要使用SQLite数据库了。

    创建数据库

    先来大概了解一下数据库和SQLite的基本语法吧,因为数据库学都没学过。。后面这玩意有些看不太懂。想看文档或者书从头学一下数据库,但这样的话又要搁置安卓了。。很难受,搜了搜,只能看博客了。推荐一篇简书的博客,介绍了仅在Android的SQLite入门中需要学习的基础知识:
    SQLite基础知识
    就只能感慨。。为什么这些人会写的这么好。。然后我再到处拼凑简单总结一下:

    • 数据库基础概念

    1.SQlite 通过文件来保存数据库,一个文件就是一个数据库。
    2.数据库里又包含数个表格
    3.每个表格里面包含了多个记录
    4.每个记录由多个字段组成;
    5.每个字段都有其对应的值;
    6.每个值都可以指定类型,并且指定约束

    • SQLite数据类型

    和Java语言一样,SQLite数据库也有其数据类型,共有五种:

    NULL:空值相当于Java中的null
    INTEGER:带符号的整型,相当于Java中的int型
    REAL:浮点数字,相当于Java中float/double型
    TEXT/VARCHAR:字符串文本,相当于Java中String类
    BLOB:二进制对象,相当于Java中的byte数组,用于存放图片、声音等文件

    • SQLite中的约束

    NOT NULL :非空
    UNIQUE : 唯一
    PRIMARY KEY :主键
    FOREIGN KEY : 外键
    CHECK :条件检查
    DEFAULT : 默认

    约束就是对表中的字段进行一些限制,约束条件放在需要约束的字段之后。比如“age INTEGER NOT NULL”,意思就是age这个字段是整型,而且不能为空。

    • 创建数据表

    数据库中包含若干数据表,数据表是存放在数据库中存放信息的容器。语法如下:

    create  table tablename(col1 type1 [not  null][primary  key], col2 type2[not  null], ··· )
    

    关键字为create table,自定义表的名字:tablename,括号里面的col1、col2是表的字段,type是字段类型,中括号中的约束可以缺省。

    下面看一个书上的例子:

    create  table Book( id integer  primary  key autoincrement, author text, price real, pages integer, name text)
    

    这里创建了一个Book表,需要解释的就是第一个id,约束中将id字段设置为主键,并用autoincrement关键字表示id列是自增长的。

    再看那个文章里面举的更详细的一个例子,一个student的数据表:

    create  table student( id INTEGER  PRIMARY  KEY AUTOINCREMENT, name VARCHAR(20)  NOT  NULL, cid INTEGER, age INTEGER  CHECK(age>18  and age<60), gender BIT  DEFAULT(1), score REAL);
    

    注意,SQL中不区分大小写,这个和汇编还是挺像的。。

    解释如下(来自原作者的博客):


    以上是一些基本概念,其他基础知识会后续补充。

    下面回到Android中来,Android提供了一个SQLiteOpenHelper帮助类,借助它可以非常简单地对数据库进行创建和升级。

    SQLiteOpenHelper是一个抽象类,其中有两个抽象方法:onCreate()onUpgrade(),我们必须在自己的帮助类中重写这两个方法,然后在这两个方法中去实现创建升级数据库的逻辑。还有两个重要的实例方法:getReadableDatabase()getWritableDatabase(),这两个方法都可以创建或者打开一个现有的数据库,(如果数据库已存在直接打开,否则新创建一个),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满),前者返回对象将以只读的方式打开数据库,而后者将会出现异常。

    我们这里使用SQLiteOpenHelper的接收4个参数的那个构造方法,第一个是Context,第二个是数据库名,创建数据库时候使用的就是这里指定的名称,第三个是允许我们在查询数据时候返回一个自定义的Cursor(光标),一般传入null,第四个参数表示当前数据库的版本号,可以用于对数据库的升级操作,构建出SQLiteOpenHelper的实例之后,再调用getReadableDatabase()getWritableDatabase()方法就能够创建数据库了。数据库文件存放在data/data/<package name>/databases/目录下,此时重写的onCreate()方法也会执行,所以通常在这里处理一些创建表的逻辑。

    新建一个DatabaseTest项目体会一下吧。

    先新建一个MyDatabaseHelper类继承自SQLiteOpenHelper,我们需要在代码中执行SQL语句,就以上面那个建立一个BOOK表为例:

    然后在布局中添加一个Button:


    为了让点击Button就创建数据库。在MainActivity活动代码中注册点击事件,调用getWritableDatabase方法即可:


    运行之后,我们按键,提示了个Toast:


    同时我们去看右下角的Device File Explorer,看到data/data/databasetest/databases目录下生成了两个文件:

    这就证明数据库已经创建成功了,我们再去按Button就不会有Toast了,因为数据库已经存在不会再去调用onCreate方法。现在有一个问题是我们怎么查看数据库的内容,怎么看那个表的内容,File Explorer是看不了的。换一个方式使用adb shell来对数据库和表的创建情况进行检查。

    adb是AS自带的调试工具,可以对连接在电脑上的手机或者模拟器进行调试工作,在命令行中使用,要使用它之前先要把它的路径配置环境变量里面。Windows系统,计算机->属性->高级系统设置->环境变量,然后系统变量中找到Path并且点击编辑,添加adb工具的目录,他在SDK的platform-tools目录下面:

    这是我的机子上面的SDK的目录,添加进去就可以直接在命令行里面使用adb了。
    然后打开cmd。输入adb shell,这时候就对模拟器那个手机的目录进行调试了:

    为了查看数据库文件,我们cd进那个目录,出现了Permission denied权限问题,再看那个美元符号。。它表示我只是普通管理员,因而存在一些权限问题,就不能访问到那个目录。书上说可以使用su指令获取超级管理员权限:


    ]尝试没有用,查找资料之后,说是连接的手机要root之后才能获得超级管理员权限,而模拟器则需要下载一下su文件,进行模拟的root。这时候为了避免这么麻烦。。我怀疑应该是有一些模拟器直接给了root权限的。就换了台模拟器,和之前一直用的模拟器同系列但是是Android6.0的系统。然后成功了。。好吧,进去之后不再显示$,而是显示#就代表已经有超级管理员权限了,进入那个目录:


    然后ls看一下那个目录,有两个文件:


    第二个文件是为了让数据库呢能够支持事务而产生的临时日志文件。一般大小是0字节。
    然后使用sqlite3命令打开数据库,格式为sqlite3 数据库名

    然后输入.table命令可以查看有多个表,再通过.schema命令来查看它们的建表语句:

    .table我们还看到另一个表,那个表是每个数据库都会自动生成的不用管,下面.schema看到的表的内容和预期一致。确实数据库已经创建成功。然后键入.exit或者.quit命令就可以退出数据库的编辑,再键入exit命令就可以退出adb shell模式了。

    升级数据库

    MyDatabaseHelper中的 onUpgrade()是超类的抽象方法我们要求重写,但是上一节中就是列了个空方法。这个方法实际上是用于对数据库的升级的。现在设想我们在Book表的基础上,像加入一张Category表用于记录图书的分类,该怎么做。所能够想到的就是和前面一模一样的方法,去改写一下MyDatabaseHelper那个类,添加一个字符串,然后onCreate里面调用execSQL方法即可。那么先设想好建表的SQL语句,有id、有分类名、有分类代码这几个字段:

    create  table Category(id integer  primary  key autoincrement, category_name text, category_code integer)
    

    然后添加这个表:


    然后按Button之后,按照我们想的应该是建好了吧。但是第一没有跳出Toast,就是说onCreate方法并没有执行,什么?其次再用adb确认一下,如下:


    可见Category这个表并没有被建立,问题在哪里?我们知道这两个表都是在bookstore.db这个数据库之中的,而getWritableDatabase方法如果检测到这个数据库已经存在了,就不会再去重新创建,那么onCreate方法只有在数据库新建的时候才会有,而bookstore.db在我们之前就已经建立好了。所以db.execSQL(CREATE_CATEGORY)那一句实际上并没有得到执行。

    解决问题的方法很简单,既然onCreate方法不执行了,我们还有onUpgrade方法,我们修改onUpgrade如下:


    那么这个方法到底怎么才能该执行呢?我们讲SQLiteOpenHelper构造方法中的第四个参数,表示当前数据库的版本号,之前传入的是1,现在只要传入一个比1大的数onUpgrade方法就会执行,所以修改主活动的构造方法的第四个参数数字即可:

    然后重新运行,首先Toast出来了,足以说明onCreate方法重新执行了,然后我们用adb查看:


    image

    新的表被添加进去了。然后.schema查看建表语句:

    升级功能成功了!

    以上是创建和升级数据库的方法,下面学习对表中的数据操作。共有四种操作,增查更删(CRUD,create,retrieve,update,delete),每一种操作各自对应了一种SQL命令,SQL语言中添加用insert,查询用select,更新用update,删除用delete,Android为我这种不会SQL语言的人提供了一系列辅助方法,使得不用编写SQL语句,也能完成增查更删操作。

    我们调用SQLiteOpenHelper的getReadableDatabase()getWritableDatabase()方法可以用于创建和升级数据库。同时这两个方法还能返回一个SQLiteDatabase对象,借助这个对象可以进行CRUD操作。

    添加数据

    SQLiteDatabase提供了一个insert()方法,这个方法用于添加数据。接收三个参数,第一个是表名,是添加的对象。第二个参数用于在未指定添加数据的情况下可以给某些可空的字段自动 赋值NULL,我们一般传入null即可。第三个参数是一个ContenValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需将表中的每个字段名及相应的待添加数据传入。

    下面还是看例子,修改activity_main.xml如下:


    添加一个button用来增加数据。
    然后修改主活动代码,为它注册点击事件,如下:


    我们组装的数据里面没有id,因为在创建表的时候,已经设置ID是


    自动增加 。它的值会在入库的时候自动生成,不需要手动赋值,然后调用insert方法添加数据。我们添加了两条数据。然后运行:


    按一下Add按钮,打开数据库看一下,SQL查询语句为:select * from 表名;,注意结尾有个分号,结果如下如下:

    image

    因为。。我没忍住按了两下,所以第一次查询是添加了两回,然后我又按了一下,第二次查询就又添加了一会,是六条信息。我们也看到ID会随着添加数据的条目增加而自增。

    更新数据

    SQLiteDatabase中提供了一个update()方法,用于更新数据,接收四个参数,第一个表名,第二个ContentValues对象,把更新的数据组装进去。第三四个参数用于约束更新某一行或某几行的数据,不指定的话默认更新所有行。

    比如现在我们要降低书的价格了,修改activity_main.xml如下:


    添加一个button,用来启动更新。然后注册点击事件:


    这块主要再来看一下这个update方法,前两个参数不用解释了,第三个参数对应SQL语句的where部分,表示更新所有name = ?的行,而是一个占位符。可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应内容,这块,我们传入的第四个参数的意图就是,让它去更新name = The Da Vinci Code的。结果如下:

    而我们还可以这样。


    把id是2的改掉:


    另外这块为什么是个new String[]而不是new String,因为那块占位符不一定只有一个,第四个参数需要提供一个字符串数组来为第三个参数的每个占位符指定相应内容。

    删除数据

    SQLiteDatabase提供delete()方法用于删除数据,这个方法接收三个参数,第一个参数表名,第二三个参数用于约束删除某一行或者某几行的数据,不指定的话默认删除所有行。

    还是加一个Button:


    然后注册点击事件:


    占位符的应用和更新数据里如出一辙。这里我们指定pages>500的删除,由于第二本书页数大于500所以被删了:


    查询数据

    SQL的全称是Structured Query Language,就是结构化查询语言。大部分功能体现在“查”上面,SQL查询涉及的内容太多了,第一行代码只介绍了Android上面的查询功能。

    SQLiteDatabase提供了一个query()方法来查询数据,这个方法参数很复杂,最短的一个方法重载也要传入7个参数。

    第一个参数指定表名,第二个指定查询哪几列,如果不指定则默认查询所有列,第三四参数用于约束查询某一行或某几行的数据,则默认查询所有行,第五个参数指定需要group by的列,不指定则不对查询结果进行group by操作(通过一定的规则将一个数据集划分成若干个小的区域,然后针对若干个小区域进行数据处理。),第六个参数用于对group by之后的数据进行进一步过滤,不指定则不过滤,第七个参数指定查询结果的排序方式,不指定则使用默认的排序。

    由于都有默认的方式,所以虽然参数多,但是我们也不必每个参数都去制定,调用query方法会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。

    还是老套路,先放一个button:


    注册点击事件:


    通过query方法来查询,返回一个Cursor对象,然后moveFirst方法是将数据的指针移动到第一行的位置,然后进入循环,通过各种get来取出数据,而get方法的参数需要用getColunmIndex方法提供,getColumnIndex的参数是需要取的字段名。然后用Log.d打印,moveToNext是移动到下一行,这里我们的数据库中只有一行,所以实际上循环只进行一次。运行后点击Query按钮:

    在Logcat中查看:


    更多高级的query方法还要继续探索,这里只是一个简单的示例。

    使用SQL操作数据库

    Android为我们提供了大量方便的API用于操作数据库,但是也可以直接使用SQL来操作数据库。当然前提是要会SQL语句。简单了解下。。等日后学习了SQL再回过来看吧,我们就以前面几节中学的同样操作为例:

    • 添加数据:
    db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)", new  String[]{"The Da Vinci Code","Dan Brown","454","16.96"});
    db.execSQL("insert into Book(name,author,pages,price) values(?,?,?,?)", new String[]{"The Lost Symbol","Dan Brown","510","19.95"});
    
    • 更新数据:
    db.execSQL("update Book set price = ? where name = ?",new  String[]{"10.99","The Da Vinci Code"});
    
    • 删除数据:
    db.execSQL("delete from Book where pages > ?",  new  String[]{"500"});
    
    • 查询数据:
    db.rawQuery("select * from Book",null);
    

    增更删操作都是用execSQL方法,而查询使用rawQuery方法。

    附:SQL常用语句

    相关文章

      网友评论

          本文标题:数据持久化存储总结几种方式

          本文链接:https://www.haomeiwen.com/subject/tqsjtqtx.html