給文本文件按行為單位進(jìn)行排序和去除重復(fù)行,是文本處理任務(wù)中非常常見(jiàn)的,尤其是對(duì)超大文本文件的排序和去重,由于所需空間可能超出計(jì)算機(jī)內(nèi)存的大小,無(wú)法一次性加載進(jìn)內(nèi)存進(jìn)行簡(jiǎn)單的處理,因此是一個(gè)有挑戰(zhàn)性的難題:既要保證能夠處理,又要保證處理速度快。本文就是為此做準(zhǔn)備,首先實(shí)現(xiàn)預(yù)覽超大文本文件,統(tǒng)計(jì)總行數(shù)和平均每行的字符數(shù),并且為了便于增加直觀的了解,增加了預(yù)覽前100行的功能。
本例中,使用了第三方包github.com/topxeq/tk,該包中有不少實(shí)用的函數(shù),我們?cè)谝院笠矊?huì)經(jīng)常用到??梢杂孟率雒畎惭b該包。
go get -v github.com/topxeq/tk
本文例子代碼中已經(jīng)加了很多代碼內(nèi)注釋,注意Go語(yǔ)言從C語(yǔ)言發(fā)展而來(lái),一定程度上可以看成C語(yǔ)言的超級(jí)加強(qiáng)版,因此在注釋的語(yǔ)法上和C/C++語(yǔ)言是一致的,都支持“//”開(kāi)始的單行注釋,和“/*...*/”包起來(lái)的多行注釋。
本文中的代碼可以在github.com/topxeq/goexamples/preview1中找到,可以用
go get -v?github.com/topxeq/goexamples/preview1
命令獲取,也可以直接用
go get -v?github.com/topxeq/goexamples
命令獲取所有例子代碼后。獲取后,到GOPATH目錄下找到該子目錄(如果GOPATH是C:\goprjs,則最終代碼包目錄為C:\goprjs\src\github.com/topxeq/goexamples/preview1),在該目錄下運(yùn)行下述命令即可執(zhí)行,
go run preview1.go test1.txt
其中test1.txt是準(zhǔn)備預(yù)覽的超大文本文件。
代碼如下:
package?main
import?(
????"bufio"
????"io"
????"os"
????"strings"
????//?本例中使用了第三方包tk,可用go?get?-v?github.com/topxeq/tk命令安裝
????"github.com/topxeq/tk"
)
func?main()?{
????//?獲取第1個(gè)命令行參數(shù)(實(shí)際上是第二個(gè)命令行參數(shù),可執(zhí)行文件名是第1個(gè),序號(hào)為0)
????fileNameT?:=?tk.GetParameterByIndexWithDefaultValue(os.Args,?1,?"")
????//?如果命令行參數(shù)中沒(méi)有指定文件名則報(bào)錯(cuò)
????if?fileNameT?==?""?{
????????tk.Pl("文件名不能為空")
????????//?等待用戶輸入(可以輸入任意字符,回車鍵結(jié)束)
????????tk.GetUserInput("按回車鍵退出……")
????????return
????}
????//?打開(kāi)文件
????fileT,?errT?:=?os.Open(fileNameT)
????if?errT?!=?nil?{
????????tk.Pl("打開(kāi)文件%v時(shí)發(fā)生錯(cuò)誤:%v。",?fileNameT,?errT)
????????tk.Pl("請(qǐng)按回車鍵結(jié)束處理……")
????????tk.GetUserInput("按回車鍵退出……")
????????return
????}
????//?保證關(guān)閉文件(在函數(shù)退出時(shí)會(huì)執(zhí)行該條語(yǔ)句)
????defer?fileT.Close()
????//?創(chuàng)建讀取文件的緩沖式讀取器
????readerT?:=?bufio.NewReader(fileT)
????//?記錄總行數(shù)
????countT?:=?0
????//?記錄總字符數(shù)
????totalLenT?:=?0
????//?循環(huán)讀取文件中的每一行
????for?true?{
????????strT,?errT?:=?readerT.ReadString('\n')
????????//?如果出現(xiàn)錯(cuò)誤則中止循環(huán)
????????if?errT?!=?nil?{
????????????//?錯(cuò)誤有可能是到達(dá)文件末尾,此時(shí)正常終止循環(huán)
????????????if?errT?==?io.EOF?{
????????????????strT?=?strings.TrimRight(strT,?"\r\n")
????????????????if?countT?<?100?{
????????????????????tk.Pl("%v:?%v",?countT+1,?strT)
????????????????}
????????????????totalLenT?+=?len(strT)
????????????????countT++
????????????}
????????????break
????????}
????????//?去除行尾可能存在的\r和\n字符(Windows中的文本文件一般的行尾結(jié)束符是連續(xù)的\r\n兩個(gè)字符)
????????strT?=?strings.TrimRight(strT,?"\r\n")
????????//?100行以內(nèi)會(huì)輸出預(yù)覽,并輸出行號(hào)
????????if?countT?<?100?{
????????????tk.Pl("%v:?%v",?countT+1,?strT)
????????}
????????//?增加總字符數(shù)和總行數(shù)
????????totalLenT?+=?len(strT)
????????countT++
????}
????tk.Pl("共%v行,平均每行%v個(gè)字符。",?countT,?totalLenT/countT)
????tk.GetUserInput("按回車鍵退出……")
????return
}
除了代碼的內(nèi)部文檔注釋,再補(bǔ)充說(shuō)明如下:
->?tk.Pl函數(shù)類似于fmt.Printf函數(shù),但多輸出一個(gè)回車換行;
->?tk.GetUserInput函數(shù)用于獲取用戶輸入,本例中實(shí)際上等待一個(gè)回車后好再退出程序;這是為了直接雙擊該程序可執(zhí)行文件運(yùn)行的情況下,如果沒(méi)有該條語(yǔ)句,CMD窗口會(huì)在程序退出時(shí)直接一閃消失,導(dǎo)致看不清程序的輸出,因此增加的便捷功能;
-> defer關(guān)鍵字是Go語(yǔ)言中的特性之一,其特點(diǎn)是:保證defer后面的語(yǔ)句(同一行內(nèi),一般為一個(gè)函數(shù))可以在任何情況下都可以被執(zhí)行,無(wú)論是函數(shù)正常退出還是異常中止。如果定義了多條defer語(yǔ)句,則會(huì)在正?;虍惓M顺鏊诘暮瘮?shù)時(shí)按定義時(shí)的反向順序執(zhí)行,即先定義的后執(zhí)行;
-> 本例中使用了bufio包中定義的Reader類,可以保證用緩沖的方式讀取,寫文件時(shí)也可以用緩沖的方式,保護(hù)一下硬盤;
-> 用bufio.Reader.ReadString函數(shù)讀取到的字符串,會(huì)包含最后的行結(jié)束符“\r\n”(Linux系統(tǒng)下只有\(zhòng)n),因此需要用strings.TrimRight函數(shù)去除行尾的這兩個(gè)字符,注意該函數(shù)第二個(gè)參數(shù)是指要截取的所有字符,即每個(gè)字符都會(huì)被從行尾去除掉,而不是按順序的兩個(gè)字符去除;
這段代碼執(zhí)行的示例截圖如下:

注意,為了演示效果清晰,本例中的代碼僅輸出了前十行預(yù)覽??梢钥闯?,該超大文本文件包含4千多萬(wàn)行,平均每行約20個(gè)字符,因此總大小在1G左右。