• 数据持久化 技术比较


     

    【数据存储】coredata、sqlite、fmdb和sqlitepersistentobject   

     
    管理提醒: 本帖被 angellixf 从 OS X开发讨论区 移动到本区(2014-02-12)
     
    • 分享类型:应用开发相关
    在ios开发过程中,经常需要用到数据持久化工作。对于基本的配置信息等,NSUserDefault已经可以满足要求,但是对于大部分需要存储的信息,主要的方式有coredata建模或者sqlite写数据库的方法进行存储。现在针对coredata、sqlite以及常用的sqlite封装库进行研究和学习。

        首先,针对四种数据持久化方式,进行基本的10w条数据插入得到性能耗时如下(由于sqlitepersistentobject未找到合适的批量插入方法,所以没有进行对比):

      sqlite          1470ms
      fmdb           3088ms
      coredata    3418ms


    从中可以看出,sqlite最快,基本都只用fmdb以及coredata等的一半时间。接下来我们逐个解析相关技术/库的操作以及使用。



    【coreData】
        coredata相信基本都不陌生,我们常用的主要就是Data Model了,有两种方法添加Data Model:新建工程时勾选或者添加Core Data->Data Model文件。采用第一种方法默认会有相关的代码生成,为了更好的了解Core Data是怎么load进来的,我们采用第二种方法进行演示。

    首先,我们添加CoreData->Model Data模型,名字输入为coreData,即可在文件列表中见到coreData.xcdatamodeld文件。我们先添加如下Entities:



    我们已经有了模型,那么该怎样使用呢。有一个NSManagedObjectModel的类,专门用来管理数据模型的。先从coreData.xcdatamodeld中初始化模型:

     
    复制代码
    1. // initilize  
    2.     NSURL* modelURL = [[NSBundle mainBundle] URLForResource:@"coreData" withExtension:@"momd"];  
    3.     coreDataModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];</span>  
    4. 我们可以理解为,coreDataModel就代表了该模型。
    5. 接下来,我们需要考虑,有了模型后,数据最终应该存在哪里?答案是:文件。接下来我们有另外一个类来管理模型跟文件之间的对应关系:
    6. [code]NSString* strInfoPath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"coreData.sqlite"];  
    7. coreDataCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:coreDataModel];  
    8.       
    9. [coreDataCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:strInfoPath] options:nil error:nil];</span>  
    10. OK,现在文件<->模型 之间的映射关系也有了。那么接下来我们就需要去对模型数据库进行读写操作等。自然,有一个上下文用来执行/处理相关数据信息:
    11. [code]// 对context进行操作  
    12.     coreDataContext = [[NSManagedObjectContext alloc] init];  
    13.     [coreDataContext setPersistentStoreCoordinator:coreDataCoordinator];</span>  
    14. 我们尝试去插入一条数据:
    15. [code]NSManagedObject* object = [NSEntityDescription insertNewObjectForEntityForName:@"TestCoreData" inManagedObjectContext:coreDataContext];  
    16.           
    17. [object setValue:[NSNumber numberWithInt:data->intType] forKey:@"intType"];  
    18. [object setValue:[NSNumber numberWithFloat:data->floatType] forKey:@"floatType"];  
    19. [object setValue:[NSNumber numberWithDouble:data->doubleType] forKey:@"doubleType"];  
    20. [object setValue:[NSString stringWithUTF8String:data->testString] forKey:@"stringType"];
      

    插入数据过程中,首先获取一个NSManagedObject的对象,可以这么理解,NSManagedObject就代表了一条数据信息,我们用insertNewObjectForEntityForName往TestCoreData这个数据模型中添加了一条数据:object。后面的[object setValue:"***" forkey:"***"]即为该条插入的数据进行赋值。
    添加完毕数据后,内存中模型已经有添加的数据了。但是文件中还没有同步进去,那么我们调用:
    [coreDataContext save:nil];


    即可将刚才插入的数据保存到文件中。
    关于其他修改、删除等操作,以及core data中的releationship操作等,可以参考附件程序或查阅相关其他资料。
    【sqlite】

        sqlite是一款轻量级数据库,在c、c++、java以及其他各种产品中都有涉及到,xcode对sqlite也提供的原生支持。使用sqlite也不难,只要在Link Binary With Libraries中添加ibsqlite3.0.dylib(我是用的3.0版本),然后在需要使用的地方添加头文件:#include<sqlite3.h>即可。
        sqlite的操作很方便,也很直观:
     
    复制代码
    1. - (IBAction)onBtnSqlite:(id)sender {  
    2.       
    3.     // 初始化数据库要保存的地方,如果存在则删除  
    4.     NSString* strSQLiteFilePath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"sqlite.sqlite"];  
    5.       
    6.     BOOL bIsDir = FALSE;  
    7.     if ([[NSFileManager defaultManager] fileExistsAtPath:strSQLiteFilePath isDirectory:&bIsDir]) {  
    8.         [[NSFileManager defaultManager] removeItemAtPath:strSQLiteFilePath error:nil];  
    9.     }  
    10.       
    11.     sqlite3* sqlite = NULL;  
    12.       
    13.     // 首先打开数据库路径,如果不存在则创建  
    14.     if (SQLITE_OK != sqlite3_open([strSQLiteFilePath UTF8String], &sqlite)) {  
    15.         NSLog(@"sqlite3: open error...");  
    16.     }  
    17.       
    18.     // create table  
    19.     // 创建表,主要就是sql语句  
    20.     NSString* strCreateTable = @"CREATE TABLE TESTCOREDATA(intType INTEGER, floatType FLOAT, doubleType DOUBLE, stringType VARCHAR(256))";  
    21.     if (sqlite3_exec(sqlite, [strCreateTable UTF8String], nil, nil, nil) != SQLITE_OK) {  
    22.         NSLog(@"sqlite Create table error...");  
    23.     }  
    24.     // 接下来是生成10w条测试数据  
    25.     NSArray* arrayTest = [self arrayWithData:100000];  
    26.     NSLog(@"Before save...");  
    27.     // !!!这里很重要,将所有的insert操作作为一个transaction操作,这样避免每次insert的时候都去写文件,导致IO时间拖慢整个数据插入操作  
    28.     NSString* strBegin = @"BEGIN TRANSACTION";  
    29.     sqlite3_exec(sqlite, [strBegin UTF8String], NULL, NULL, NULL);  
    30.     // 遍历数据并插入,就是普通的sql语句操作  
    31.     for (NSValue* value in arrayTest)  
    32.     {  
    33.         Data* data = [value pointerValue];  
    34.           
    35.         NSString* strSQLInsert = [NSString stringWithFormat:@"INSERT INTO TESTCOREDATA(intType, floatType, doubleType, stringType) values(%d, %f, %lf, '%s')", data->intType, data->floatType, data->doubleType, data->testString];  
    36.           
    37.         if (SQLITE_OK != sqlite3_exec(sqlite, [strSQLInsert UTF8String], NULL, NULL, NULL))  
    38.         {  
    39.             const char* errormsg = sqlite3_errmsg(sqlite);  
    40.             NSLog(@"exec Error...");  
    41.         }  
    42.           
    43.         free(data);  
    44.     }  
    45.       
    46.     // 提交所有的插入操作  
    47.     NSString* strEnd = @"COMMIT";  
    48.     sqlite3_exec(sqlite, [strEnd UTF8String], NULL, NULL, NULL);  
    49.       
    50.     NSLog(@"End Save...");  
    51.       
    52.       
    53.     // 不使用的时候关闭即可  
    54.     sqlite3_close(sqlite);  
    55.     sqlite = NULL;  
    56.       
    57. }
      

    sqlite非常直观,并且依赖于sql语句,所以sqlite的有点在于灵活性高,上手简单并易于理解。缺点就是带来了很多底层数据库的操作,一般都需要自己再去进行数据建模并进行封装使用。
    相关读写步骤也很简单:
     
    复制代码
    1. - (IBAction)onBtnSqliteRead:(id)sender {  
    2.     sqlite3* sqlite = NULL;  
    3.       
    4.     NSString* strSQLiteFilePath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"sqlite.sqlite"];  
    5.       
    6.     BOOL bIsDir = FALSE;  
    7.     if (![[NSFileManager defaultManager] fileExistsAtPath:strSQLiteFilePath isDirectory:&bIsDir]) {  
    8.         NSLog(@"Sqlite Open Error....File NOt exist...");  
    9.         return;  
    10.     }  
    11.       
    12.     if (SQLITE_OK != sqlite3_open([strSQLiteFilePath UTF8String], &sqlite))  
    13.     {  
    14.         NSLog(@"sqlite open error...");  
    15.         return;  
    16.     }  
    17.       
    18.     NSString* strSQL = @"select * from TESTCOREDATA";  
    19.       
    20.     sqlite3_stmt* stmt;  
    21.     // 将对应的操作信息跟stmt进行bind,如果有相关条件可以在prepare之后进行调整  
    22.     sqlite3_prepare_v2(sqlite, [strSQL UTF8String], -1, &stmt, NULL);  
    23.     // 获取执行SQL的返回结果  
    24.     while (SQLITE_ROW == sqlite3_step(stmt)) {  
    25.         int nIntType = sqlite3_column_int(stmt, 0);  
    26.         float floatType = sqlite3_column_double(stmt, 1);  
    27.         double doubleType = sqlite3_column_double(stmt, 2);  
    28.         const unsigned char* strTest = sqlite3_column_text(stmt, 3);  
    29.           
    30.         break;  
    31.     }  
    32.       
    33.       
    34.       
    35.     sqlite3_close(sqlite);  
    36. }  
      


    【FMDB】
        fmdb是一个开源的库(https://github.com/ccgus/fmdb),主要的操作就是针对sqlite进行封装并提供了很多针对线程、多个sqlite实例的管理等相关操作。避免了sqlite操作过程中多行的各种bind,getcolumn等操作。
    要使用fmdb,将获取到的包中src下的文件(除了fmdb.m)拖到自己工程中(最好用一个group管理起来),然后添加libsqlite3.0.dylib即可。
    照例我们先来看下fmdb的基础用法:
     
    复制代码
    1. NSString* strSQLiteFilePath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:@"fmdb.sqlite"];  
    2.       
    3.     BOOL bIsDir = FALSE;  
    4.     if ([[NSFileManager defaultManager] fileExistsAtPath:strSQLiteFilePath isDirectory:&bIsDir]) {  
    5.         [[NSFileManager defaultManager] removeItemAtPath:strSQLiteFilePath error:nil];  
    6.     }  
    7.       
    8.     FMDatabase* db = [FMDatabase databaseWithPath:strSQLiteFilePath];  
    9.       
    10.     if (![db open]) {  
    11.         NSLog(@"db Open Error...");  
    12.     }  
    13.       
    14.     NSString* strCreateTable = @"CREATE TABLE TESTCOREDATA(intType INTEGER, floatType FLOAT, doubleType DOUBLE, stringType VARCHAR(256))";  
    15.       
    16.     [db executeUpdate:strCreateTable];  
    17.       
    18.     NSLog(@"begin ");  
    19.     [db beginTransaction];  
    20.       
    21.     NSArray* arrayTest = [self arrayWithData:100000];  
    22.       
    23.     for (NSValue* value in arrayTest)  
    24.     {  
    25.         Data* data = [value pointerValue];  
    26.           
    27.         NSString* strSQLInsert = [NSString stringWithFormat:@"INSERT INTO TESTCOREDATA(intType, floatType, doubleType, stringType) values(%d, %f, %lf, '%s')", data->intType, data->floatType, data->doubleType, data->testString];  
    28.         [db executeUpdate:strSQLInsert];  
    29.         free(data);  
    30.     }  
    31.       
    32.     [db commit];  
    33.     NSLog(@"end...");  
    34.       
    35.     [db close];  
    36.     db = nil;
      


    fmdb使用比直接调用sqlite要省略很多代码和相关参数(估计让很多人很头疼),整个使用过程感觉就是四个字:酣畅淋漓。只将必要的一些关键操作需要给出参数。符合大部分对性能要求不高的场合,非常方便。fmdb针对读取操作也一样方便,这里就不多讲。接下来我们看看fmdb中其他几个文件的用途,有几个比较有趣的东西可以细说。
    在我们拿到的src中,还有部分文件我们在基本的使用场景中很少用到的:


    FMDatabaseAdditions.*文件,我们可以打开h文件看下:

     
    复制代码
    1. /** Return `int` value for query 
    2.   
    3. @param query The SQL query to be performed.  
    4. @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 
    5. @return `int` value. 
    6. */  
    7.   
    8. - (int)intForQuery:(NSString*)query, ...;  
    9.   
    10. /** Return `long` value for query 
    11. @param query The SQL query to be performed. 
    12. @param ... A list of parameters that will be bound to the `?` placeholders in the SQL query. 
    13. @return `long` value. 
    14. */  
    15.   
    16. - (long)longForQuery:(NSString*)query, ...;
      

    很直观是不是,就是针对某些你确定只有一个返回值的select语句,就不需要再通过返回FMResultSet并遍历set获取了。提供简单、直观易懂的操作。


    FMDatabaseQueue.*提供多线程下针对db操作的一个队列。使用FMDatabaseQueue,我们在任何线程中,都可以操作:

     
    复制代码
    1. [queue inDatabase:^(FMDatabase *db) {  
    2.         // 操作db  
    3.     }];  
    4. 而由queue自己去保证执行的先后顺序和唯一性,避免同时操作时产生冲突等。
    5. FMDatabasePool*则提供了一个db池,每一个db实力都会放在FMDatabasePool的池中,使用完成后归还db即可,sqlite相关的生命周期都由db池进行管理。避免经常性的open和close操作。
    6. 具体的相关细节可以参考fmdb的源码。
    7. 【sqlitepersistentobject】
    8.     sqlitepersistentobject库是基于ORM模型编写,将没一条数据都封装成对应的一个对象,而且完全屏蔽相关表名、文件名等信息,采用sqlitepersistentobject时,将所有的信息都屏蔽在实现细节后面。操作的时候只要操作每一个对象即可。
    9. sqlitepersistentobject相关下载路径:https://code.google.com/p/sqlitepersistentobjects/
    10. 同样,要使用sqlitepersistentobject,首先将库下载下来后,将src中相关文件拉入到自己的工程。并添加libsqlite3.0.dylib。因为sqlitepersistentobject底层也是用的sqlite进行的操作。
    11. 先来看下基础的数据插入部分:
    12. [code]#import <Foundation/Foundation.h>  
    13. #import "SQLitePersistentObject.h"  
    14.   
    15. @interface ZJSqlitePersistentobjectsPerson : SQLitePersistentObject  
    16. {  
    17.     int intType;  
    18.     float floatType;  
    19.     double doubleType;  
    20.     NSString* stringType;  
    21. }  
    22.   
    23. @property(assign, nonatomic) int intType;  
    24. @property(assign, nonatomic) float floatType;  
    25. @property(assign, nonatomic) double doubleType;  
    26. @property(copy, nonatomic) NSString* stringType;  
    27. @end
      

    首先我们有一个数据类,继承自SQLitePersistentObject,每一个ZJSqlitePersistentobjectPerson对象都对应数据库中的一条记录。
    接下来,我们添加一批ZJSqlitePersistentobjectsPerson数据:


     
    复制代码
    1. NSArray* arrayTest = [self arrayWithData:1000];  
    2.       
    3.     NSLog(@"begin...");  
    4.       
    5.     for (NSValue* value in arrayTest)  
    6.     {  
    7.         Data* data = [value pointerValue];  
    8.           
    9.         ZJSqlitePersistentobjectsPerson* person = [[ZJSqlitePersistentobjectsPerson alloc] init];  
    10.         person.intType = data->intType;  
    11.         person.floatType = data->floatType;  
    12.         person.doubleType = data->doubleType;  
    13.         person.stringType = [NSString stringWithUTF8String:data->testString];  
    14.           
    15.         [person save];  
    16.         [person release];  
    17.         free(data);  
    18.     }  
    19.       
    20.     // [ZJSqlitePersistentobjectsPerson clearCache];  
    21.       
    22.     [ZJSqlitePersistentobjectsPerson clearCache];  
    23.       
    24.     NSLog(@"end...");  
      

    可以从插入过程中看到,整个操作过程中完全屏蔽了相关sql语句、表结构等细节。非常方便简单,不过sqlitepesistentobject只支持基础数据类型和实现了NSCoding等相关类,由此,对于相关集合类型(NSArray,NSSet,NSDictionary)等是不支持的。关于sqlitepersistentobject的其他细节,可以参考sqlitepersistentobject源码。


    【总结】


    综合比较coredata、sqlite、fmdb和sqlitepersistentobject等四种数据永久化方式,个人认为:
    1、如果只是基础数据类型并且对sql不熟悉,用sqlitepersistenttobject是最理想的,我们需要的是数据,sqlitepersistentobject需要你面对的也是一条一条的数据。
    2、如果需要操作一般的比较复杂的数据库以及类型,个人推荐用fmdb,非常方便和便于操作,而且sqlite本身还支持对数据加密的借口。
    3、如果需要很好的性能,可以使用sqlite并自己封装相关接口。
    4、如果需要基本的数据模型,并且对xcode可视化情有独钟的话,可以使用coredata->data model等。


    由于对四种数据存储类型只是浅尝辄止,其中不免疏漏的地方。
     
     
  • 相关阅读:
    特征选取1-from sklearn.feature_selection import SelectKBest
    使用K-S检验一个数列是否服从正态分布、两个数列是否服从相同的分布
    风控8-收码平台
    风控7-同盾设备指纹
    互联网黑产剖析_虚假号码
    风控3_iv算法详细解释
    woe_iv原理和python代码建模
    逾期30天和60天的回款概率
    (剑指Offer)面试题15:链表中倒数第k个结点
    (剑指Offer)面试题14:调整数组顺序使奇数位于偶数前面
  • 原文地址:https://www.cnblogs.com/iOS-mt/p/4242730.html
Copyright © 2020-2023  润新知