本來不打算寫這篇的,但是檔案的重要性大家都知道。在裝置I/O中,有一種裝置叫檔案裝置,這是一個抽象的概念,就把它理解為檔案就行了。
檔案裝置,可以通過CreateFile函數開啟,得到一個檔案物件控點。
在檔案中,有兩個比較重要的屬性:
1、檔案大小:在32位中最大為4GB,64位中可以達到16EB。
2、檔案讀寫指標:這個指標表明讀寫位置,大小範圍可以超出檔案的大小。
先討論檔案的大小。
要得到檔案的大小,可以使用GetFileSizeEx函數:
BOOL GetFileSizeEx(
HANDLE hFile, //檔案物件控點
PLARGE_INTEGER pliFileSize); //LARGE_INTEGER聯合的指標,返回大小
這個函數接受一個LARGE_INTEGER聯合的指標,用來返迴文件大小,這個結構可以表示64位值:
typedef union _LARGE_INTEGER {
struct {
DWORD LowPart; // 低32位值
LONG HighPart; // 高32位值
};
LONGLONG QuadPart; // 64位值得
} LARGE_INTEGER, *PLARGE_INTEGER;
從這個定義可以看出,該聯合可以用QuadPart表示一個64位值,也可以差分成兩個32位值。這個聯合有一個無符號數版本,叫做ULAGER_INTEGER聯合,對應的3個成員都是儲存的無符號數。
還有一個函數可以得到一個檔案的大小:
DWORD GetCompressedFileSize(
PCTSTR pszFileName, //檔案路徑名
PDWORD pdwFileSizeHigh); //檔案大小如果大於4GB,高32位值由該參數返回
這個函數接受一個檔案的路徑名稱,返迴文件大小的低32位值,高32位值由參數pdwFileSizeHigh返回。與GetFileSizeEx不同的是,該函數返回一個檔案的物理大小,而GetFileSizeEx返迴文件的邏輯大小。
比如一個檔案大小為100KB,它被壓縮為85KB,如果使用GetFileSizeEx,則返回100KB,使用GetCompressedFileSize則返回85KB。
與GetFileSizeEx不同的是,該函數接受一個字串,指明檔案路徑,這就可以直接查詢某個檔案大小,而不要先開啟它獲得它的控制代碼。
可以如下使用該函數:
ULARGE_INTEGER ulFileSize; //與LARGE_INTEGER聯合類似,儲存無符號數
ulFileSize.LowPart = GetCompressedFileSize(TEXT("SomeFile.dat"),
&ulFileSize.HighPart); //取得目前的目錄下的SomeFile.dat檔案大小
這樣,64位的檔案大小儲存在ulFileSize.QuadPart中。
討論完了檔案的大小,下面來討論檔案讀寫指標。
CreateFile函數建立或開啟了一個檔案核心對象,該核心對象中管理著一個“檔案讀寫指標”。該指標指明了一個64位的位移量。初始情況下,該指標設定為0,即你讀取或寫入資料的時候從檔案開始處進行,即從位移量為0的地方開始。每次讀取或寫入N位元組的資料,系統更新該讀寫指標,使位移量加上N個位元組。比如下面代碼反映了讀取檔案前100個位元組的資料:
BYTE pbFirst[50], pbSecond[50];
DWORD dwNumBytes;
HANDLE hFile = CreateFile(TEXT("MyFile.dat"), ...); //指標初始化為0
ReadFile(hFile, pbFirst, 50, &dwNumBytes, NULL); //讀取第0~49位元組
ReadFile(hFile, pbSecond, 50, &dwNumBytes, NULL);//讀取第50~99位元組
需要注意的是,一個檔案物件控點對應一個讀寫指標,如果一個檔案被開啟多次,那麼就有多個檔案對象,每個檔案對象管理著一個讀寫指標,這些指標相互之間不影響。比如下面的代碼:
BYTE pb[10];
DWORD dwNumBytes;
HANDLE hFile1 = CreateFile(TEXT("MyFile.dat"), ...); //指標初始化為0
HANDLE hFile2 = CreateFile(TEXT("MyFile.dat"), ...); //指標初始化為0
ReadFile(hFile1, pb, 10, &dwNumBytes, NULL); //讀取第0~9位元組
ReadFile(hFile2, pb, 10, &dwNumBytes, NULL); //也是讀取第0~9位元組
上面這段代碼,hFile1和hFile2是同一個檔案的兩個不同的檔案核心對象的控制代碼,這兩個核心對象管理著兩個不同檔案指標,所以改變其中一個的讀寫指標,不會影響另一個。
下面這段代碼更能說明問題:
BYTE pb[10];
DWORD dwNumBytes;
HANDLE hFile1 = CreateFile(TEXT("MyFile.dat"), ...); //讀寫指標初始化為0
HANDLE hFile2; //另一個檔案控制代碼
//將本進程內hFile1控制代碼值複製給本進程中的hFile2
DuplicateHandle(
GetCurrentProcess(), hFile1,
GetCurrentProcess(), &hFile2,
0, FALSE, DUPLICATE_SAME_ACCESS);
ReadFile(hFile1, pb, 10, &dwNumBytes, NULL); //讀取第0~9位元組
ReadFile(hFile2, pb, 10, &dwNumBytes, NULL); //讀取第10~19位元組
上面這段代碼,使用DuplicateHandle函數複製控制代碼,使得兩個控制代碼hFile1和hFile2共用同一個檔案核心對象,因此讀寫指標也是共用的。
可以使用SetFilePointerEx函數來定位檔案讀寫指標:
BOOL SetFilePointerEx(
HANDLE hFile, //檔案核心物件控點
LARGE_INTEGER liDistanceToMove, //64位元,移動位元組數
PLARGE_INTEGER pliNewFilePointer, //返回新的檔案讀寫指標位置
DWORD dwMoveMethod); //移動方式
該函數中dwMoveMethod告訴系統如何移動。FILE_BEGIN,表示從檔案頭開始移動;FILE_END,表示從檔案尾往前移動;FILE_CURRENT,表示從當前讀寫指標位置移動。移動的位移量在第2個參數liDistaceToMove中。
有幾點需要注意
- 將檔案讀寫指標的位置設定為超過檔案大小範圍是合法的。這麼做不會使得檔案大小變大,除非調用函數SetEndOfFile。
- 當開啟檔案使用函數CreateFile時,該函數的dwFlagsAndAttributes參數中包括FILE_FLAG_NO_BUFFERING,檔案讀寫指標只能被設定為硬碟扇區的單位大小。
- 沒有GetFilePointerEx函數來取得當前檔案指標位置,可以調用SetFilePointerEx函數來得到其位置,要把第二個參數設定為0,如下代碼:
LARGE_INTEGER liCurrentPosition = { 0 };
SetFilePointerEx(hFile, liCurrentPosition,
&liCurrentPosition,FILE_CURRENT);
當檔案被關閉的時候,系統會在檔案上設定一個結束位置,以確定該檔案的大小。當然,你也可以自己設定檔案的結束位置,以此來改變檔案的大小。使用SetEndOfFile函數:
BOOL SetEndOfFile(HANDLE hFile);
該檔案在當前的檔案讀寫指標處設定檔案的結束標誌,來截斷或擴充檔案的大小。比如,你想設定一個檔案的大小為1024位元組的話,可以通過以下代碼實現:
HANDLE hFile = CreateFile(...);
LARGE_INTEGER liDistanceToMove;
liDistanceToMove.QuadPart = 1024;
//設定檔案指標
SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN);
SetEndOfFile(hFile); //在檔案指標處設定結束標誌
CloseHandle(hFile);