Golang如何讀取單行超長的文本詳解
:
最近在探索用Go來讀取文件,讀取文本時(shí)發(fā)現(xiàn),對(duì)于單行超長的文本,我的Go代碼無法處理。經(jīng)過查閱才發(fā)現(xiàn),Go提供的Scanner無法讀取單行超長文本文件。我這里就來總結(jié)一下問題的發(fā)現(xiàn)和解決過程。
1.問題復(fù)現(xiàn)
首先注釋main函數(shù)里面的內(nèi)容,執(zhí)行 CreateBigText
函數(shù),它會(huì)創(chuàng)建一個(gè)含有3行內(nèi)容的文件,第一行是一個(gè)長度超過100KB的行。然后解決main函數(shù)的注釋,嘗試執(zhí)行代碼,會(huì)發(fā)現(xiàn)只有一行錯(cuò)誤信息:
package main import ( "bufio" "bytes" "log" "os" "strconv" ) func main() { file, err := os.Open("./read/test.txt") if err != nil { log.Fatal(err) } ReadBigText(file) } func ReadBigText(file *os.File) { defer file.Close() scanner := bufio.NewScanner(file) for scanner.Scan() { println(scanner.Text()) } // 輸出錯(cuò)誤 println(scanner.Err().Error()) } func CreateBigText() { file, err := os.Create("./read/test.txt") if err != nil { log.Fatal(err) } defer file.Close() data := make([]byte, 0, 32*1024) buffer := bytes.NewBuffer(data) // 構(gòu)造一個(gè)大的單行數(shù)據(jù) for i := 0; i < 50000; i++ { buffer.WriteString(strconv.Itoa(i)) } // 寫入一個(gè)換行符 buffer.WriteByte('\n') buffer.WriteString("I love you yesterday and today!\n") buffer.WriteString("有一美人兮,見之不忘。\n") // 將3行寫入文件 file.Write(buffer.Bytes()) log.Println("創(chuàng)建文件成功") }
2.問題探究
讓我們來探究一下這個(gè)問題的原因,首先看一下Scan()
方法的注釋,這個(gè)方法就是每次掃描到下一個(gè)token,然后就可以通過獲取字節(jié)或者文本的方法來獲取掃描過的token。如果它返回值是false,就會(huì)返回掃描期間遇到的錯(cuò)誤,除了io.EOF.
Scan advances the Scanner to the next token, which will then be available through the Bytes or Text method. It returns false when the scan stops, either by reaching the end of the input or an error. After Scan returns false, the Err method will return any error that occurred during scanning, except that if it was io.EOF, Err will return nil. Scan panics if the split function returns too many empty tokens without advancing the input. This is a common error mode for scanners.
所以Scan()和Text()函數(shù)是這樣結(jié)合起來使用的,首先Scan()會(huì)掃描出一個(gè)token,然后Text()將其轉(zhuǎn)成文本(或者其它方法轉(zhuǎn)成字節(jié)),循環(huán)執(zhí)行這種操作就可以按行讀取一個(gè)文件。
通過閱讀Scan()函數(shù)的源碼,我們可以發(fā)現(xiàn)這樣一個(gè)判斷,如果buf的長度大于了最大token長度,那就會(huì)報(bào)錯(cuò),見下圖。
繼續(xù)查找,可以看到最大長度已經(jīng)定義好了,它的長度是 64*1024 byte,即64KB,所以一行文本超過了這個(gè)最大長度,那么就會(huì)報(bào)錯(cuò)!
3.問題解決
其實(shí)大部分情況下我們都應(yīng)該使用Scan()函數(shù)結(jié)合Text()或者Bytes()函數(shù)來讀取文件的,這個(gè)也是官方推薦的,因?yàn)樗鼈兪?high-level
方法,用起來很方便。但是如果我們有一些極端的情況,例如單行超過64KB,那么怎么辦呢?(這種情況是很少的,但是又有可能會(huì)遇到這種需求的,例如文件里面存儲(chǔ)了一串Base64編碼)
這里可以這樣來使用,這個(gè)方法不會(huì)受到64KB的限制,ReaderString方法會(huì)按照指定的定界符來讀取一個(gè)完整的行,返回值是字符串和讀取遇到的錯(cuò)誤。如果想要讀取返回值為字節(jié)的話,可以使用 ReadBytes 方法。
func ReadBigText(file *os.File) { defer file.Close() reader := bufio.NewReader(file) for { line, err := reader.ReadString('\n') if err != nil { log.Fatal(err) } fmt.Printf("%d %s", len(line), line) } }
通過閱讀源碼可知,其實(shí)這個(gè)方法也是會(huì)遇到行太長的問題,只不過它忽略了這種情況。
ErrBufferFull就是這個(gè)緩沖區(qū)溢出錯(cuò)誤。
我們繼續(xù)進(jìn)入內(nèi)容其實(shí)也可以知道,它默認(rèn)的緩沖區(qū)大小是4KB。
4.擴(kuò)展
上面都說相對(duì)高層的方法,我們來看一下相對(duì)底層的方法。
ReadLine is a low-level line-reading primitive. Most callers should use ReadBytes('\n') or ReadString('\n') instead or use a Scanner.
ReadLine是讀取一行,但是它是一個(gè) low-level
方法,它會(huì)返回三個(gè)值:[]byte、isPrefix bool和err error。
其中最令人好奇的是第二個(gè)參數(shù),它如果是true,則表示當(dāng)前行沒有讀取完畢,但是緩沖區(qū)滿了,可以看下面這段注釋。
If the line was too long for the buffer then isPrefix is set and the beginning of the line is returned. The rest of the line will be returned from future calls.
func ReadBigText(file *os.File) { defer file.Close() reader := bufio.NewReader(file) for { bline, isPrefix, err := reader.ReadLine() if err == io.EOF { break // 讀取到文件結(jié)束才退出 } // 讀取到超長行,即單行超過4k字節(jié),直接寫入文件,不對(duì)此行做處理 if isPrefix { fmt.Print(string(bline)) continue } fmt.Println(string(bline)) } }
不過需要注意這個(gè)方法讀取出來的數(shù)據(jù)是不包括換行符的,所以我是用的println打印輸出的。
如果你也去看了 ReadString
、ReadBytes
和 ReadLine
方法,會(huì)發(fā)現(xiàn)兩種都依賴于一個(gè)底層的方法——ReadSlice方法。這個(gè)方法很原始,一般不會(huì)直接使用它。如果它遇到了超長行,它就會(huì)直接返回讀取到的字節(jié)和一個(gè)ErrBufferFull,那這樣我們就可以根據(jù)這個(gè)錯(cuò)誤來繼續(xù)讀取數(shù)據(jù)了。這種方式還是相對(duì)麻煩了一些,不過如果你可以理解的話,對(duì)于上面的方法也就不是問題了。學(xué)習(xí)嘛,還是有必要一探究竟的。不過閱讀源碼感覺有些還是理解起來很困難,特別是這些英語注釋,不過也能看一個(gè)七七八八了。還不行的話,那就再借助一些翻譯軟件,不過我個(gè)人覺得提高自己的英語能力還是非常必要的。
func ReadBigText(file *os.File) { defer file.Close() reader := bufio.NewReader(file) for { byt, err := reader.ReadSlice('\n') if err != nil { if err == bufio.ErrBufferFull { fmt.Print(string(byt)) continue } log.Fatal(err) } fmt.Print(string(byt)) } }
總結(jié)
到此這篇關(guān)于Golang如何讀取單行超長的文本的文章就介紹到這了,更多相關(guān)Golang讀取超長文本內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習(xí)參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。