前言
最近在探索用Go來讀取文件,讀取文本時發(fā)現(xiàn),對于單行超長的文本,我的Go代碼無法處理。經(jīng)過查閱才發(fā)現(xiàn),Go提供的Scanner無法讀取單行超長文本文件。我這里就來總結(jié)一下問題的發(fā)現(xiàn)和解決過程。
1.問題復(fù)現(xiàn)
首先注釋main函數(shù)里面的內(nèi)容,執(zhí)行 CreateBigText 函數(shù),它會創(chuàng)建一個含有3行內(nèi)容的文件,第一行是一個長度超過100KB的行。然后解決main函數(shù)的注釋,嘗試執(zhí)行代碼,會發(fā)現(xiàn)只有一行錯誤信息:
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()) } // 輸出錯誤 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)造一個大的單行數(shù)據(jù) for i := 0; i < 50000; i++ { buffer.WriteString(strconv.Itoa(i)) } // 寫入一個換行符 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.問題探究
讓我們來探究一下這個問題的原因,首先看一下Scan()方法的注釋,這個方法就是每次掃描到下一個token,然后就可以通過獲取字節(jié)或者文本的方法來獲取掃描過的token。如果它返回值是false,就會返回掃描期間遇到的錯誤,除了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()會掃描出一個token,然后Text()將其轉(zhuǎn)成文本(或者其它方法轉(zhuǎn)成字節(jié)),循環(huán)執(zhí)行這種操作就可以按行讀取一個文件。
通過閱讀Scan()函數(shù)的源碼,我們可以發(fā)現(xiàn)這樣一個判斷,如果buf的長度大于了最大token長度,那就會報錯,見下圖。
繼續(xù)查找,可以看到最大長度已經(jīng)定義好了,它的長度是 64*1024 byte,即64KB,所以一行文本超過了這個最大長度,那么就會報錯!
3.問題解決
其實大部分情況下我們都應(yīng)該使用Scan()函數(shù)結(jié)合Text()或者Bytes()函數(shù)來讀取文件的,這個也是官方推薦的,因為它們是 high-level 方法,用起來很方便。但是如果我們有一些極端的情況,例如單行超過64KB,那么怎么辦呢?(這種情況是很少的,但是又有可能會遇到這種需求的,例如文件里面存儲了一串Base64編碼)
這里可以這樣來使用,這個方法不會受到64KB的限制,ReaderString方法會按照指定的定界符來讀取一個完整的行,返回值是字符串和讀取遇到的錯誤。如果想要讀取返回值為字節(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) } }
通過閱讀源碼可知,其實這個方法也是會遇到行太長的問題,只不過它忽略了這種情況。
ErrBufferFull就是這個緩沖區(qū)溢出錯誤。
我們繼續(xù)進入內(nèi)容其實也可以知道,它默認(rèn)的緩沖區(qū)大小是4KB。
4.擴展
上面都說相對高層的方法,我們來看一下相對底層的方法。
ReadLine is a low-level line-reading primitive. Most callers should use ReadBytes('\n') or ReadString('\n') instead or use a Scanner.
ReadLine是讀取一行,但是它是一個 low-level 方法,它會返回三個值:[]byte、isPrefix bool和err error。
其中最令人好奇的是第二個參數(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é),直接寫入文件,不對此行做處理 if isPrefix { fmt.Print(string(bline)) continue } fmt.Println(string(bline)) } }
不過需要注意這個方法讀取出來的數(shù)據(jù)是不包括換行符的,所以我是用的println打印輸出的。
如果你也去看了 ReadString、ReadBytes 和 ReadLine 方法,會發(fā)現(xiàn)兩種都依賴于一個底層的方法——ReadSlice方法。這個方法很原始,一般不會直接使用它。如果它遇到了超長行,它就會直接返回讀取到的字節(jié)和一個ErrBufferFull,那這樣我們就可以根據(jù)這個錯誤來繼續(xù)讀取數(shù)據(jù)了。這種方式還是相對麻煩了一些,不過如果你可以理解的話,對于上面的方法也就不是問題了。學(xué)習(xí)嘛,還是有必要一探究竟的。不過閱讀源碼感覺有些還是理解起來很困難,特別是這些英語注釋,不過也能看一個七七八八了。還不行的話,那就再借助一些翻譯軟件,不過我個人覺得提高自己的英語能力還是非常必要的。
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)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://blog.csdn.net/qq_40734247/article/details/122049728