隨機存取檔案


使用 get 指標或 put 指標,可以自由的移動至檔案中指定的位置進行讀取或寫入的動作,通常隨機存取檔案會使用二進位模式進行,文字模式開啟的檔案並不適合作隨機存取的動作。

如何利用隨機存取來讀寫所有的資料,必須視您的需求而定,需求決定您的資料結構,這邊以一個最簡單的例子來示範隨機存取,寫入檔案時都是使用固定大小的struct,由於資料大小固定,這可以方便明確的指定檔案中讀取的位置。

假設有一個簡單的學生成績資料如下:
  • Student.h
#ifndef DATASTRU_H 
#define DATASTRU_H

struct Student {
int studyNumber;
char name[80];
double score;
};

#endif

一個結構的大小是固定的,當要寫入一個結構時,可以使用這樣的語法:
fout.write(reinterpret_cast<const char*> (&student),
            sizeof(Student));


其中student是Student自訂struct所宣告的變數名稱,由於write接受const char*型態的變數,所以使用reinterpret_cast<const char*> 將之轉換為const char*指標。

下面這個程式示範,如何建立一個檔案,當中包括50筆空的資料:
  • main.cpp
#include <iostream> 
#include <fstream>
#include "Student.h"
using namespace std;

int main(int argc, char* argv[]) {
if(argc != 2) {
cout << "指令: create <filename>" << endl;
return 1;
}

ofstream fout(argv[1], ios::binary);

if(!fout) {
cerr << "檔案輸出失敗" << endl;
return 1;
}

int count;
cout << "要建立幾筆資料? ";
cin >> count;

Student student = {0, "", 0.0};

for(int i = 0; i < count; i++) {
fout.write(reinterpret_cast<const char*> (&student),
sizeof(Student));
}

fout.close();

return 0;
}

執行結果:
create data.bin
要建立幾筆資料? 50

接下來可以使用下面這個程式進行隨機存取,使用學號來作資料的位置指定,將之儲存在檔案中的指定位置:
#include <iostream> 
#include <fstream>
#include "Student.h"
using namespace std;

int main(int argc, char* argv[]) {
Student student;
int count = 0;

if(argc < 2) {
cerr << "指令: write <filename>";
return 1;
}

fstream fio(argv[1], ios::in | ios::out | ios::binary);
if(!fio) {
cerr << "無法讀取檔案" << endl;
return 1;
}

while(true) {
fio.read(reinterpret_cast<char *> (&student),
sizeof(Student));
if(!fio.eof())
count++;
else
break;
}

fio.clear();

cout << "輸入學號(1-" << count << ")" << endl
<< "輸入0離開";

while(true) {
cout << "\n學號? ";
cin >> student.studyNumber;
if(student.studyNumber == 0)
break;

cout << "輸入姓名, 分數" << endl
<< "? ";
cin >> student.name >> student.score;

fio.seekp((student.studyNumber - 1) * sizeof(Student));
fio.write(reinterpret_cast<const char*> (&student),
sizeof(Student));
}

fio.close();

return 0;
}

執行結果:
write data.bin
輸入學號(1-50)
輸入0離開
學號? 1
輸入姓名, 分數
? 良葛格 88

學號? 2
輸入姓名, 分數
? 毛妹妹 94

學號? 5
輸入姓名, 分數
? 毛毛蟲 75

學號? 0

接下來可以使用下面這個程式讀取方才所輸入的資料:
#include <iostream> 
#include <fstream>
#include "Student.h"
using namespace std;

int main(int argc, char* argv[]) {
Student student;
int count = 0, number;

if(argc != 2) {
cout << "指令: read <filename>" << endl;
return 1;
}

ifstream fin(argv[1], ios::in | ios::binary);
if(!fin) {
cerr << "無法讀取檔案" << endl;
return 1;
}

while(true) {
fin.read(reinterpret_cast<char *> (&student),
sizeof(Student));
if(!fin.eof())
count++;
else
break;
}
fin.clear();

cout << "輸入學號(1-" << count << ")" << endl
<< "輸入0離開";

while(true) {
cout << "\n學號? ";
cin >> number;
if(number == 0)
break;
else if(number > count) {
cout << "輸入學號(1-" << count << ")" << endl;
continue;
}

cout << "\n學號\t姓名\t\t分數" << endl;

fin.seekg((number - 1) * sizeof(Student));
fin.read(reinterpret_cast<char*> (&student),
sizeof(Student));
cout << student.studyNumber << "\t"
<< student.name << "\t\t"
<< student.score << endl;
}

fin.close();

return 0;
}

執行結果:
read data.bin
輸入學號(1-50)
輸入0離開
學號? 1

學號    姓名            分數
1       良葛格          88

學號? 2

學號    姓名            分數
2       毛妹妹          94

學號? 3

學號    姓名            分數
0                       0

學號? 5

學號    姓名            分數
5       毛毛蟲          75

學號? 0

這幾個程式是隨機存取的簡單示範,您也可以結合起來,製作一個簡易的成績登記程式。

在判斷資料筆數時還有更簡單的方法,就是開啟檔案後先使用ios::end將指標移至檔案尾,然後使用tellg()得到目前的檔案指標位置,再除以資料結構的大小除可得知資料筆數,例如:
file.seekg(0, ios::end);
count = file.tellg() / sizeof(資料結構);