什么是数据持久化
内存在断点后就会清空重置,为了保持数据在断点后保持,比如手机重启,就需要把数据存放在硬盘里,做持久化的保存。为什么不一直使用硬盘保存,因为内存会比硬盘更快速。所以在程序的声明周期里,数据加载在内存里,进行快速的计算,然后把需要持久保存的数据保存到硬盘里。
数据持久化就是怎么把数据存储到硬盘里的技术,准确的说,存储是硬件干的事,而对软件而言,关键任务是组织和识别。就是一块数据,你怎么识别为你需要的内容,比如4个字节,可以是一个int数字,也可以是4个字符。所以关键是找到一种格式把数据组织好,存入硬盘,然后按对应的方式再识别。
计算机里有很多地方都是这个逻辑,如音视频的编解码,HTTP请求的数据解析,传输或存储之前都要有一个组织和识别的过程。
方式
- plist文件
- 归档Archive
- 数据库
有了上面的理解,再看这些,不同方式的区别也就是组织数据的方式的区别
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原生操作
-
建库
sqlite3_open
一句搞定,没有文件的会自动创建 建表
执行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_int
和sqlite3_column_int64
两个方法来指定长度读取。
亲和类型
除了5大基本类型外,在建表的时候可以写入其他类型,会根据亲和类型,把你写入的类型转到基本类型来存储。比如常见的char(20)
,这个是TEXT的亲和类型,数据库内部实际按TEXT存储,但建表的时候可以char(20)
这么写。
- 插入数据
insert into 表名称 (列名1,列名2) values (值1,值2)
然后执行sql语句就可以了。
注意:不指定列名时表示对所有列都插入数据;值是字符串类型的要使用引号包括起来;
- 查询数据
select * from 表名称
,或者指定查询的列select 列名1,列名2 from 表名称
然后后面可以接查询条件:
where子语句
- 比价和逻辑运算符 > = < like not between等,更多
- 条件是可以使用括号来做组合的,如
name like 'book90%' and age < 10 or age > 100
跟name 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用来指定递增或递减排序。
- 更新数据
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之间可循环操作多条数据。
- 删除数据
delete from 表名称 [条件语句]
,然后执行sql语句
- 事务
- begin transaction 开启事务
- commit 提交事务,把修改提交
- end(transaction) 结束事务,结束会自动commit
- rollback 回滚事务,上次commit或rollback之后的修改取消
使用事务的好处:
- 多个操作时发生错误,整体回滚,确保数据库的完整性,不会出现未知的中间状态。
- 大量插入数据时提高效率
- 表信息
-
.tables
查看所有表明 -
select * from sqlite_master;
查看表的信息 -
.schema
查看建表的sql语句 - 最有用的是:
pragma table_info("Book");
可以查看到每个表的列和对应的类型,这个让sqlite具有类似反射的能力,可以了解到自身的结构。
- 表修改
- 修改表名:
alter table Book rename to realB;
- 添加列:
alter table realB add column pic2 text;
- 删除和修改列都没有办法,只能“改表名-新建表-搬移数据”这样迂回完成。