您的当前位置:首页正文

Golang如何读取单行超长的文本详解

2021-05-14 来源:好走旅游网
Golang如何读取单⾏超长的⽂本详解

⽬录

前⾔:1.问题复现2.问题探究3.问题解决4.扩展总结前⾔:

最近在探索⽤Go来读取⽂件,读取⽂本时发现,对于单⾏超长的⽂本,我的Go代码⽆法处理。经过查阅才发现,Go提供的Scanner⽆法读取单⾏超长⽂本⽂件。我这⾥就来总结⼀下问题的发现和解决过程。

1.问题复现

⾸先注释main函数⾥⾯的内容,执⾏ CreateBigText 函数,它会创建⼀个含有3⾏内容的⽂件,第⼀⾏是⼀个长度超过100KB的⾏。然后解决main函数的注释,尝试执⾏代码,会发现只有⼀⾏错误信息:

package mainimport ( \"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) // 构造⼀个⼤的单⾏数据 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(\"创建⽂件成功\")}

2.问题探究

让我们来探究⼀下这个问题的原因,⾸先看⼀下Scan()⽅法的注释,这个⽅法就是每次扫描到下⼀个token,然后就可以通过获取字节或者⽂本的⽅法来获取扫描过的token。如果它返回值是false,就会返回扫描期间遇到的错误,除了io.EOF.

Scan advances the Scanner to the next token, which will then be available through the Bytes or Text method. Itreturns false when the scan stops, either by reaching the end of the input or an error. After Scan returns false, theErr method will return any error that occurred during scanning, except that if it was io.EOF, Err will return nil. Scanpanics if the split function returns too many empty tokens without advancing the input. This is a common errormode for scanners.

所以Scan()和Text()函数是这样结合起来使⽤的,⾸先Scan()会扫描出⼀个token,然后Text()将其转成⽂本(或者其它⽅法转成字节),循环执⾏这种操作就可以按⾏读取⼀个⽂件。

通过阅读Scan()函数的源码,我们可以发现这样⼀个判断,如果buf的长度⼤于了最⼤token长度,那就会报错,见下图。

继续查找,可以看到最⼤长度已经定义好了,它的长度是 64*1024 byte,即64KB,所以⼀⾏⽂本超过了这个最⼤长度,那么就会报错!

3.问题解决

其实⼤部分情况下我们都应该使⽤Scan()函数结合Text()或者Bytes()函数来读取⽂件的,这个也是官⽅推荐的,因为它们是high-level ⽅法,⽤起来很⽅便。但是如果我们有⼀些极端的情况,例如单⾏超过64KB,那么怎么办呢?(这种情况是很少的,但是⼜有可能会遇到这种需求的,例如⽂件⾥⾯存储了⼀串Base64编码)

这⾥可以这样来使⽤,这个⽅法不会受到64KB的限制,ReaderString⽅法会按照指定的定界符来读取⼀个完整的⾏,返回值是字符串和读取遇到的错误。如果想要读取返回值为字节的话,可以使⽤ 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\ }}

通过阅读源码可知,其实这个⽅法也是会遇到⾏太长的问题,只不过它忽略了这种情况。ErrBufferFull就是这个缓冲区溢出错误。

我们继续进⼊内容其实也可以知道,它默认的缓冲区⼤⼩是4KB。

4.扩展

上⾯都说相对⾼层的⽅法,我们来看⼀下相对底层的⽅法。

ReadLine is a low-level line-reading primitive. Most callers should use ReadBytes('\\n') or ReadString('\\n') insteador use a Scanner.

ReadLine是读取⼀⾏,但是它是⼀个 low-level ⽅法,它会返回三个值:[]byte、isPrefix bool和err error。

其中最令⼈好奇的是第⼆个参数,它如果是true,则表⽰当前⾏没有读取完毕,但是缓冲区满了,可以看下⾯这段注释。

If the line was too long for the buffer then isPrefix is set and the beginning of the line is returned. The rest of theline 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 // 读取到⽂件结束才退出 }

// 读取到超长⾏,即单⾏超过4k字节,直接写⼊⽂件,不对此⾏做处理 if isPrefix {

fmt.Print(string(bline)) continue }

fmt.Println(string(bline)) }}

不过需要注意这个⽅法读取出来的数据是不包括换⾏符的,所以我是⽤的println打印输出的。

如果你也去看了 ReadString、ReadBytes 和 ReadLine ⽅法,会发现两种都依赖于⼀个底层的⽅法——ReadSlice⽅法。这个⽅法很原始,⼀般不会直接使⽤它。如果它遇到了超长⾏,它就会直接返回读取到的字节和⼀个ErrBufferFull,那这样我们就可以根据这个错误来继续读取数据了。这种⽅式还是相对⿇烦了⼀些,不过如果你可以理解的话,对于上⾯的⽅法也就不是问题了。学习嘛,还是有必要⼀探究竟的。不过阅读源码感觉有些还是理解起来很困难,特别是这些英语注释,不过也能看⼀个七七⼋⼋了。还不⾏的话,那就再借助⼀些翻译软件,不过我个⼈觉得提⾼⾃⼰的英语能⼒还是⾮常必要的。

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)) }}

总结

到此这篇关于Golang如何读取单⾏超长的⽂本的⽂章就介绍到这了,更多相关Golang读取超长⽂本内容请搜索以前的⽂章或继续浏览下⾯的相关⽂章希望⼤家以后多多⽀持!

因篇幅问题不能全部显示,请点此查看更多更全内容