简介
使用过C或C++进行过文件操作的可能都清楚,文件操作主页就是那几个函数,打开文件、关闭文件、定位文件指针等等。在Windows操作系统中,文件操作保留了这些概念,但变得更为复杂。Windows下的采用文件句柄的方式来操作文件,当我们指定一个要操作的文件时,操作系统就需要为我们提供一个对应文件的文件句柄,我们要使用这个句柄来进行读写文件。操作系统也在内部为每个文件句柄维护一个读写指针。读写指针总是指向文件下一次要存取的位置,每次对文件的读写操作完成之后,读写指针总是指向文件下一次要存取的位置。Windows为我们提供了一些文件函数来进行文件的读写操作,但同时这些函数不仅仅局限于我们常规意义上的普通文件,windows对其做了很大的扩展。比如:串口、磁盘设备、网络文件以及目录等都可以使用这些文件函数。同时也增加了异步、共享、锁定等等内容。
读写文件
要对一个文件进行读写操作,第一步就是要打开这个文件。我们自然而然的会想到,Windows是不是为我们提供了一个OpenFile的函数来完成这项功能。答案是错误的,打开文件使用的函数是CreateFile函数。Windows创建和打开文件都使用CreateFile函数,我们只需要设置一下参数的不同即可。CreateFile函数的原型如下所示:
HANDLE CreateFile( LPCTSTR lpFileName,DWORD dwDesiredAccess,DWORD dwShareMode,LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile );
参数比较多,下面我们一个个来分析:
lpFileName:指向一个以0结尾的字符串,该字符串表示要创建和打开对象的名称(对文件来说即为文件名)。
dwDesiredAccess:表示要访问对象的类型,可以使GENERIC_READ或GENERIC_WRITE或两者的组合,来表示需要读取文件、写文件或读写文件
dwShareMode:共享方式,表示文件被打开之后是否允许其他进程以某种方式再次打开文件,可以使下列一些取值或者组合:
0---不允许文件再被打开
FILE_SHARE_DELETE---允许其他进程同时对文件进行删除
FILE_SHARE_READ---允许其他进程同时以读方式打开文件
FILE_SHARE_WRITE---允许其他进程同时以写方式打开文件
lpSecurityAttributes:安全属性,指定返回的文件句柄是否可以被子进程继承,如果参数设置为NULL,则表明无法继承,否则需要将参数指向一个SECURITY_ATTRIBUTES结构。
dwCreationDisposition:表明当文件存在或者不存在的情况下,程序采取的行为。可以取下面值:
CREATE_NEW---创建新文件,如果文件已经存在函数返回失败。
CREATE_ALWAYS---创建新文件,如果文件已经存在则清除原文件。
OPEN_EXISTING---打开存在的文件,当文件不存在时函数会返回失败。
OPEN_ALWAYS---如果文件已经存在,则打开,不存在则创建新文件。
TRUNCATE_EXISTING---打开文件并将文件截断为0,当文件不存在时返回失败。
dwFlagsAndAttributes:指定新建文件的属性以及对文件操作的方式,文件属性可以使下面值的组合
FILE_ATTRIBUTE_NORMAL---普通文件,设置这个属性时,其他属性都不会生效。
FILE_ATTRIBUTE_ARCHIVE---设置归档属性。
FILE_ATTRIBUTE_HIDDEN---设置隐藏属性。
FILE_ATTRIBUTE_READONLY---设置只读属性。
FILE_ATTRIBUTE_SYSTEM---设置系统属性
FILE_ATTRIBUTE_TEMPORARY---临时文件,系统会尽量把所有的文件内容保存在内存中以加快存取熟读,程序在不再使用文件的时候需要尽快将它删除。
文件操作方式有
FILE_FLAG_WRITE_THROUGH---使用WriteThrough模式,系统不会对文件使用缓存,文件的改变会立刻写入磁盘中。
FILE_FLAG_OVERLAPPED---使用异步方式操作文件
FILE_FLAG_DELETE_ON_CLOSE---文件被关闭后立即被操作系统自动删除。
FILE_FLAG_RANDOM_ACCESS---对文件进行随机读写操作(操作系统对该文件的缓存进行优化)。
hTemplateFile:指定了一个文件模板的句柄,该模板的所有属性都会被复制到当前创建的文件中,一般建议设置为NULL。
返回值:当打开或者创建文件成功的时候,函数返回一个文件句柄,失败的话,函数的返回值是INVALID_HANDLE_VALUE(注意这个值是-1不是NULL)。在上一篇文章的代码中我们这样来判断函数是否成功。
if( INVALID_HANDLE_VALUE==
(hf = CreateFile( ofn.lpstrFile, GENERIC_READ, 0,
(LPSECURITY_ATTRIBUTES) NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, (HANDLE) NULL )))
{
MessageBox( hwnd, TEXT("打开文件失败!" ), TEXT("Error"), MB_OK | MB_ICONERROR );
return 0;
}
终于把这个函数讲完了,下面我们终于开始读写文件了。
我们使用ReadFile/WriteFile函数来完成读写功能。先看一下ReadFile函数。
BOOL ReadFile(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped )
各参数的意义如下:
hFile:文件句柄
lpBuffer:一个缓冲区指针,函数会将读出的数据放到这里。
nNumberOfBytesToRead:指定需要读入的字节数。
lpNumberOfBytesRead:由于函数并不是每次都能读出nNumberOfBytesToRead个字节数(原因有很多),这个参数指向一个dword类型的变量,函数在这里返回实际读入的字节数。
lpOverlapped:指向一个OVERLAPPED结构,供函数在异步读取文件时使用,在同步读写中这个参数设置为NULL。
如果读取文件失败,函数返回0,成功返回非0值,当函数返回非0并且lpNumberOfBytesRead中返回的已读取字节数为0时,表示已经读到文件尾。
向文件中写数据使用WriteFile函数,其参数和ReadFile相同。当使用WriteFile写文件的时候,写入的数据可能被Windows暂时保存在内部的高速缓存中,而不是写到磁盘中,如果文件关闭之前计算机突然断电了,那么写入的数据有可能会丢失,所以有时我们需要保证数据已经写入磁盘中了,我们可以使用FlushFileBuffers函数,这个函数的参数只有一个文件句柄。
在C语言中我们知道有个文件指针的东西,同样在Windows中,这个东西依然存在,Windows为每一个打开的文件都维护一个文件指针,指定在文件中下一个读操作或写操作在什么位置进行,当文件刚被打开的时候,文件指针处于文件的头部。
有时候我们需要调整文件指针的位置,这个是偶我们就需要使用SetFilePointer函数
DWORD SetFilePointer( HANDLE hFile, LONG lDistanceToMove, PLONG lpDistanceToMoveHigh, DWORD dwMoveMethod );
hFile:文件句柄
lDistanceToMoe:要移动的距离。
lpDistanceToMoveHigh:指向一个32位的变量,变量存放移动距离的高32位,这个值和lDistanceToMove一起组成一个64位的距离。在80x86平台上,这个值设置为NULL。
dwMoveMethod:移动的模式,也就是指明从什么地方开始移动,可以为FILE_BEGIN、FILE_CURRENT、FILE_END。
需要注意的是,Windows并没有为我们提供一个GetFilePointer的函数来获取当前文件的指针位置,我们可以像下面这么做来完成这项功能。
SetFilePointer( hFile, 0, NULL, FILE_CURRENT );
最后,别忘了关闭文件,关闭文件我们使用CloseHandle函数,同样只需要传入一个文件句柄。
文件属性
我们在windows中的一个文件中,右键单击属性,会弹出一个类似于下图的属性对话框。
一个文件的属性有很多,常用的包括上图中标出来的部分,比如文件的类型、大小、日期、文件是只读的还是隐藏的等等。如果我们在程序中需要获取这些属性可以使用下面的函数。
DWORD GetFileType( HANDLE hFile )
这个函数用来获取文件的类型,这个类型实际是指传入的文件句柄对应的是什么对象。(上面曾经提过文件句柄的范围是很广的),返回值可以是FILE_TYPE_UNKNOWN:未知,FILE_TYPE_DISK:磁盘文件,FILE_TYPE_CHAR:字符设备,FILE_TYPE_PIPE:管道
DWORD GetFileSize( HANDLE hFile, LPDWORD lpFileSizeHigh )
第二个参数是要用来接收文件长度的指针,我们在上一篇文章的示例中使用过这个函数。
BOOL GetFileTime( HANDLE hFile, LPFILETIME lpCreationTime, LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime )
在上图中我们已经看到文件的时间包括三种时间,后三个参数就分别对应了这三个值,需要注意的是,参数是一个FILETIME结构体的指针,我们一般需要使用FileTimeToSystemTime函数来把FILETIME结构体转换成SYSTEMTIME结构。与这个函数相对应的,我们可以使用SetFileTime函数来设置文件的时间。当然我们也是预先填好SYSTEMTIME结构体,然后使用SystemTimeToFileTime函数来转换成FILETIME结构。最后作为SetFileTime函数的参数传入。
DWORD GetFileAttributes( LPCTSTR lpFileName )
我们使用这个函数来获取文件的只读、隐藏等属性,注意的是这个函数使用文件名作为输入而不是使用文件句柄。我们根据返回值来判断,如只读文件的返回值为FILE_ATTRIBUTE_READONLY,隐藏的返回值为FILE_ATTRIBUTE_HIDDEN。
其他
除了上面的文件操作之外,windows还为我们提供了许多其他有用的文件操作。
CopyFile/CopyFileEx
BOOL CopyFile( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, BOOL bFailIfExists )
第一个参数指出源文件的文件名,第二个参数指示目的文件的文件名,第三个参数指定如果目的文件已经存在时所发生的动作,如果TRUE,则拷贝失败,否则,继续拷贝并覆盖原文件。CopyFileEx函数多了一个回调函数,一般用于大型文件的拷贝。
MoveFile/MoveFileEx
BOOL MoveFIle( LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName )
参数含义同上,需要注意的是目的文件名必须不存在,否则文件移动失败。
BOOL DeleteFile( LPCTSTR lpFileName )
需要注意的是不能对一个已经打开的文件进行删除,必须首先用CloseHandle函数关闭文件之后才能执行成功。
查找文件相对来讲要比上面的复杂一些,查找文件我们需要首先使用FindFirstFile函数,如果函数执行成功,返回一个句柄hFindFile。然后我们需要用这个句柄来调用FindNextFile函数。一般查找文件的时候我们需要使用如下结构:
hFindFile = FindFirstFile( lpFileName, lpFindFileData );
if( hFindFile != INVALID_HANDLE_VALUE )
{
do{
// 处理找到的文件
}while(FindNextFile(hFileFile, lpFindFileData))
}
其中lpFileName代表要查找的文件名,如果文件名中不包含路径,那么在当前的目录中查找,如果包含路径就在指定的路径中去查找,在文件名中可以使用*或?通配符。比如*.*表示所有文件,*.txt表示所有文本文件,a??.*表示所有开始字母是a并且文件名长度为3的文件。lpFindFileData指向一个WIN32_FIND_DATA结构,结构包括了Windows查找过程中临时使用的数据和找到的文件名与文件属性等数据。其定义如下:
现在,我们应该明白在windows的资源管理器或许多杀毒软件中,查找一个文件以及全盘查找是如何进行操作的了。大家也可以自己动手试一试。