使用文件设备

来源:百度文库 编辑:神马文学网 时间:2024/04/30 11:12:23
使用文件设备

    因为文件的使用非常普遍,我将花一些时间来专门讨论适用于文件设备的一些要点。本节讨论了如何定位文件指针和改变文件大小。

    你必须明白的第一个要点是Windows被设计为可操作非常大的文件。微软的设计师们原本就选用了64-bit的数值来描述一个文件的大小,而不是32-bit。这就意味着一个文件理论上可达到16EB(exabytes)。

    在一个32-bit的操作系统上处理64-bit的数值,使得操作文件有点令人不快,因为许多Windows函数要求你以两个独立的32-bit数值来传递64-bit参数。不过你会发现,使用这些参数并不是很困难,而且在日常操作中,你不太可能会使用一个大于4GB的文件。也就是说表示一个文件大小的 64-bit数值的高32-bit通常总是为0。


取得文件大小

    当你使用文件时,经常需要获取文件的大小。最简单的方法是调用GetFileSizeEx:

BOOL GetFileSizeEx(
   HANDLE         hfile,
   PLARGE_INTEGER pliFileSize);

    第一个参数hfile是文件的句柄,pliFileSize参数是一个LARGE_INTEGER联合体的地址。LARGE_INTEGER联合使得一个 64-bit数既可以作为两个32-bit的数值,也可以作为一个单独的64-bit数值来引用,这在计算文件大小和偏移值时是相当方便的。这个联合看起来(基本上)如下所示:

typedef union _LARGE_INTEGER {
   struct {
      DWORD LowPart;    // Low  32-bit unsigned value
      LONG HighPart;    // High 32-bit signed value
   };
   LONGLONG QuadPart;   // Full 64-bit signed value
} LARGE_INTEGER, *PLARGE_INTEGER; 

    除了LARGE_INTEGER外,还有ULARGE_INTEGER联合用来描述一个无符号的64-bit数值:

typedef union _ULARGE_INTEGER {
   struct {
      DWORD LowPart;     // Low  32-bit unsigned value
      DWORD HighPart;    // High 32-bit unsigned value
   };
   ULONGLONG QuadPart;   // Full 64-bit unsigned value
} ULARGE_INTEGER, *PULARGE_INTEGER; 

 
    另外一个很有用的获取文件大小的函数是GetCompressedFileSize:

DWORD GetCompressedFileSize(
   PCTSTR pszFileName,
   PDWORD pdwFileSizeHigh);

    这个函数返回文件的物理大小,而GetFileSizeEx返回的是文件的逻辑大小。例如,考虑一个100-KB的文件被压缩后占用85 KB空间。调用GetFileSizeEx返回的是文件的逻辑大小100 KB,而GetCompressedFileSize返回的是文件在磁盘上实际占用的字节数85 KB。

    与GetFileSizeEx不同的是,GetCompressedFileSize的第一个参数用一个文件名作为字符串传递而不是使用句柄。 GetCompressedFileSize函数以一种与众不同的方式返回64-bit的数值:文件大小的低32-bit就是函数的返回值,高32- bit被放置于pdwFileSizeHigh参数指向的DWORD变量中。下面是ULARGE_INTEGER联合常见的用法:

ULARGE_INTEGER ulFileSize;
ulFileSize.LowPart = GetCompressedFileSize("SomeFile.dat",
   &ulFileSize.HighPart);
// 64-bit file size is now in ulFileSize.QuadPart 


定位文件指针

    调用CreateFile会使系统创建一个文件内核对象来管理与文件相关的各种操作。这个内核对象的内幕是一个文件指针。文件指针就是一个文件内的64- bit偏移值,指示下一次同步读写的位置。开始时,文件指针被设为0,所以如果你在调用CreateFile后立即调用ReadFile,你将从文件的偏移0处开始读。如果你从文件读了10字节到内存,系统会更新与文件句柄关联的指针,使得下次调用ReadFile时,从文件10字节处开始读。例如,看一下这段代码,其中文件的开始10字节被读入缓存,接着又把随后的10字节读入缓存:

BYTE pb[10];
DWORD dwNumBytes;
HANDLE hfile = CreateFile("MyFile.dat", ...); // Pointer set to 0
ReadFile(hfile, pb, 10, &dwNumBytes, NULL);   // Reads bytes  0 - 9
ReadFile(hfile, pb, 10, &dwNumBytes, NULL);   // Reads bytes 10 - 19


    因为每一个文件内核对象有自己的文件指针,故两次打开同一个文件会有稍微不同的结果:

BYTE pb[10];
DWORD dwNumBytes;
HANDLE hfile1 = CreateFile("MyFile.dat", ...); // Pointer set to 0
HANDLE hfile2 = CreateFile("MyFile.dat", ...); // Pointer set to 0
ReadFile(hfile1, pb, 10, &dwNumBytes, NULL);   // Reads bytes 0 - 9
ReadFile(hfile2, pb, 10, &dwNumBytes, NULL);   // Reads bytes 0 - 9


    在这个例子中,两个不同的内核对象管理同一个文件。由于每个内核对象都有自己的文件指针,用一个文件对象操作文件时不会影响另一个对象维护的文件指针,故文件开始处的10字节会被读两次。

    我想多举一个例子以使得这些内容更清晰:

BYTE pb[10];
DWORD dwNumBytes;
HANDLE hfile1 = CreateFile("MyFile.dat", ...); // Pointer set to 0
HANDLE hfile2;
DuplicateHandle(
   GetCurrentProcess(), hfile1, 
   GetCurrentProcess(), &hfile2,
   0, FALSE, DUPLICATE_SAME_ACCESS);
ReadFile(hfile1, pb, 10, &dwNumBytes, NULL);   // Reads bytes  0 - 9
ReadFile(hfile2, pb, 10, &dwNumBytes, NULL);   // Reads bytes 10 - 19


     在这个例子中,一个文件内核对象被两个文件句柄引用。不管用哪一个句柄操作文件,都只有一个文件指针被更新。和上个例子一样,每次都读出不同的字节。

    如果你需要随机访问一个文件,你就需要改变与文件内核对象相关联的文件指针。调用SetFilePointerEx可以做到这一点:

BOOL SetFilePointerEx(
   HANDLE         hfile,
   LARGE_INTEGER  liDistanceToMove,
   PLARGE_INTEGER pliNewFilePointer,
   DWORD          dwMoveMethod); 


 
    hfile参数指示你想改变其文件指针的文件内核对象。iDistanceToMove参数告诉系统你想移动指针多少个字节。你指定的数字是增加到文件指针的当前值,所以一个负数将引起文件指针的倒退。SetFilePointerEx的最后一个参数,dwMoveMethod,告诉 SetFilePointerEx如何解释liDistanceToMove参数。表 2-8 描述了你可以传递给dwMoveMethod的三个不同值以指定移动的起始点。


表 2-8.可传递给SetFilePointerEx的dwMoveMethod参数的值
参数值   含义
FILE_BEGIN  文件对象的文件指针被设为liDistanceToMove参数给出的值。注意liDistanceToMove被当成一个无符号的64-bit值。
FILE_CURRENT  文件对象的文件指针的值被加上liDistanceToMove。注意liDistanceToMove被当成一个有符号的64-bit值,你可以在文件中向后移动指针。
FILE_END  文件对象的文件指针被设为文件逻辑大小加上liDistanceToMove的值。注意liDistanceToMove被当成一个有符号的64-bit值,你可以在文件中向后移动指针。


    在SetFilePointerEx更新文件对象的文件指针后,文件指针的新值被返回到由pliNewFilePointer参数指向的LARGE_INTEGER。如果你对新指针值不感兴趣,可以传递NULL给pliNewFilePointer。

    关于SetFilePointerEx这里给出一些要注意的要点:

    ■  设置文件指针越过文件的当前长度末尾是合法的。这样做并不会实际增加文件在磁盘上的大小,除非你在这个位置写文件或调用SetEndOfFile。

    ■  对由FILE_FLAG_NO_BUFFERING标志打开的文件使用SetFilePointerEx时,文件指针只能被定位于对齐扇区的边界处。本章后面的FileCopy示例程序演示了如何正确使用这种方式。

    ■  Windows没有提供一个GetFilePointerEx函数,但是你可以使用SetFilePointerEx来获得期望的效果:

   LARGE_INTEGER liCurrentPosition = { 0 };
   SetFilePointerEx(hfile, liCurrentPosition, &liCurrentPosition, 
      FILE_CURRENT);



 
设置文件尾
 
    通常,系统在文件关闭时负责设置文件尾。不过,你可能有时想要使一个文件变大或变小。在这些情况下,调用

BOOL SetEndOfFile(HANDLE hfile);



    SetEndOfFile截断或延伸一个文件的末尾到文件对象的指针所指示的位置。例如,如果你想强制一个文件为1024字节长,你可以这样使用SetEndOfFile:

HANDLE hfile = CreateFile(...);
LARGE_INTEGER liDistanceToMove;
liDistanceToMove.QuadPart = 1024;
SetFilePointerEx(hfile, liDistanceToMove, NULL, FILE_BEGIN);
SetEndOfFile(hfile);
CloseHandle(hfile);


 
    用Windows Explorer查看该文件的属性,你会看到文件正好是1024字节长。