資料檔案處理

 

  實 習 內 容






1. 文字模式檔案處理

2. 二進位模式檔案處理

3. 將資料直接以二進位格式儲存的好處與壞處

1

檔案 (File) 是作業系統中用來組織資料, 管理資料的基本單位, 檔案裡面包含多個位元組的資料。每一個檔案有一個唯一的檔案名稱, 檔案的存放位置 (路徑), 檔案的產生時間, 檔案的最後修改時間, 檔案所有人, 檔案存取權限控制的資料, 以及檔案備份狀況的記錄。我們寫的程式通常將一個檔案視為一連串的位元組, 可以依序讀取先前存放在檔案裡的資料。

文字模式的檔案處理: 檔案中特定的位元組 0x0d 0x0a (Windows 系統) 會被當成是列與列的區隔, 大家在這種檔案中存放的資料都是文字 (以 ASCII 編碼, Big5 編碼, 或是 Unicode 編碼)。程式在處理這種檔案時, 常常以一列為讀取的單位, 例如呼叫 fgets(string, size, stream) 函數的時候, 如果 string 字元陣列變數的大小 size 的數值夠大, 就會從檔案串流中讀取以 0x0d 0x0a 分隔的一整列資料到 string 字元陣列變數裡面, 同時這兩個位元組 0x0d 0x0a 會被換成 \n 換列字元, 否則會由檔案中讀取 size-1 個位元組。一個程式也可以運用 int fgetc(stream) 函數由檔案中一個字元一個字元讀出資料, 當在資料檔案中讀到 0x0d 0x0a 連續位元組時, 它會轉換為單一的一個 '\n' 換列字元。 一個程式也可以呼叫 fputc(c, stream) 或是 fputs(string, stream) 函數來輸出字元或是字串到檔案裡面, 此時一個換列字元 '\n' 還是會被轉換為0x0d 0x0a 兩個位元組寫到檔案中。

下面範例以文字模式拷貝任意一個檔案 (請注意檔案的內容一般來說是看得懂的 ASCII 文字資料, 因為純粹二進位數值資料裡萬一出現 0x0d 0x0a 的資料就會導致資料處理的錯誤)。你可以用文字模式開啟來源檔案和目標檔案, 一列一列讀取來源檔案, 並寫入目標檔案, 最後關閉兩個檔案

檔案開啟:

 FILE *infp, *outfp;
 char filename[100], buf[256];
 int lineno;

 printf("Please input the source text file: ");
 scanf("%s", filename);

 infp = fopen(filename, "rt");
 if (infp == 0)
 {
     printf("Error in openning the source file %s\n", filename);
     exit(1);
 }

 printf("Please input the destination file: ");
 scanf("%s", filename);
 outfp = fopen(filename, "wt");
 if (outfp == 0)
 {
     printf("Error in openning the destination file %s\n", filename);
     exit(1);
 }

一列一列的讀取檔案與寫入檔案:

 lineno = 0;
 while (fgets(buf, 256, infp) != NULL)
 {
     fputs(buf, outfp);
     printf("%d:%s", lineno++, buf);
 }

檔案關閉:

 fclose(infp);
 fclose(outfp);

2

二進位模式的檔案處理: 在這種檔案處理模式中, 檔案裡每一個的字元都運用一樣的方法來處理, 不像文字模式處理時 0x0d 0x0a 有特別的意義。在 C 語言中通常運用標準輸入輸出函數庫中的 fread()fwrite() 來以二進位模式讀寫檔案裡的資料, 同時你也可以運用 fseek() 函數快速移動檔案的讀寫頭到某一個指定的地方。

下面範例以二進位模式拷貝任意一個檔案 (請注意檔案的內容可以是看得懂的 ASCII 文字資料, 也可以是純粹二進位數值資料), 以二進位模式開啟來源檔案和目標檔案, 一個區塊一個區塊讀取來源檔案, 並寫入目標檔案, 最後關閉兩個檔案

檔案開啟:

FILE *infp, *outfp;
char filename[100], buf[256];
int len, count=0;

printf("Please input the source text file: ");
scanf("%s", filename);

infp = fopen(filename, "rb");
if (infp == 0)
{
    printf("Error in openning the source file %s\n", filename);
    exit(1);
}

printf("Please input the destination file: ");
scanf("%s", filename);
outfp = fopen(filename, "wb");
if (outfp == 0)
{
    printf("Error in openning the destination file %s\n", filename);
    exit(1);
}

以整個區塊為單位讀取資料與寫入資料:

while ((len = fread(buf, 1, 256, infp)) > 0)
{
    count += len;
    fwrite(buf, 1, len, outfp);
}
printf("%d bytes copied!\n", count);

檔案關閉:

fclose(infp);
fclose(outfp);

3

串接兩個檔案的內容:

 printf("Please input the destination file name: ");
 scanf("%s", filename);
 outfp = fopen(filename, "a");
 if (outfp == 0)
 {
     printf("Error in openning the destination file %s\n", filename);
     exit(1);
 }

一列一列的由檔案讀取資料, 附加 (append) 在另一個檔案的最後:

 lineno = 0;
 while (fgets(buf, 256, infp) != NULL)
 {
     fputs(buf, outfp);
     printf("%d:%s", lineno++, buf);
 }

檔案關閉:

 fclose(infp);
 fclose(outfp);

4

以文字模式在檔案中儲存整數資料:

一個 int 型態變數在記憶體中使用 4 個位元組:

int datavar = 123456789;

但是當你運用 fprintf() 函數把變數的資料內容以文字模式存放在檔案裡時, 例如:

fprintf(outfp, "%10d", datavar);

在檔案裡需要佔據 10 個位元組, 其中 outfp 是以文字模式開啟的輸出檔案指標。為什麼要使用比較多的位元組呢? 這表示說寫在檔案裡的資料應該有一些是不必要的, 如果寫1000,0000 個數字到檔案裡, 不就必須要多花 1.5 倍的硬碟空間嗎? 實際上在整數變數裡資料是二進位的 (二的補數的格式), 當以文字模式寫到檔案裡面去時, fprintf 用 %10d 的格式將二進位的資料換成用 ASCII 字元 '0' 到 '9' 表示的十進位字串存放到檔案裡面。所以上面這個 fprintf() 的函數呼叫的效果就跟呼叫 fprintf(outfp, " 123456789"); 完全一樣。

也就是說用文字模式儲存資料, 需要額外的運算時間以及額外的儲存空間才能將原本二進位的資料儲存為等效的 ASCII 文字資料。最大的好處是你可以用任意的編輯工具 (筆記本, WORD, ...) 來讀取檔案的內容

直接以二進位模式在檔案中儲存整數資料:

如果想要直接把變數 datavar 的內容, 在不做任何格式轉換而且使用一模一樣數量的位元組的情況下存放到檔案裡, 你需要以二進位模式開啟檔案, 並且使用 fwrite() 函數來儲存資料, 例如:

outfp = fopen(filename, "wb");
...
fwrite(&datavar, sizeof(int), 1, outfp); /* or sizeof(datavar) */

在上面這個函數呼叫的動作裡面, sizeof(int) 的數值為 4, 所以 fwrite() 會將 &datavar 這個位址處的連續 4 個位元組, 不做任何格式轉換直接寫到檔案 outfp 中。

如果想從檔案裡把剛才寫出去的資料讀回來, 你需要以二進位模式開啟檔案, 並且使用 fread() 函數來讀取資料, 例如:

infp = fopen(filename, "rb");
...
fread(&datavar, sizeof(int), 1, infp);

fread() 會從檔案 infp 中, 目前讀寫頭位置連續 4 個位元組, 不做任何格式轉換直接讀到記憶體&datavar 這個位址的連續 4 個位元組中 。

5

以文字模式在檔案中儲存浮點數資料:

一個 double 型態的變數在記憶體中使用 8 個位元組:

double datavar = 0.3;

但是當你運用 fprintf() 函數把變數的資料內容以文字模式存放在檔案裡時, 例如,

fprintf(outfp, "%25.17f", datavar);

其中 outfp 是一個以文字模式開啟的輸出檔案指標。在檔案中這筆資料佔據 25 個位元組。此外, 如果你以任何一個文字編輯程式去看輸出檔案的內容, 你有可能會看到下列資料

0.29999999999999999

實際上在浮點數變數裡資料是二進位的 (IEEE754的格式), 當以文字模式寫到檔案裡面去時, fprintf 用 %25.17f 的格式將二進位的資料換成用 ASCII 字元 '0' 到 '9'以及小數點 '.' 表示的十進位數字字串存放到檔案裡面。所以上面這個 fprintf() 的函數呼叫的效果就跟呼叫 fprintf(outfp, " 0.29999999999999999"); 完全一樣。

也就是說用文字模式儲存資料時, 需要額外的運算時間, 需要容忍一些轉換過程造成的誤差, 以及需要額外的儲存空間才能將原本二進位的資料儲存為 ASCII 文字資料。最大的好處是你可以用任意的編輯工具 (筆記本, WORD, ...) 來讀取檔案的內容此外如果你要從檔案裡將一個浮點數資料以文字模式讀入記憶體, 你需要再做一次資料格式的轉換, 由 ASCII '0' 到 '9' 以及 '.' 所表示的十進位小數轉換為二進位, 於是又可能有一次的轉換誤差出現

直接以二進位模式在檔案中儲存浮點數資料:

如果想要直接把變數 datavar 的內容, 在不做任何格式轉換而且使用一模一樣數量的位元組的情況下存放到檔案裡, 你需要以二進位模式開啟檔案, 並且使用 fwrite() 函數來儲存資料, 例如:

outfp = fopen(filename, "wb");
...
fwrite(&datavar, sizeof(double), 1, outfp); /* or sizeof(datavar) */

在上面這個函數呼叫的動作裡面, sizeof(double) 的數值為 8, 所以 fwrite() 會將 &datavar 這個位址處的連續 8 個位元組, 不做任何格式轉換直接寫到檔案 outfp 中。

如果想從檔案裡把剛才寫出去的資料讀回來, 你需要以二進位模式開啟檔案, 並且使用 fread() 函數來讀取資料, 例如:

infp = fopen(filename, "rb");
...
fread(&datavar, sizeof(double), 1, infp);

計算機程式設計實習 首頁

製作日期: 101/11/18 by 丁培毅 (Pei-yih Ting)
E-mail: pyting@ntu.edu.tw