Re: [問題] 如何知道一個檔案有幾行

看板C_and_CPP作者 (卡卡獸)時間10年前 (2014/02/02 20:01), 編輯推噓2(2019)
留言21則, 5人參與, 最新討論串4/4 (看更多)
原文在這 #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

02/02 20:20, , 1F
個人認為可以的話應該還是不要用 fgets
02/02 20:20, 1F

02/02 20:23, , 2F
此外 gary8520 的 code 應該跟原本的 code 也不同~
02/02 20:23, 2F

02/02 20:48, , 3F
個人認為 fgets 是在 c-style 開發下的捷徑,要正確的話當
02/02 20:48, 3F

02/02 20:49, , 4F
然它不會是首選,ifstream 比它正確,但相對的速度慢,不好用
02/02 20:49, 4F

02/02 20:57, , 5F
喔. 沒有. 是我剛想錯了 :P
02/02 20:57, 5F

02/02 20:59, , 6F
我想表達的是如果有極小的機率造成錯誤就別用
02/02 20:59, 6F

02/02 21:01, , 7F
不是 fgets 本身的問題. 而是原文的用法
02/02 21:01, 7F

02/02 21:01, , 8F
了解, 謝謝 :D
02/02 21:01, 8F

02/02 21:12, , 9F
其實我那個code基本上是複製t大的,只是加了一些宣告之類
02/02 21:12, 9F

02/02 21:14, , 10F
忘了謝謝E大大
02/02 21:14, 10F

02/02 21:14, , 11F
嗯,所以可能你沒了解 bufsize 的含意吧,導致 fgets 有問題
02/02 21:14, 11F

02/02 21:18, , 12F
我自認為我應該知道fgets那個方法,只要bufsize夠大,能
02/02 21:18, 12F

02/02 21:18, , 13F
讀完一整行,其實fgets應該就可以跑出正確的結果。
02/02 21:18, 13F

02/02 21:21, , 14F
嗯,那是我誤會你了. 附一提, edisonx==tropical72 .
02/02 21:21, 14F

02/02 21:25, , 15F
@gary8520: 你的 code 我覺得一定有什麼神祕力量. 建議貼一下
02/02 21:25, 15F

02/02 22:14, , 16F
t大魂魄不散 (?
02/02 22:14, 16F

02/02 22:18, , 17F
嗯... 還剩一口氣 Orz
02/02 22:18, 17F

02/02 22:43, , 18F
如果只給Windows用,CreateFile/ReadFile是你的好朋友。
02/02 22:43, 18F

02/02 22:45, , 19F
先GetFileSizeEx()再看你要怎麼分段或一次讀完都可以。
02/02 22:45, 19F

02/02 22:51, , 20F
可以從32Kb buffer大小開始測讀取速度。
02/02 22:51, 20F

02/02 22:52, , 21F
讀大檔案memory-mapped file更快。
02/02 22:52, 21F
文章代碼(AID): #1IxZEXWQ (C_and_CPP)
文章代碼(AID): #1IxZEXWQ (C_and_CPP)