【重读iOS】数据持久化2:持久化方案介绍

什么是数据持久化

内存在断点后就会清空重置,为了保持数据在断点后保持,比如手机重启,就需要把数据存放在硬盘里,做持久化的保存。为什么不一直使用硬盘保存,因为内存会比硬盘更快速。所以在程序的声明周期里,数据加载在内存里,进行快速的计算,然后把需要持久保存的数据保存到硬盘里。

数据持久化就是怎么把数据存储到硬盘里的技术,准确的说,存储是硬件干的事,而对软件而言,关键任务是组织和识别。就是一块数据,你怎么识别为你需要的内容,比如4个字节,可以是一个int数字,也可以是4个字符。所以关键是找到一种格式把数据组织好,存入硬盘,然后按对应的方式再识别。

计算机里有很多地方都是这个逻辑,如音视频的编解码,HTTP请求的数据解析,传输或存储之前都要有一个组织和识别的过程。

方式

  1. plist文件
  2. 归档Archive
  3. 数据库

有了上面的理解,再看这些,不同方式的区别也就是组织数据的方式的区别

plist文件本质是一个xml文件,xml文件可以很好的定义层级关系,用字典和数组互相嵌套定义一个复杂的结构。

归档这个内部不知道是怎么实现的,但一般配合NSKeyedArchiver使用,几次对encodeWithCoder:initWithCoder:的使用情况来看,有几点可以推测:

  • 它使用key-value的方式组织
  • 它是一种层层嵌套的结构

然后数据库是更复杂的结构,应对大量数据的处理。上面两种方式最大问题是它们的修改都是整体修改,一张很大的plist文件里的某一个小部分的修改,也需要把整个文件读取,修改然后再整个重新写入,所以对于数据量大和修改频繁的情况,前两种不好。

plist文件和归档对比

@interface Book : NSObject<NSCoding>

@property (nonatomic) NSString *name;
@property (nonatomic) NSString *name1;
@property (nonatomic) NSString *name2;
@property (nonatomic) NSString *name3;

@end

建一个类,使用归档方式写入文件,然后使用字典模拟这个类的结构,把字典写入plist文件,对比两个文件:

  • plist文件更大,而且结构越复杂,差距越明显。从xml的结构上就可以猜到这个结果,xml携带了许多冗余的信息,比如标签要写两次。如果是数组,每个元素都加标签。
  • 下表展示了不同数量对象存入时,两种方式的文件大小
1个对象 10个 100个 1000个
归档 259 733 5477 52128
plist 374 2300 21290 211190

相比之下,归档文件要小很多,但从10-100-1000对比看,貌似还是和plist文件一样,都是线性增长。

  • 还有就是直接当纯文本打开两种文件,plist文件是可读的xml文件,而归档是乱码。

照着这个思路,只要找到一种描述对象内存的方式,就可以按这种方式组织内存,然后写入文件里,就可以设计一种新的持久化方案。最简单的,使用json描述对象,然后把json文件写入硬盘。神奇的是在这个例子里,10000个对象后,json文件比归档还小!!!

数据库

sqlite原生操作

  1. 建库

    sqlite3_open一句搞定,没有文件的会自动创建

  2. 建表

执行SQL语句create table 表名称 (列名1 列类型1, 列名2 列类型2)

列名有5种基本类型:

  • NULL: 值为一个NULL空值。
  • INTEGER: 值被标识为整数,依据值的大小可以依次被存储为1,2,3,4,6或8个字节。
  • REAL: 所有值都是浮点数值,被存储为8字节的IEEE浮点数。
  • TEXT: 值为文本字符串,使用数据库编码存储,如UTF-8、UTF-16BE或UTF-16-LE。
  • BLOB: 值是数据的二进制对象,如何输入就如何存储,不改变格式。

INTEGER 根据值的大小会自动变化,数据库中只有一个表,表中就一个列为INTEGER时,1000条数据时,值全部为1(1个字节)时数据库文件为25k,值为1LL<<60(8个字节)时为33k,可以说很明显了。

读取的时候提供了sqlite3_column_intsqlite3_column_int64两个方法来指定长度读取。

亲和类型

除了5大基本类型外,在建表的时候可以写入其他类型,会根据亲和类型,把你写入的类型转到基本类型来存储。比如常见的char(20),这个是TEXT的亲和类型,数据库内部实际按TEXT存储,但建表的时候可以char(20)这么写。

列的亲和类型

  1. 插入数据

insert into 表名称 (列名1,列名2) values (值1,值2)
然后执行sql语句就可以了。

注意:不指定列名时表示对所有列都插入数据;值是字符串类型的要使用引号包括起来;

  1. 查询数据

select * from 表名称,或者指定查询的列select 列名1,列名2 from 表名称

然后后面可以接查询条件:

where子语句
  • 比价和逻辑运算符 > = < like not between等,更多
  • 条件是可以使用括号来做组合的,如name like 'book90%' and age < 10 or age > 100name like 'book90%' and (age < 10 or age > 100)完全不同的性质
  • AND和OR连接条件
  • LIKE用来进行文本的匹配,一般使用通配符,百分号%代表0个或多个字符或数字,下划线_表示单个数字或字符
  • limit 限制查询数量 offset 指定查询的开始位置,然后limit要在前面
  • order by,用来排序结果,order by 列名1[asc][desc],列名2,列名...,asc和desc用来指定递增或递减排序。
  1. 更新数据

update Book set 列名1 = 值1, 列名2 = 值2 [条件语句]
一种方式值直接把值写入sql语句里,然后执行,还有一种是在值的位置填入问号?,然后使用sqlite3_bind系列的函数绑定数据,具体操作示例:

sqlite3_stmt *stmt;
   sqlite3_prepare_v2(dbHanle, [updateSql cStringUsingEncoding:NSUTF8StringEncoding], (int)updateSql.length, &stmt, nil);
   //1
   sqlite3_bind_text(stmt, 1, [book.name cStringUsingEncoding:NSUTF8StringEncoding], book.name.length, nil);
  sqlite3_bind_int64(stmt, 2, book.age);
   sqlite3_step(stmt);
   //2
   sqlite3_finalize(stmt);

位置1到2之间可循环操作多条数据。

  1. 删除数据

delete from 表名称 [条件语句],然后执行sql语句

  1. 事务
  • begin transaction 开启事务
  • commit 提交事务,把修改提交
  • end(transaction) 结束事务,结束会自动commit
  • rollback 回滚事务,上次commit或rollback之后的修改取消

使用事务的好处:

  • 多个操作时发生错误,整体回滚,确保数据库的完整性,不会出现未知的中间状态。
  • 大量插入数据时提高效率

事务和多线程

  1. 表信息
  • .tables 查看所有表明
  • select * from sqlite_master; 查看表的信息
  • .schema 查看建表的sql语句
  • 最有用的是:pragma table_info("Book");可以查看到每个表的列和对应的类型,这个让sqlite具有类似反射的能力,可以了解到自身的结构。
  1. 表修改
  • 修改表名:alter table Book rename to realB;
  • 添加列:alter table realB add column pic2 text;
  • 删除和修改列都没有办法,只能“改表名-新建表-搬移数据”这样迂回完成。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容