golang逐行讀取檔案的操作
我就廢話不多說了,大家還是直接看程式碼吧~
func ReadLine(fileName string) ([]string,error){ f,err := os.Open(fileName) if err != nil { return nil,err } buf := bufio.NewReader(f) var result []string for { line,err := buf.ReadString('\n') line = strings.TrimSpace(line) if err != nil { if err == io.EOF { //讀取結束,會報EOF return result,nil } return nil,err } result = append(result,line) } return result,nil }
補充:Golang讀取檔案和處理超大檔案方案
Golang 操作檔案的讀取的方法很多,適用的場景也是各不相同,在此我們將檔案的讀取分為如下幾種 :
檔案整體讀取
檔案分片讀取(塊級讀取)
檔案行級讀取
系統的配置不同,執行的耗時也不相同,此處給出一參考
系統配置 :
OS : Windows10
Memory : 16G
CPU (英特爾)Intel® Core™ i3-4370 CPU @ 3.80GHz(3800 MHz)
1. 檔案整體讀取
檔案整體讀取就是將檔案一次性讀取到,理解上是將檔案的內容第一次就讀取完了
使用場景 :
針對小檔案比較合適(大檔案讀取空間和時間的消耗也很大)
對於整體性強的檔案也比較合適(檔案也不能太大)
程式碼示例1
package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "time" ) // 測試用的文字檔案11M大小 var m11 string = `G:\runtime\log\ccapi\11M.log` // 測試用的文字檔案400M大小 var m400 string = `G:\runtime\log\ccapi\400M.log` // 將整個檔案都讀取 func readAll(filePath string) { start1 := time.Now() ioutil.ReadFile(filePath) fmt.Println("readAll spend : ",time.Now().Sub(start1)) } func main() { readAll(m11) readAll(m400) }
$ go run main.go readAll spend : 6.9999ms readAll spend : 358.8014ms
程式碼示例2
package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "time" ) // 測試用的文字檔案11M大小 var m11 string = `G:\runtime\log\ccapi\11M.log` // 測試用的文字檔案400M大小 var m400 string = `G:\runtime\log\ccapi\400M.log` // 將檔案完整讀取 func readAllBuff(filePath string) { start1 := time.Now() // 開啟檔案 FileHandle,err := os.Open(filePath) if err != nil { log.Println(err) return } // 關閉檔案 defer FileHandle.Close() // 獲取檔案當前資訊 fileInfo,err := FileHandle.Stat() if err != nil { log.Println(err) return } buffer := make([]byte,fileInfo.Size()) // 讀取檔案內容,並寫入buffer中 n,err := FileHandle.Read(buffer) if err != nil { log.Println(err) } // 列印所有切片中的內容 fmt.Println(string(buffer[:n])) fmt.Println("readAllBuff spend : ",time.Now().Sub(start1)) } func main() { readAllBuff(m11) readAllBuff(m400) }
2. 檔案分片讀取
對檔案一部分一部分逐步的讀取,直到檔案完全讀取完
PS : 每次讀取檔案的大小是根據設定的 分片 大小,所以對於讀取文字型別的檔案時(例如 : 日誌檔案)
不一定是按照你的期望逐行輸出,因為不會處理文字尾部的換行符,而是按照分片大小讀取內容
使用場景 :
讀取超大的檔案很合適
讀二進位制型別的檔案很合適(比如:音視訊檔案或者資源型別檔案等)
程式碼示例
package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "time" ) // 測試用的文字檔案11M大小 var m11 string = `G:\runtime\log\ccapi\11M.log` // 測試用的文字檔案400M大小 var m400 string = `G:\runtime\log\ccapi\400M.log` // 檔案一塊一塊的讀取 func readBlock(filePath string) { start1 := time.Now() FileHandle,err := os.Open(filePath) if err != nil { log.Println(err) return } defer FileHandle.Close() // 設定每次讀取位元組數 buffer := make([]byte,1024) for { n,err := FileHandle.Read(buffer) // 控制條件,根據實際調整 if err != nil && err != io.EOF { log.Println(err) } if n == 0 { break } // 如下程式碼打印出每次讀取的檔案塊(位元組數) //fmt.Println(string(buffer[:n])) } fmt.Println("readBolck spend : ",time.Now().Sub(start1)) } func main() { readBlock(m11) readBlock(m400) }
$ go run main.go readBolck spend : 31.9814ms readBolck spend : 1.0889488s
3. 檔案逐行讀取
對檔案一行一行的讀取,直到讀到檔案末尾
使用場景 :
讀取超大的檔案很合適(例如 : 超大log檔案等)
讀取的檔案最好是有換行的(如果使用單行檔案組成的大檔案,需要注意)
對需要分析內容的大檔案
統計某些資料出現的次數
查詢某些資料是否存在
查詢指定行的資料
示例程式碼1
package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "time" ) // 測試用的文字檔案11M大小 var m11 string = `G:\runtime\log\ccapi\11M.log` // 測試用的文字檔案400M大小 var m400 string = `G:\runtime\log\ccapi\400M.log` // 讀取檔案的每一行 func readEachLineReader(filePath string) { start1 := time.Now() FileHandle,err := os.Open(filePath) if err != nil { log.Println(err) return } defer FileHandle.Close() lineReader := bufio.NewReader(FileHandle) for { // 相同使用場景下可以採用的方法 // func (b *Reader) ReadLine() (line []byte,isPrefix bool,err error) // func (b *Reader) ReadBytes(delim byte) (line []byte,err error) // func (b *Reader) ReadString(delim byte) (line string,err error) line,_,err := lineReader.ReadLine() if err == io.EOF { break } // 如下是某些業務邏輯操作 // 如下程式碼列印每次讀取的檔案行內容 fmt.Println(string(line)) } fmt.Println("readEachLineReader spend : ",time.Now().Sub(start1)) } func main(){ readEachLineReader(m11) readEachLineReader(m400) }
$ go run main.go readEachLineReader spend : 16.9902ms readEachLineReader spend : 537.9683ms
程式碼示例2
package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "time" ) // 測試用的文字檔案11M大小 var m11 string = `G:\runtime\log\ccapi\11M.log` // 測試用的文字檔案400M大小 var m400 string = `G:\runtime\log\ccapi\400M.log` // 讀取檔案的每一行 func readEachLineScanner(filePath string) { start1 := time.Now() FileHandle,err := os.Open(filePath) if err != nil { log.Println(err) return } defer FileHandle.Close() lineScanner := bufio.NewScanner(FileHandle) for lineScanner.Scan() { // 相同使用場景下可以使用如下方法 // func (s *Scanner) Bytes() []byte // func (s *Scanner) Text() string // 實際邏輯 : 對讀取的內容進行某些業務操作 // 如下程式碼列印每次讀取的檔案行內容 fmt.Println(lineScanner.Text()) } fmt.Println("readEachLineScanner spend : ",time.Now().Sub(start1)) } func main() { readEachLineScanner(m11) readEachLineScanner(m400) }
$ go run main.go readEachLineScanner spend : 17.9895ms readEachLineScanner spend : 574.1722ms
4. 總結
面試中常見的類似超大檔案讀取的問題,通常我們採用分片讀取或者逐行讀取的方案即可
大檔案的上傳也可以採用類似的解決方案,每次讀取檔案的部分內容上傳(寫入)網路介面中,直至檔案讀取完畢
普通的小檔案並且對內容沒有太多操作的,可以採用整體讀取,速度相對較快
對檔案內容有操作的採用分片讀取和逐行讀取更合適
二進位制型別檔案採用分片讀取或者整體讀取的方案比較合適
檔案讀取不僅是本地檔案,要讀去網路上的檔案(各種文件,音視訊,圖片,和其他各種型別檔案)時要訪問到檔案獲取 io.ReadCloser 或者 io.Reader 後可以採用三種方式將檔案內容讀取到
func ReadAll(r io.Reader) ([]byte,error) 檔案完整讀取
func Copy(dst Writer,src Reader) (written int64,err error) 檔案讀取並寫入
type Reader interface { Read(p []byte) (n int,err error) }
通過Reader 介面的 Read 方法讀取
以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。如有錯誤或未考慮完全的地方,望不吝賜教。