转:磁盘文件的正常读写与异步读写

在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中只支持对串口的异步操作。
如果返回值为FALSE并且读入的字节数也返回为0,则表示文件到达了末尾。
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 从文件末尾开始移动
函数返回值和参数lpDistanceToMoveHigh(当该参数不为NULL时)表明文件指针当前的位置(从文件头到当前的字节数),但当参数lpDistanceToMoveHigh为NULL时如果返回INVALID_SET_FILE_POINTER表明执行失败,当参数lpDistanceToMoveHigh不为NULL时如果返回INVALID_SET_FILE_POINTER还需要判断GetLastError的返回值是否不为NO_ERROR。下面是两种情况下判断错误。
//第一种情况
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中磁盘操作将耗费更多的时间,所以使用异步写更能体现优势,你可以将这个例子改为磁盘写后自己测试一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值