在Win32系统下文件可以支持平常的同步读写和异步读写(但在Win9X下,Win32系统不支持磁盘文件的异步读写)。本节在后面部分将会介绍文件的异步读写,最后一段内容将向大家讲解一下文件的区域加锁。
在Win32系统中支持64位长度的文件,所以在很多文件操作函数中需要两个DWORD参数来表示文件长度,一个DWORD用来表示低32位,另一个用来表示高32位。
文件的读写进行在文件被正确打开后,但请确认在打开文件时设置了正确的读写标记。在Win32的文件操作中没有了以前类似与以前ANSI C中的fputs fgets fprintf fscanf等函数,只有类似于fread和fwrite的ReadFile和WriteFile函数。
ReadFile用于文件读,函数原型为:
BOOL ReadFile( HANDLE hFile, // handle to file LPVOID lpBuffer, // data buffer DWORD nNumberOfBytesToRead, // number of bytes to read LPDWORD lpNumberOfBytesRead, // number of bytes read LPOVERLAPPED lpOverlapped // overlapped buffer );其中各项参数的含义为:
- hFile:文件句柄,为CreateFile时返回的句柄
- lpBuffer:保存读入的数据的指针
- nNumberOfBytesToRead:指定需要读入的字节数
- lpNumberOfBytesRead:返回实际读入的字节数
- lpOverlapped:在文件异步读写时使用的数据,在同步读写中全部都设置为NULL,在Win9X中只支持对串口的异步操作。
WriteFile用于文件写,函数原型为: BOOL WriteFile( HANDLE hFile, // handle to file LPCVOID lpBuffer, // data buffer DWORD nNumberOfBytesToWrite, // number of bytes to write LPDWORD lpNumberOfBytesWritten, // number of bytes written LPOVERLAPPED lpOverlapped // overlapped buffer );参数的含义和ReadFile类似。
如果需要移动文件指针到相关位置(和文件读写不同,这个函数没有异步版本),使用
DWORD SetFilePointer( HANDLE hFile, // handle to file LONG lDistanceToMove, // bytes to move pointer PLONG lpDistanceToMoveHigh, // bytes to move pointer DWORD dwMoveMethod // starting point );其中各项参数的含义为:
- hFile:文件句柄,为CreateFile时返回的句柄
- lpBuffer:保存读入的数据的指针
- lDistanceToMove:移动的字节数低DWORD
- lpDistanceToMoveHigh:移动的字节数高DWORD,为了支持64位(2的64次方字节)长度的大文件,而用来指定64字节的高32位,如果文件大小只需要32位就可以表示,则设置为NULL
- ldwMoveMethod:移动方法,可以选择下面的值。
FILE_BEGIN 从文件开始处开始移动
FILE_CURRENT 从文件开始除开始移动
FILE_END 从文件末尾开始移动
//第一种情况 DWORD dwPtr = SetFilePointer (hFile, lDistance, NULL, FILE_BEGIN) ; if (dwPtr == INVALID_SET_FILE_POINTER) // Test for failure { // Obtain the error code. dwError = GetLastError() ; // 处理错误 // . . . } // End of error handler //第二种情况 dwPtrLow = SetFilePointer (hFile, lDistLow, & lDistHigh, FILE_BEGIN) ; // Test for failure if (dwPtrLow == INVALID_SET_FILE_POINTER && (dwError = GetLastError()) != NO_ERROR ) { // 处理错误 // . . . } // End of error handler在Win32中没有提供得到文件当前指针位置的函数,但通过SetFilePointer也可以确定文件指针当前的位置。在MSDN中提供了两个宏来得到当前文件的指针位置:
//对32位长度的文件 #define GetFilePointer(hFile) SetFilePointer(hFile, 0, NULL, FILE_CURRENT) //对超过32位长度的文件 #define GetVLFilePointer(hFile, lpPositionHigh) / (*lpPositionHigh = 0, / SetFilePointer(hFile, 0, lpPositionHigh, FILE_CURRENT))对了可以通过SetFilePointer来得到文件长度,方法就是从文件位结束处移动0字节,返回值就是文件的长度。
HANDLE hFile = CreateFile(...);//打开文件进行读 DWORD dwLen; dwLen = SetFilePointer (hFile, 0, NULL, FILE_END) ; CloseHandle( hFile ) ;当然Win32中也提供了专门的函数来得到文件的大小
BOOL GetFileSizeEx( HANDLE hFile, // handle to file PLARGE_INTEGER lpFileSize // file size ); typedef union _LARGE_INTEGER { struct { DWORD LowPart; LONG HighPart; }; LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER;其中lpFileSize是一个联合数据,用来表明文件的大小。
文件的异步读写主要是用在大文件的读写上,当使用异步读写时,ReadFile和WriteFile会马上返回,并在读写完成时通知应用程序。
要使用异步功能首先需要在打开文件时指定FILE_FLAG_OVERLAPPED作为标记(dwFlagsAndAttributes),在读写文件时可以使用ReadFile/WriteFile或者ReadFileEx/WriteFileEx来进行读写,当调用不同的函数时读写完成后通知应用程序的方法有所不同的,。下面分别介绍这两种方法:
第一种方法,利用ReadFile/WriteFile,这对函数使用事件信号来进行读写完成的通知,由于磁盘读写是一个比较耗费时间的操作,而且现在的磁盘子系统可以在磁盘读写时脱离CPU而单独进行,例如使用DMA方式,所以在磁盘读写时可以进行其他一些操作以充分利用CPU。关于事件信号相关内容请参考4.4节 进程/线程间同步中事件部分内容。并且在文件读写时提供OVERLAPPED数据。
结构定义如下: typedef struct _OVERLAPPED { ULONG_PTR Internal; //系统使用 ULONG_PTR InternalHigh; //系统使用 DWORD Offset; // 文件读或写的开始位置低32位,对于命名管道和其他通信设备必须设置为0 DWORD OffsetHigh; // 文件读或写的开始位置高32位,对于命名管道和其他通信设备必须设置为0 HANDLE hEvent; // 事件量,当操作完成时,这个时间会变为有信号状态 } OVERLAPPED; //下面的代码演示了文件异步读取 //并且比较了同步和异步之间的性能差异 void DoDataDeal(BYTE *pbData,int iLen) {//对字节进行操作 Sleep(3*1000);//假设每次计算需要2秒钟 } //下面是使用异步读的示范代码,假设c:/temp/large_file.dat文件有130MB大小() //每次读入10MB字节,并且对文件中每个字节进行操作,由于可以使用异步操作所以可以在下一次读入数据的同时进行计算 void ReadM1(void) { HANDLE hFile = CreateFile("c://temp//large_file.dat",GENERIC_READ,0, NULL,OPEN_EXISTING,FILE_FLAG_OVERLAPPED|FILE_ATTRIBUTE_NORMAL,NULL); if( INVALID_HANDLE_VALUE != hFile ) { HANDLE hEvent = CreateEvent(NULL,FALSE,FALSE,"read_event"); BYTE *pbRead = new BYTE[1024*1024*10];//10MB字节 BYTE *pbBuf = new BYTE[1024*1024*10]; DWORD dwRead,dwCount=0; OVERLAPPED overlap; overlap.Offset = 0; overlap.OffsetHigh =0; overlap.hEvent = hEvent; DWORD dwBegin= GetTickCount();//记录开始时间 ReadFile(hFile,pbRead,1024*1024*10,&dwRead,&overlap); {//开始计算 for(int i=1;i<13;i++) { printf("M1 i=%d/n",i); WaitForSingleObject(hEvent,INFINITE);//等待上一次完成 memcpy(pbBuf,pbRead,1024*1024*10); overlap.Offset = i * (1024*1024*10); overlap.OffsetHigh =0; overlap.hEvent = hEvent; ReadFile(hFile,pbRead,1024*1024*10,&dwRead,&overlap); //在文件进行读的同时进行计算 DoDataDeal(pbBuf,1024*1024*10); } WaitForSingleObject(hEvent,INFINITE);//等待最后一次完成 memcpy(pbBuf,pbRead,1024*1024*10); //数据处理 DoDataDeal(pbBuf,1024*1024*10); }//结束计算 printf("耗时 %d/n",GetTickCount()-dwBegin); //操作完成 CloseHandle(hEvent); CloseHandle(hFile); delete pbRead; delete pbBuf; } } //下面是上面功能的文件同步读版本 void ReadM2(void) { HANDLE hFile = CreateFile("c://temp//large_file.dat",GENERIC_READ,0, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if( INVALID_HANDLE_VALUE != hFile ) { DWORD dwRead,dwCount=0; BYTE *pbRead = new BYTE[1024*1024*10];//10MB字节 BYTE *pbBuf = new BYTE[1024*1024*10];//10MB字节 DWORD dwBegin= GetTickCount();//记录开始时间 for(int i=0;i<13;i++) { printf("M2 i=%d/n",i); if(!ReadFile(hFile,pbRead,1024*1024*10,&dwRead,NULL)) { printf("error read"); break; } memcpy(pbBuf,pbRead,1024*1024*10); //计算 DoDataDeal(pbBuf,1024*1024*10); } printf("耗时 %d/n",GetTickCount()-dwBegin); //操作完成 CloseHandle(hFile); delete pbRead; delete pbBuf; } }在文件的异步读写中,如果ReadFile/WriteFile返回FALSE,则需要通过LastError来进一步确认是否发生错误,例如下面的错误检测代码:
bResult = ReadFile(hFile, &inBuffer, nBytesToRead, &nBytesRead, &gOverlapped) ; if (!bResult) switch (dwError = GetLastError()) { case ERROR_HANDLE_EOF: { //到达文件尾 } case ERROR_IO_PENDING: { //正在进行异步操作 } // end case } // end switch } // end if 此外可以通过GetOverlappedResult函数来得到异步函数的执行情况 BOOL GetOverlappedResult( HANDLE hFile, // handle to file, pipe, or device LPOVERLAPPED lpOverlapped, // overlapped structure LPDWORD lpNumberOfBytesTransferred, // bytes transferred BOOL bWait // wait option );如果函数调用返回FALSE则可以用GetLastError来得到错误,如果返回成功则可以通过lpNumberOfBytesTransferred参数来确定当前有多少数据已经被读或写。lpOverlapped参数必须与调用ReadFile或WriteFile时使用同一个数据区。最后一个参数bWait表明是否等待异步操作结束时才返回,如果设置为TRUE就可以等待文件读写完成时返回,否则就会马上返回,利用这个特点可以利用它来等待异步文件操作的结束(就如同等待事件变为有信号状态一样起到相同的作用)。
在上面的例子中没有过多的进行错误检查,如果大家有兴趣可以自己运行一下这个例子,看看异步文件读写对性能的影响。在我自己的计算机PII 450 IBM 30GB硬盘上同步读比异步读慢了大约10%左右,这主要时因为数据处理时间我设置为两秒钟,如果设置得足够长,会显示出异步和同步处理时的差异极限。此外由于磁盘缓存得作用也会影响结果,所以如果读入的数据量更大将会产生更明显的差异,这是因为虽然异步读写会在调用等待函数上也会耗费一些时间,所以如果数据量小就无法分辨出差异。
请记住OVERLAPPED参数在文件操作执行完以前不要手工修改结构内的值,因为系统会使用结构中的数据。
对于WriteFile操作也可以用相同方法,在WriteFile中磁盘操作将耗费更多的时间,所以使用异步写更能体现优势,你可以将这个例子改为磁盘写后自己测试一下。