golang中bufio.SplitFunc的深入理解
前言
bufio模块是golang标准库中的模块之一,主要是实现了一个读写的缓存,用于对数据的读取或者写入操作。该模块在多个涉及io的标准库中被使用,比如http模块中使用buffio来完成网络数据的读写,压缩文件的zip模块利用bufio来操作文件数据的读写等。
golang的bufio包里面定以的SplitFunc是一个比较重要也比较难以理解的东西,本文希望通过结合简单的实例介绍SplitFunc的工作原理以及如何实现一个自己的SplitFunc。
一个例子
在bufio包里面定义了一些常用的工具比如Scanner,你可能需要读取用户在标准输入里面输入的一些东西,比如我们做一个复读机,读取用户的每一行输入,然后打印出来:
packagemain import( "bufio" "fmt" "os" ) funcmain(){ scanner:=bufio.NewScanner(os.Stdin) scanner.Split(bufio.ScanLines) forscanner.Scan(){ fmt.Println(scanner.Text()) } }
这个程序很简单,os.Stdin实现了io.Reader接口,我们从这个reader创建了一个scanner,设置分割函数为bufio.ScanLines,然后for循环,每次读到一行数据就将文本内容打印出来。麻雀虽小五脏俱全,这个小程序虽然简单,却引出了我们今天要介绍的对象:bufio.SplitFunc,它的定义是这个样子的:
package"buffio" typeSplitFuncfunc(data[]byte,atEOFbool)(advanceint,token[]byte,errerror)
golang官方文档的描述是这个样子的:
SplitFuncisthesignatureofthesplitfunctionusedtotokenizetheinput.Theargumentsareaninitialsubstringoftheremainingunprocesseddataandaflag,atEOF,thatreportswhethertheReaderhasnomoredatatogive.Thereturnvaluesarethenumberofbytestoadvancetheinputandthenexttokentoreturntotheuser,ifany,plusanerror,ifany.
Scanningstopsifthefunctionreturnsanerror,inwhichcasesomeoftheinputmaybediscarded.
Otherwise,theScanneradvancestheinput.Ifthetokenisnotnil,theScannerreturnsittotheuser.Ifthetokenisnil,theScannerreadsmoredataandcontinuesscanning;ifthereisnomoredata--ifatEOFwastrue--theScannerreturns.Ifthedatadoesnotyetholdacompletetoken,forinstanceifithasnonewlinewhilescanninglines,aSplitFunccanreturn(0,nil,nil)tosignaltheScannertoreadmoredataintothesliceandtryagainwithalongerslicestartingatthesamepointintheinput.
ThefunctionisnevercalledwithanemptydatasliceunlessatEOFistrue.IfatEOFistrue,however,datamaybenon-emptyand,asalways,holdsunprocessedtext.
英文!参数这么多!返回值这么多!好烦!不知道各位读者遇到这种文档会不会有这种感觉...正式由于这种情况,我才决定写一篇文章介绍一下SplitFunc的具体工作原理,用一种通俗的方式结合具体实例加以说明,希望对读者有所帮助。
好了,废话少说,开始正题吧!
Scanner和SplitFunc的工作机制
package"buffio" typeSplitFuncfunc(data[]byte,atEOFbool)(advanceint,token[]byte,errerror)
Scanner是有缓存的,意思是Scanner底层维护了一个Slice用来保存已经从Reader中读取的数据,Scanner会调用我们设置SplitFunc,将缓冲区内容(data)和是否已经输入完了(atEOF)以参数的形式传递给SplitFunc,而SplitFunc的职责就是根据上述的两个参数返回下一次Scan需要前进几个字节(advance),分割出来的数据(token),以及错误(err)。
这是一个通信双向的过程,Scanner告诉我们的SplitFunc已经扫描到的数据和是否到结尾了,我们的SplitFunc则根据这些信息将分割的结果返回和下次扫描需要前进的位置返回给Scanner。用一个例子来说明:
packagemain import( "bufio" "fmt" "strings" ) funcmain(){ input:="abcdefghijkl" scanner:=bufio.NewScanner(strings.NewReader(input)) split:=func(data[]byte,atEOFbool)(advanceint,token[]byte,errerror){ fmt.Printf("%t\t%d\t%s\n",atEOF,len(data),data) return0,nil,nil } scanner.Split(split) buf:=make([]byte,2) scanner.Buffer(buf,bufio.MaxScanTokenSize) forscanner.Scan(){ fmt.Printf("%s\n",scanner.Text()) } }
输出
false2ab
false4abcd
false8abcdefgh
false12abcdefghijkl
true12abcdefghijkl
这里我们把缓冲区的初始大小设置为了2,不够的时候会扩展为原来的2倍,最大为bufio.MaxScanTokenSize,这样一开始扫描2个字节,我们的缓冲区就满了,reader的内容还没有读取到EOF,然后split函数执行,输出:
false2ab
紧接着函数返回0,nil,nil这个返回值告诉Scanner数据不够,下次读取的位置前进0位,需要继续从reader里面读取,此时因为缓冲区满了,所以容量扩展为2*2=4,reader的内容还没有读取到EOF,输出
false4abcd
重复上述步骤,一直到最后全部内容读取完了,EOF此时变成了true
true12abcdefghijkl
看了上面的过程是不是对SplitFunc的工作原来有了一点理解了呢?再回头看一下golang的官方文档有没有觉得稍微理解了一点?下面是bufio.ScanLines的实现,读者可以自己研究一下该函数是如何工作的
标准库里的ScanLines
funcScanLines(data[]byte,atEOFbool)(advanceint,token[]byte,errerror){ //表示我们已经扫描到结尾了 ifatEOF&&len(data)==0{ return0,nil,nil } //找到\n的位置 ifi:=bytes.IndexByte(data,'\n');i>=0{ //把下次开始读取的位置向前移动i+1位 returni+1,dropCR(data[0:i]),nil } //这里处理的reader内容全部读取完了,但是内容不为空,所以需要把剩余的数据返回 ifatEOF{ returnlen(data),dropCR(data),nil } //表示现在不能分割,向Reader请求更多的数据 return0,nil,nil }
参考
In-depthintroductiontobufio.ScannerinGolang
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对毛票票的支持。