天天看點

Golang讀取檔案和處理超大檔案方案

文章目錄

        • 1. 檔案整體讀取
            • 代碼示例1
            • 代碼示例2
        • 2. 檔案分片讀取
            • 代碼示例
        • 3. 檔案逐行讀取
            • 示例代碼1
            • 代碼示例2
        • 4. 總結
        • 5. 參考資料
Golang 操作檔案的讀取的方法很多,适用的場景也是各不相同,在此我們将檔案的讀取分為如下幾種 :
  1. 檔案整體讀取
  2. 檔案分片讀取(塊級讀取)
  3. 檔案行級讀取

系統的配置不同,執行的耗時也不相同,此處給出一參考

系統配置 :

OS : Windows10

Memory : 16G

CPU (英特爾)Intel® Core™ i3-4370 CPU @ 3.80GHz(3800 MHz)

1. 檔案整體讀取

檔案整體讀取就是将檔案一次性讀取到,了解上是将檔案的内容第一次就讀取完了

使用場景 :

  1. 針對小檔案比較合适(大檔案讀取空間和時間的消耗也很大)
  2. 對于整體性強的檔案也比較合适(檔案也不能太大)

代碼示例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 : 每次讀取檔案的大小是根據設定的

分片

大小 ,是以對于讀取文本類型的檔案時(例如 : 日志檔案)

不一定是按照你的期望逐行輸出,因為不會處理文本尾部的換行符,而是按照分片大小讀取内容

使用場景 :

  1. 讀取超大的檔案很合适
  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 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. 檔案逐行讀取

對檔案一行一行的讀取,直到讀到檔案末尾

使用場景 :

  1. 讀取超大的檔案很合适(例如 : 超大log檔案等)
  2. 讀取的檔案最好是有換行的(如果使用單行檔案組成的大檔案,需要注意)
  3. 對需要分析内容的大檔案
    1. 統計某些資料出現的次數
    2. 查詢某些資料是否存在
    3. 查找指定行的資料

示例代碼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. 總結

  1. 面試中常見的類似超大檔案讀取的問題,通常我們采用分片讀取或者逐行讀取的方案即可
    1. 大檔案的上傳也可以采用類似的解決方案 , 每次讀取檔案的部分内容上傳(寫入)網絡接口中,直至檔案讀取完畢
  2. 普通的小檔案并且對内容沒有太多操作的,可以采用整體讀取,速度相對較快
  3. 對檔案内容有操作的采用分片讀取和逐行讀取更合适
  4. 二進制類型檔案采用分片讀取或者整體讀取的方案比較合适
  5. 檔案讀取不僅是本地檔案,要讀去網絡上的檔案(各種文檔,音視訊,圖檔,和其他各種類型檔案)時要通路到檔案擷取

    io.ReadCloser

    或者

    io.Reader

    後可以采用三種方式将檔案内容讀取到
    1. func ReadAll(r io.Reader) ([]byte, error)

      檔案完整讀取
    2. func Copy(dst Writer, src Reader) (written int64, err error)

      檔案讀取并寫入
    3. type Reader interface {
          Read(p []byte) (n int, err error)
      }
                 
      通過

      Reader

      接口的

      Read

      方法讀取
有疏漏 , 萬望指正 , 不勝感謝

5. 參考資料

- [1] Golang文檔

繼續閱讀