- 第16章 使用文件
- import<Foundation/NSObject.h>
- import<Foundation/NSString.h>
- import<Foundation/NSFileManager.h>
- import<Foundation/NSAutoreleasePool.h>
- import<Foundation/NSDictionary.h>
- import<Foundation/NSObject.h>
- import<Foundation/NSString.h>
- import<Foundation/NSFileManager.h>
- import<Foundation/NSAutoreleasePool.h>
- import<Foundation/NSData.h>
第16章 使用文件
Foundation框架允许你利用文件系统对文件或目录执行基本操作。这些基本操作是由NSFileManager类提供的,这个类的方法具有如下功能:
·创建一个新文件
·从现有文件中读取数据
·将数据写入文件中
·重新命名文件
·删除文件
·测试文件是否存在
·确定文件的大小及其他属性
·复制文件
·测试两个文件的内容是否相同
上述多数操作也可以直接对目录进行。例如,可以创建目录,读取其中的内容,或者删除目录。另一个重要特性是链接文件的能力,也就是,同一个文件可以以不同的名字存在,这两个文件甚至可以位于不同的目录中。
用NSFileHandle类提供的方法,可以打开文件并对其执行多次读写操作。NSFileHandle类的方法可以实现如下功能:
·打开一个文件,执行读、写或更新(读写)操作
·在文件中查找指定位置
·从文件中读取特定数目的字节,或将特定数目的字节写入文件中NSFileHandle类提供的方法也可以用于各种设备或套接字。然而,本章我们只讨论普通文件的处理。
16.1 管理文件和目录:NSFileManager
对于NSFileManager,文件或目录是使用文件的路径名唯一地标识的。每个路径名都是一个NSString对象,它既可以是相对路径名,也可以是完整路径名。相对路径名是相对于当前目录的路径名。所以,文件名copy1.m意味着当前目录中的文件copy1.m。斜线字符用于隔开路径中的目录列表。文件名ch16/copy1.m也是相对路径,它标识存储在目录ch16中的文件copy1.m,而Ch16包含在当前目录中。
完整路径名,也称为绝对路径名,以斜线/开始。斜线实际上就是一个目录,称为根目录。在我的Mac上,主目录的完整路径名为/Users/stevekochan。这个路径名指定了三个目录:/(根目录)、Users和stevekochan。
这个特殊的代字符(~)用作用户主目录的缩写。因此,~linda表示用户linda的主目录的缩写,这个目录的路径可能是/Users/linda。单个代字符表示当前用户的主目录,这意味着路径名~/copy1.m将引用存储在当前用户主目录中的文件copy1.m。其他特殊的UNIX风格的路径名字符,如表示当前目录的和表示父目录的,应该在Foundation中文件处理方法使用路径之前,从路径名中删除。还可以使用一些路径实用工具,它们将在本章后面讨论。
在程序中,应该尽量避免使用硬编码的路径名。本章前面讲到过,可使用方法和函数来获取当前目录的路径名、用户的主目录以及可以用来创建临时文件的目录。应该尽可能地利用这些函数和方法。在本章后面会看到,Foundation有一个函数,它用于获取一列特殊的目录,如用户的Documents目录。
表16-1总结了一些基本的NSFileManager方法,这些方法用于处理文件。在表16-1中,path、path1、path2、from和to都是NSString对象;attr是一个NSDictionary对象;handler是一个回调处理程序,它允许你使用自己的方式来处理错误。如果对handler指定nil,就会采取默认的行为,即如果该操作成功,那么返回BOOL的方法就会返回YES,失败,就会返回NO。本章并不涉及编写自己的处理程序。
每个文件方法都是对NSFileManager对象的调用,而NSFileManager对象是通过向类发送一条defaultManager消息创建的,如下所示:
NSFileManager*fm;
……
fm=[NSFileManager defaultManager];
例如,要从当前目录删除名为todolist的文件,首先要创建一个NSFileManager对象(如前面所示),然后调用removeFileAtPath方法,代码如下:
[fm removeFileAtPath:@“todolist”handler:nil];
可以测试返回结果,以确保成功地删除该文件。
if([fm removeFileAtPath:@“todolist”handler:nil]==NO){
NSLog(@“Couldnt remove file todolist”);
return 1;
}
除了其他事情之外,属性字典还允许你指定要创建的文件的权限,以便获取或者更改现有文件的信息。对于文件创建,如果将该参数指定为nil,将会为该文件设置默认权限。fileAttributesAtPath:traverseLink:方法返回一个包含指定文件属性的字典。对于符号链接(symbolic link),traverseLink:参数的值为yes或no。如果该文件是一个符号链接,则指定yes,并且返回链接到的文件属性。如果指定no,则返回链接本身的属性。
对于现有的文件,属性字典包括各种信息,如文件的所有者、文件大小、文件的创建日期,等等。字典的每个属性都可以根据其键来提取,而所有键都定义在头文件<Foundation/NSFileManager.h>中。例如,表示文件大小的键为NSFileSize。
代码清单16-1展示了一些基本的文件操作。这个例子假设当前目录中存在一个名为testfile的文件,文件的内容如下:
This is a test file with some data in it.
Heres another line of data.
And a third.
代码清单16-1
//Basic file operations
//Assumes the existence of a file called“testfile”
//in the current working directory
import<Foundation/NSObject.h>
import<Foundation/NSString.h>
import<Foundation/NSFileManager.h>
import<Foundation/NSAutoreleasePool.h>
import<Foundation/NSDictionary.h>
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
NSString*fName=@“testfile”;
NSFileManager*fm;
NSDictionary*attr;
//Need to create an instance of the file manager
fm=[NSFileManager defaultManager];
//Lets make sure our test file exists first
if([fm fileExistsAtPath:fName]==NO){
NSLog(@“File doesnt exist!”);
return 1;
}
//Now lets make a copy
if([fm copyPath:fName toPath:@“newfile”handler:nil]==NO){
NSLog(@“File copy failed!”);
return 2;
}
//Lets test to see if the two files are identical
if([fm contentsEqualAtPath:fName andPath:@“newfile”]==NO){
NSLog(@“Files are not equal!”);
return 3;
}
//Now lets rename the copy
if([fm movePath:@“newfile”toPath:@“newfile2”
handler:nil]==NO){
NSLog(@“File rename failed!”);
return 4;
}
//Get the size of newfile2
if((attr=[fm fileAttributesAtPath:@“newfile2”
traverseLink:NO])==nil){
NSLog(@“Couldnt get file attributes!”);
return 5;
}
NSLog(@“File size is%i bytes”,
[[attr objectForKey:NSFileSize]intValue]);
//And finally, lets delete the original file
if([fm removeFileAtPath:fName handler:nil]==NO){
NSLog(@“File removal failed!”);
return 6;
}
NSLog(@“All operations were successful!”);
//Display the contents of the newly-created file
NSLog(@,[NSString stringWithContentsOfFile:@“newfile2”encoding:
NSUTF8StringEncoding error:nil]);
[pool drain];
return 0;
}
代码清单16-1输出
File size is 84 bytes
All operations were successful!
This is a test file with some data in it.
Heres another line of data.
And a third.
这个程序首先测试testfile文件是否存在。如果存在,则复制testfile文件,然后比较原文件和复制文件是否相等。经验丰富的UNIX用户都知道,不能只通过为方法copyPath:toPath:和movePath:toPath:指定目标目录,就将文件移动或复制到这个目录中;必须明确地指定目标目录中的文件名。
注意可以使用Xcode创建testfile,方法是选择File菜单中的New File……。在出现的左窗格中,突出显示Other,然后选择右侧窗格中的Empty File。键入testfile作为文件名,并且创建时该文件所在的目录一定要与可执行文件所处的目录相同。这将是项目的Build/Debug文件夹。
movePath:toPath:方法可以用来将文件从一个目录移到另一个目录中(也可以用来移动整个目录)。如果两个路径引用同一目录中的文件(如本例所示),其结果仅仅是重新命名这个文件。因此,在代码清单16-1中,使用这个方法将文件newfile重新命名为newfile2。
如表16-1所示,在执行复制、重命名或移动操作时,目标文件不能是已存在的;否则,操作将失败。
newfile2的大小是通过使用fileAttributesAtPath:traverseLink:方法确定的。测试并确保返回了一个非nil目录,然后使用NSDictionary类中的方法objectForKey:,并用键NSFileSize从字典中获得文件的大小。最后,显示字典中表示文件大小的整数值。
程序使用removeFileAtPath:handler:方法来删除原始文件testfile。
最后,使用NSString的stringWithContentsOfFile:方法将文件newfile2的内容读入一个字符串对象,然后这个对象作为参数传递给要显示的NSLog。
在代码清单16-1中,测试每个文件操作以检查它是否成功。如果任何一个操作失败,就会使用NSLog来记录错误,并且程序将通过返回一个非零的退出状态而退出。根据约定,每个非零值都表示一次程序失败,并且根据错误类型,这个值都是唯一的。如果正在编写命令行工具,这将是一项有用的技术,因为可以由另一个程序来测试返回值,比如从一个shell脚本中测试。
16.1.1 使用NSData类
使用文件时,需要频繁地将数据读入一个临时存储区,它通常称为缓冲区。当收集数据,以便随后将这些数据输出到文件中时,通常也使用存储区。Foundation的NSData类提供了一种简单的方式,它用来设置缓冲区、将文件的内容读入缓冲区,或将缓冲区的内容写到一个文件。有一点不要奇怪,对于32位应用程序,NSDATA缓冲区最多可存储2GB的数据。对于64位应用程序,最多可存储8EG(注意是EB),即80亿GB的数据。</p>
正如你所期望的,我们既可以定义不可变缓冲区(使用NSData类),也可以定义可变的缓冲区(使用NSMutableData类)。在本章和后续几章将介绍这个类的方法。
代码清单16-2展示了如何方便地将文件的内容读入内存缓冲区。
这个程序读取文件newfile2的内容,并将其写入一个名为newfile3的新文件中。从某种意义来说,它实现了文件的复制操作,尽管它采取的方式并不像方法copyPath:toPath:handler:那样直接。
代码清单16-2
//Make a copy of a file
import<Foundation/NSObject.h>
import<Foundation/NSString.h>
import<Foundation/NSFileManager.h>
import<Foundation/NSAutoreleasePool.h>
import<Foundation/NSData.h>
int main(int argc, char*argv[])
{
NSAutoreleasePool*pool=[[NSAutoreleasePool alloc]init];
NSFileManager*fm;
NSData*fileData;
fm=[NSFileManager defaultManager];
//Read the file newfile2
fileData=[fm contentsAtPath:@“newfile2”];
if(fileData==nil){
NSLog(@“File read failed!”);
return 1;
}
//Write the data to newfile3
if([fm createFileAtPath:@“newfile3”contents:fileData
attributes:nil]==NO){
NSLog(@“Couldnt create the copy!”);return 2;
}
NSLog(@“File copy was successful!”);
[pool drain];return 0;
}
代码清单16-2输出
File copy was successful!
NSData的contentsAtPath:方法仅仅接受一个路径名,并将指定文件的内容读入该方法创建的存储区;如果读取成功,这个方法将返回存储区对象作为结果,否则(例如,该文件不存在或者你不能读取),将返回nil。
方法createFileAtPath:contents:attributes:创建了一个具有特定属性(或者如果attributes参数提供为nil,则采用默认的属性值)的文件。然后,将指定的NSData对象内容写入这个文件中。在本例中,数据区包含前面读取的文件内容。