1. 程式人生 > 程式設計 >golang逐行讀取檔案的操作

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 方法讀取

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援我們。如有錯誤或未考慮完全的地方,望不吝賜教。