無緩衝 vs 緩衝 I/O
想像從井邊把水搬回家。你可以每次想喝一口就拿杯子來回跑——很多趟,每一口都費好大力氣。或者你可以一次裝滿一個水桶,回到家再從桶裡倒杯子——較少趟,省力的是那個桶。無緩衝 I/O 是杯子;緩衝 I/O 是水桶。
無緩衝(原始)I/O 意指直接對檔案描述符呼叫系統呼叫 read() 與 write()。每次呼叫都從你的程式跨入核心——一次有實際成本的上下文切換——並且剛好搬移你要求的位元組。用這種方式一次一個位元組地寫檔案,就是每個位元組一個系統呼叫,慢得殘酷。緩衝 I/O 由 C 標準函式庫的 stdio 提供,它把描述符包進一個 FILE 物件(一個 FILE 指標,FILE *)並保有一塊記憶體中的緩衝區。你使用 fopen()、fread()、fwrite()、fgetc()、fputc()、fprintf()、fclose()。當你寫入時,位元組堆積在緩衝區裡;只有當它滿了(或你 flush 或 close)時,函式庫才對核心做一次大的 write()。當你讀取時,函式庫用一次 read() 抓一大塊,再從緩衝區把小片交給你。你的許多操作於是變成零個系統呼叫。
為何重要:這是日常檔案處理中最重要的效能區別。一般檔案工作通常該用緩衝 stdio,因為它把成千上萬個微小的系統呼叫變成寥寥幾個大的。但緩衝在你與核心之間多加了一層:你「寫」出去的資料可能還躺在使用者空間的緩衝區裡、還沒到磁碟,直到一次 flush——這會嚇到用 print 敘述除錯的人,也關乎持久性。當你需要精確掌控、精準時機,或正在跟裝置對話時,就改用原始的 read()/write()。
/* 無緩衝:每次呼叫一個系統呼叫 */ write(1, "a", 1); write(1, "b", 1); /* 緩衝:在緩衝區填滿或你 flush 之前,沒有東西抵達核心 */ FILE *f = fopen("out", "w"); fputc('a', f); fputc('b', f); fclose(f);
兩次 write() = 兩趟進核心;兩次 fputc() = 零趟,直到 fclose 把緩衝區清出。
不要不加小心地在同一個開啟檔案上混用原始 read()/write() 與緩衝 stdio——stdio 緩衝區與核心偏移量可能失去同步,給出令人困惑的結果。每個檔案就選一層。