IOS 应用文件数据的读写分析

来源:互联网 发布:网络咨询医生聊天技巧 编辑:程序博客网 时间:2024/05/01 22:32

转自 http://blog.csdn.net/linux_zkf/article/details/7797864

作者:朱克锋

邮箱:zhukefeng@iboxpay.com

转载请注明出处:http://blog.csdn.net/linux_zkf


iPhone OS提供了如下几种读、写、和管理文件的方法:
    ▪Foundation框架:
        如果您可以将应用程序数据表示为一个属性列表,则可以用NSPropertyListSerialization API来将属性列表转换为一个NSData对象,然后通过NSData类的方法将数据对象写入磁盘。
        如果应用程序的模型对象采纳了NSCoding协议,则可以通过NSKeyedArchiver类、特别是它的archivedDataWithRootObject:方法将模型对象图进行归档。
        Foundation框架中的NSFileHandle类提供了随机访问文件内容的方法。
        Foundation框架中的NSFileManager类提供了在文件系统中创建和操作文件的方法。
    ▪Core OS调用:
        诸如fopen、fread、和fwrite这些调用可以用于对文件进行顺序或随机读写。
        mmap和munmap调用是将大文件载入内存并访问其内容的有效方法。
请注意:上面的Core OS调用列表只是列举一些较为常用的例子。更完全的可用函数列表请参见iPhone OS手册的第三部分中的函数列表。
本章的下面部分将描述如何使用一些高级技术来进行文件的读写。有关Foundation框架中与文件相关类的更多信息,请参见Foundation框架参考。
属性列表数据的读写
属性列表是一种数据表示形式,用于封装几种Foundation(及 Core Foundation)的数据类型,包括字典、数组、字符串、日期、二进制数据、数值及布尔值。属性列表通常用于存储结构化的配置数据。举例来说,每个Cocoa和iPhone应用程序中都有一个Info.plist文件,它就是用于存储应用程序本身配置信息的属性列表。您自己也可以用属性列表来存储其它信息,比如应用程序退出时的状态等。
在代码中,属性列表的构造通常从构造一个字典或数组、并将它作为容器对象开始,然后在容器中加入其它的属性列表对象,(可能)包含其它的字典和数组。字典的键必须是字符串对象,键的值则是NSDictionary、NSArray、NSString、NSDate、NSData、和NSNumber类的实例。
对于可以将数据表示为属性列表对象的应用程序(比如NSDictionary对象),您可以用程序清单6-2所示的方法来将属性列表写入磁盘。该方法将属性列表序列化为NSData对象,然后调用writeApplicationData:toFile:方法(其实现如程序清单6-4所示)将数据写入磁盘。
程序清单6-2  将属性列表对象转换为NSData对象并写入存储
 
- (BOOL)writeApplicationPlist:(id)plist toFile:(NSString *)fileName {
       
    NSString *error;
       
    NSData *pData = [NSPropertyListSerialization dataFromPropertyList:plist format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
       
    if (!pData) {
       
        NSLog(@"%@", error);
       
        return NO;
       
    }
       
    return ([self writeApplicationData:pData toFile:(NSString *)fileName]);
       
}
    
在iPhone OS系统上保存属性列表文件时,采用二进制格式进行存储是很重要的。在编码时,可以通过为dataFromPropertyList:format:errorDescription:方法的format 参数指定NSPropertyListBinaryFormat_v1_0值来实现。二进制格式比其它基于文本的格式紧凑得多,这种紧凑不仅使属性列表在用户设备上占用的空间最小,还可以减少读写属性列表的时间。
程序清单6-3的代码展示了如何从磁盘装载属性列表,并重新生成属性列表中的对象。
程序清单 6-3 从应用程序的Documents目录读取属性列表对象
 
- (id)applicationPlistFromFile:(NSString *)fileName {
       
    NSData *retData;
       
    NSString *error;
       
    id retPlist;
       
    NSPropertyListFormat format;
       
    retData = [self applicationDataFromFile:fileName];
       
    if (!retData) {
       
        NSLog(@"Data file not returned.");
       
        return nil;
       
    }
       
    retPlist = [NSPropertyListSerialization propertyListFromData:retData  mutabilityOption:NSPropertyListImmutable format:&format errorDescription:&error];
       
    if (!retPlist){
       
        NSLog(@"Plist not returned, error: %@", error);
       
    }
       
    return retPlist;
       
}
    
有关属性列表和NSPropertyListSerialization类的更多信息,请参见属性列表编程指南。
用归档器进行数据读写
归档器的作用是将任意的对象集合转换为字节流。这听起来像是NSPropertyListSerialization类采用的过程,但它们之间有一个重要的区别。属性列表序列化只能转换一个有限集合的数据类型(大多数是数量类型),而归档器可以转换任意的Objective-C对象、数量类型、数组、结构、字符串、及更多其它类型。
归档过程的关键在于目标对象的本身。归档器操作的对象必须遵循NSCoding协议,该协议定义了读写对象状态的接口。归档器在编码一组对象时,会向每个对象发送一个encodeWithCoder:消息,目标对象则在这个方法中将自身的关键状态信息写入到对应的档案中。解档过程的信息流与此相反,在解档过程中,每个对象都会接收到一个initWithCoder:消息,用于从档案中读取当前状态信息,并基于这些信息进行初始化。解档过程完成后,字节流就被重新组成一组与之前写入档案时具有相同状态的新对象。
Foundation框架支持两种归档器—顺序归档和基于键的归档。基于键的归档器更加灵活,是应用程序开发中推荐使用的归档器。下面的例子显示如何用一个基于键的归档器对一个对象图进行归档。_myDataSource对象的representation方法返回一个单独的对象(可能是一个数组或字典),指向将要包含到档案中的所有对象,之后该数据对象就被写入由myFilePath变量指定路径的文件中。
 
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[_myDataSource representation]];
       
[data writeToFile:myFilePath atomically:YES];
    
请注意:您还可以向NSKeyedArchiver对象发送archiveRootObject:toFile:消息,以便在一个步骤中完成档案的创建和将档案写入存储。
您可以简单地通过相反的流程来装载磁盘上的档案内容。在装载磁盘数据之后,可以通过NSKeyedUnarchiver类及其unarchiveObjectWithData:类方法来取回模型对象图。例如,您可以用下面的代码来解档之前例子中的数据:
 
NSData* data = [NSData dataWithContentsOfFile:myFilePath];
       
id rootObject = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    
更多如何使用归档器和如何使对象支持NSCoding协议的信息,请参见Cocoa的归档和序列化编程指南。
将数据写到Documents目录
有了封装应用程序数据的NSData对象(或者是档案,或者是序列化了的属性列表)之后,您就可以调用程序清单6-4所示的方法来将数据写到应用程序的Documents目录中。
程序清单6-4  将数据写到应用程序的Documents目录
 
- (BOOL)writeApplicationData:(NSData *)data toFile:(NSString *)fileName {
       
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
       
    NSString *documentsDirectory = [paths objectAtIndex:0];
       
    if (!documentsDirectory) {
       
        NSLog(@"Documents directory not found!");
       
        return NO;
       
    }
       
    NSString *appFile = [documentsDirectory stringByAppendingPathComponent:fileName];
       
    return ([data writeToFile:appFile atomically:YES]);
       
}
    
从Documents目录读取数据
为了从应用程序的Documents目录读取文件,您首先需要根据文件名构建相应的路径,然后以期望的方法将文件内容读入内存。对于相对较小的文件—也就是尺寸小于几个内存页面的文件—您可以用程序清单6-5中的代码来取得文件内容。该代码首先为Documents目录下的文件构建一个全路径,并为这个路径创建一个数据对象,然后返回。
程序清单6-5  从应用程序的Documents目录读取数据
 
- (NSData *)applicationDataFromFile:(NSString *)fileName {
       
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
       
    NSString *documentsDirectory = [paths objectAtIndex:0];
       
    NSString *appFile = [documentsDirectory stringByAppendingPathComponent:fileName];
       
    NSData *myData = [[[NSData alloc] initWithContentsOfFile:appFile] autorelease];
       
    return myData;
       
}
    
对于载入时需要多个内存页面的文件,应该避免一次性地装载整个文件。如果您只是计划使用部分文件,这一点就尤其重要。对于大文件,您应该考虑用mmap函数或NSData的initWithContentsOfMappedFile:方法来将文件映射到内存。

到底是采用映射文件还是直接装载取决于您的考虑。如果只需要少量(3-4)内存页面,则将整个文件载入内存相对安全一些。但是,如果您的文件需要数十或上百个页面,则将文件映射到内存可能更为有效一些。当然,无论采用什么方法,您都应该测量应用程序的性能,确定装载文件和为其分配必要内存需要多长时间