作者EdisonX (卡卡獸)
看板C_and_CPP
標題Re: [問題] 如何知道一個檔案有幾行
時間Sun Feb 2 20:01:32 2014
原文在這
#1DyUBD7Q (C_and_CPP)
http://www.ptt.cc/bbs/C_and_CPP/M.1307697869.A.1DA.html
都翻起舊文了,給個機會,澄清一下。
我先自首件事,這篇文當初我只有小提 fread + strchr 可以達成
較穩定的需求,會這麼說是有個專案 BUF_SIZE 設 32767 , 結果
fgets 檔案有一行真的 overflow。
我必須強調,當初講的 fgets / fread + strchr / fgetc 等方式,
其實都沒考慮到檔案編碼、多國語系問題,然後 fread + strchr 只
是小提一下,當時實作上是順手寫的,這次將它完善。
※ 引述《gary8520 (元丁)》之銘言:
: 小弟學C不久,非資工人,
: 正在寫一個小程式需要讀數十行,每行字元十個左右的資料。
: 想瞭解BUF_size大約要怎麼取,
: 小小測試了一下,
: 我使用動態記憶體配製決定BUF_SIZE的大小(用for迴圈跑),
: 並算出line_cut。
: ※ 引述《tropical72 (藍影)》之銘言:
<............恕刪............>
: : ----------
: : step 4: 用 fread 進行
: 計算結果輸出為line_cnt4
: 這似乎就要看BUF_SIZE的大小…
<............恕刪............>
: 只要bufsize一改,這個方法算出來的結果就會不同?
: 說實在話我不知道為什麼,也想不出來為什麼"Orz
: 所以,若要像我讀小筆資料,step3的方法似乎是比較適當的。
^^^^^^^^^^^^ 就是 fgets
一般而言,不論檔案大小,大多做法都是用 fgets 去做,因很少有機會會遇到
存文字檔,一行很長的(正確的說,很少情況會遇到純文字檔會寫很大的,寫大
的話到後來都是用 binary mode 寫入),所以 buf_size 設大一點就沒事了,
一般我是直接給
BUFSIZ * 4 ,我手邊 compiler BUFSIZ 是給 512 。
---------------------
原 source code
有問題的 重點如下
while(BUF_SIZE==fread(buf, 1, BUF_SIZE, fp)){
ptr = (char*)strchr(buf, '\n');
/* 這裡還有個 issue 要修正 */
while(ptr!=NULL){
++line_cnt;
ptr = (char*)strchr(ptr+1, '\n');
}
}
關鍵其實在於
fread 傳回值 ,代表成功從檔案讀取了幾個 bytes ,
原本是只考慮成功讀取了 BUF_SIZE bytes 時才繼續往下做 , 想一下
如果檔案有 351 bytes, 每次讀 100 bytes , 最後會有 51 bytes 會
被丟掉,所以判斷式不該那麼下
[Lemma],要簡單的話是只要 fread
傳回值是非 0 就直接往下做。
然後考慮一下最後一次 fread 的情況,假設 BUF_SIZE = 100 , 但只讀
了 51 bytes , 這時候
buf 後面的 49 bytes 都不會被清 0 , 意思是說
如果 buf 後 49 bytes 裡面有 '\n' 的話就會被重覆計算, 所以在做
string search 之前要再塞個結束字元。
整個可以 run 的 code 如下。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
enum {LINE_CNT = 150, BUF_SIZE = 20};
const char * FILENAME = "tst.txt";
int main()
{
FILE * fp ;
char * ptr;
char buf[BUF_SIZE+1] ; // +1 : 加上結束字元
size_t read_bytes , line_cnt = 0;
fp = fopen(FILENAME, "rb"); // no error defect
while(read_bytes = fread(buf, 1, BUF_SIZE, fp)) { // read_bytes==0 時結束
buf[read_bytes] = '\0';
ptr = (char*)strchr(buf, '\n');
while(ptr!=NULL) {
++line_cnt;
ptr = (char*)strchr(ptr+1, '\n');
}
}
fclose(fp);
printf("line_cnt = %d\n", line_cnt);
return 0;
}
然後整個 fread + strchr , 其實可用 fread + memchr 做 , memchr 速度應會比
strchr 還快一點點 , 這裡就不再示範。
[Lemma]
當初之所以會用
while(BUF_SIZE==fread(buf, 1, BUF_SIZE, fp)) ,
是因為不想在 while loop 裡面做很多事,想單純化,最後沒讀滿 BUF_SIZE
的是跳出 loop 之後再獨立做,速度估會較快。
--
~ 這輩子與神手無緣
我只好當神獸了 ~
卡卡獸
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 180.177.74.188
→ Feis:個人認為可以的話應該還是不要用 fgets 02/02 20:20
→ Feis:此外 gary8520 的 code 應該跟原本的 code 也不同~ 02/02 20:23
→ EdisonX:個人認為 fgets 是在 c-style 開發下的捷徑,要正確的話當 02/02 20:48
→ EdisonX:然它不會是首選,ifstream 比它正確,但相對的速度慢,不好用 02/02 20:49
→ Feis:喔. 沒有. 是我剛想錯了 :P 02/02 20:57
→ Feis:我想表達的是如果有極小的機率造成錯誤就別用 02/02 20:59
→ Feis:不是 fgets 本身的問題. 而是原文的用法 02/02 21:01
→ EdisonX:了解, 謝謝 :D 02/02 21:01
推 gary8520:其實我那個code基本上是複製t大的,只是加了一些宣告之類 02/02 21:12
→ gary8520:忘了謝謝E大大 02/02 21:14
→ EdisonX:嗯,所以可能你沒了解 bufsize 的含意吧,導致 fgets 有問題 02/02 21:14
推 gary8520:我自認為我應該知道fgets那個方法,只要bufsize夠大,能 02/02 21:18
→ gary8520:讀完一整行,其實fgets應該就可以跑出正確的結果。 02/02 21:18
→ EdisonX:嗯,那是我誤會你了. 附一提, edisonx==tropical72 . 02/02 21:21
→ Feis:@gary8520: 你的 code 我覺得一定有什麼神祕力量. 建議貼一下 02/02 21:25
→ purincess:t大魂魄不散 (? 02/02 22:14
→ EdisonX:嗯... 還剩一口氣 Orz 02/02 22:18
→ freaky:如果只給Windows用,CreateFile/ReadFile是你的好朋友。 02/02 22:43
→ freaky:先GetFileSizeEx()再看你要怎麼分段或一次讀完都可以。 02/02 22:45
→ freaky:可以從32Kb buffer大小開始測讀取速度。 02/02 22:51
→ freaky:讀大檔案memory-mapped file更快。 02/02 22:52