星期五, 1月 16, 2009

有關File I/O的兩三事(2) - Binary FIle

有趣問題: 電腦的資訊是由0和1組成,當塞了一堆0和1時,電腦如何得知要執行什麼動作?

這個問題從我學電腦的第一年到現在為止都是一個很有趣的問題XD 我在大學才給了一個自己比較滿意的解答,我所認為的答案是,Instruction Set Architecture(ISA) 會解釋比較基本的答案,也就是說,只要塞入一道道的instructions,電腦就會依序執行,當然,這中間還有很多事要做,我們就暫時略過。

有趣問題: 電腦如何判斷且讀取一個檔案?

這個問題其實更有趣,最常見的方法是,判斷副檔名。當然,如果自己把副檔名改掉,而電腦就會用另外一個程式來開啟時,當然我們不確保能打的開XD 那麼怎麼讀取? 通常每個File都會有一個header,而每個header有其固定的format,例如說我這學期處理過的wav file,讀取了前面這麼多bytes之後,我們才能判斷如何讀取接下來的資料(於是就有許許多多的file format document需要閱讀XD)。

那麼我們回到(1)的有趣問題,我們要怎麼寫入一個binary tree? 假設我們把每個node data structure如下(以下以C++為例)。

typedef struct _node{
unsigned int node_number;
unsigned int data;
unsigned int left_number, right_number;
struct _node* left; /* 理論上不用寫入檔案XD */
struct _node* right; /* 理論上不用寫入檔案XD */
}Node;

那麼當我們在C++中寫入Node array時,可能是這樣子寫的

    std::size_t list_size = 20;
Node* list = new Node[list_size];
/* ... */

std::ofstream outfile;
outfile.open("test.out", std::ofstream::out | std::ofstream::binary);
outfile.write(static_cast<char*>(static_cast<void*>(list)), sizeof(Node)*list_size);
當然,寫進去的檔案在linux下嘗試用more觀看時,應該會看到一堆亂碼XD

可是這樣子寫入我們會有一個問題,root node number為何,而總共又有幾個node,方法也很簡單,我們也一併寫入file,於是我們現在的File Format就變成

0~34~7...
total node sizeroot node numbernode data

所以我們的寫入檔案的方法就變成...

    unsigned int root_number;
std::size_t node_size;
std::size_t list_size = 20;
Node* list = new Node[list_size];

std::ofstream outfile;
outfile.open("test.out", std::ofstream::out | std::ofstream::binary);
outfile.write(static_cast<char*>(static_cast<void*>(&root_number)), sizeof(unsigned int));
outfile.write(static_cast<char*>(static_cast<void*>(&node_size)), sizeof(std::size_t));
outfile.write(static_cast<char*>(static_cast<void*>(list)), sizeof(Node)*list_size);
於是我們就把檔案寫完啦,那麼讀檔的時候,我就不另外寫啦(用std::ifstream XD)。


那麼這樣子的設計缺失在那? 第一個是,我們一定要寫入root node number嗎? 其實可以不用,如果在設計list時,我們強制把list[0]設為root,我們就不用另外寫入檔案(因此省了4 byte),第二個是,total nodes的數目只能是2^32-1個,其實非常大,足以應付一般日常生活所需,但在數學模型上,還是不能支援無限多個是有點可惜的事。

那麼這樣子寫入檔案有什麼好處? 答案是,非常的快以及容易撰寫(不論是讀取或寫入),而且,其實每個node size是固定的(24 bytes),也就是說,可以配合seekp, seekg任意跳及讀取,大部分的狀況,如果資料不夠大會一次讀進來,如果資料很大的時候,我們會利用buffered I/O來讀取,或者是跳到檔案某處只讀取我們所需要的資料(有時候,你只會需要檔案的某個部分。),至於讀進來之後要做什麼事,這就不是我要關心的了XD。

---
下回繼續分解XD

張貼留言