Golang读取文件和处理超大文件

golang 22-11-11 23:04 19  

在日常编码中,常常也会遇到如何读取文件,其实读取文件看是简单,但是如果文件是一个特别大的文件,那么如何办呢?本文主要讲解下如何读取文件? 整个文件读取 适用场景:文件较小 按照每行读取 适用场景:如果是大文件,文件内容有严格的分行,可以使用分行读取 按照块读取 使用场景:超大文件 文件准备 ``` $ ll -h total 462M -rw-r--r-- 1 zhj 197121 14M 6月 5 13:55 file_1.log -rw-r--r-- 1 zhj 197121 414M 6月 5 13:54 file_2.log ``` 整文件读取 示例1 常规读取文件操作 ``` package main import ( "fmt" "io/ioutil" "log" "os" "time" ) func main() { file1 := "./file_1.log" file2 := "./file_2.log" readFileBuff(file1) readFileBuff(file2) } //读取文件,常规流程 func readFileBuff(filename string) (content []byte) { startTime := time.Now() //打开文件 fileHandler, err := os.Open(filename) if err != nil { log.Println(err.Error()) return } //关闭文件 defer fileHandler.Close() //获取当前文件的信息 fileInfo, err := fileHandler.Stat() if err != nil { log.Println(err.Error()) return } //初始化切片的长度 content = make([]byte, fileInfo.Size()) //读取文件内容到content中 n, err := fileHandler.Read(content) if err != nil { log.Println(err.Error()) return } fmt.Println("读取的内容长度:", n) fmt.Println("运行时间:", time.Now().Sub(startTime)) return content } ``` 运行结果: ``` $ go run main.go 读取的内容长度: 13816352 运行时间: 8.9166ms 读取的内容长度: 433550208 运行时间: 258.3091ms ``` 上面读取文件是我们一个比较常规的操作,但是实际在golang中,已经有包帮我们处理了这个读取,io/ioutil包就是干了这件事,下面示例2就是采用该包读取 示例2 ``` package main import ( "fmt" "io/ioutil" "log" "time" ) func main() { file1 := "./file_1.mp4" //14M file2 := "./file_2.mp4" //414M readFile(file1) readFile(file2) } //读取文件 func readFile(filename string) (content []byte) { startTime := time.Now() content, err := ioutil.ReadFile(filename) if err != nil { log.Println(err.Error()) } fmt.Println("读取的内容长度:", len(content)) fmt.Println("运行时间:", time.Now().Sub(startTime)) return } ``` 运行结果: ``` $ go run main.go 读取的内容长度: 13816352 运行时间: 7.976ms 读取的内容长度: 433550208 运行时间: 291.2078ms ``` 是不是比示例1简单了很多,可以去看readFile的实现,内部的大致流程就是按照示例1去实现的。 分片读取 当一个文件是非常大,如20G的日志文件,我们按照上面的整个文件读取,其实是不现实的,可能内存都没有这么大,那么我们就要考虑分段读取。 分段读取的思路: 设置一个容量的切片 每次都读取固定长度的内容到切片中 直到文件内容读取完为止 ``` package main import ( "fmt" "io" "io/ioutil" "log" "os" "time" ) func main() { file1 := "./file_1.log" file2 := "./file_2.log" readBlock(file1) readBlock(file2) } //分片读取 func readBlock(filename string) (content []byte) { startTime := time.Now() //打开文件 fileHandler, err := os.Open(filename) if err != nil { log.Println(err.Error()) return } //关闭文件 defer fileHandler.Close() buffer := make([]byte, 1024) for { n, err := fileHandler.Read(buffer) if err != nil && err != io.EOF { log.Println(err.Error()) } //读取完成 if n == 0 { break } content = append(content, buffer[:n]...) } fmt.Println("读取的内容长度:", len(content)) fmt.Println("运行时间:", time.Now().Sub(startTime)) return } ``` 运行结果: ``` $ go run main.go 读取的内容长度: 13816352 运行时间: 83.8145ms 读取的内容长度: 433550208 运行时间: 2.5322629s ``` 逐行读取 逐行读取适合大文件,逐行读取要求文件里面的内容一定是分行存储的,并且每行的内容不能过大。 方式一: ``` package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "time" ) func main() { file1 := "./file_1.log" file2 := "./file_2.log" readLine(file1) readLine(file2) } func readLine(filename string) (content []byte) { startTime := time.Now() //打开文件 fileHandler, err := os.Open(filename) if err != nil { log.Println(err.Error()) return } //关闭文件 defer fileHandler.Close() lineReader := bufio.NewReader(fileHandler) for { line, _, err := lineReader.ReadLine() if err != nil && err == io.EOF { break } content = append(content, line...) } fmt.Println("读取的内容长度:", len(content)) fmt.Println("运行时间:", time.Now().Sub(startTime)) return } ``` 运行结果: ``` $ go run main.go 读取的内容长度: 13755104 运行时间: 57.8427ms 读取的内容长度: 431320344 运行时间: 1.7922053s ``` 方法二: ``` package main import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "time" ) func main() { file1 := "./file_1.log" file2 := "./file_2.log" readScanner(file1) readScanner(file2) } func readScanner(filename string) (content []byte) { startTime := time.Now() //打开文件 fileHandler, err := os.Open(filename) if err != nil { log.Println(err.Error()) return } //关闭文件 defer fileHandler.Close() lineScanner := bufio.NewScanner(fileHandler) for lineScanner.Scan() { content = append(content, lineScanner.Bytes()...) } fmt.Println("读取的内容长度:", len(content)) fmt.Println("运行时间:", time.Now().Sub(startTime)) return } ``` 执行结果: ``` $ go run main.go 读取的内容长度: 13755104 运行时间: 51.8617ms 读取的内容长度: 431320344 运行时间: 1.623657s ``` 总结: 从以上三种读取文件的方式可以看出,整个文件读取的效率是非常高的,但是这种方式只适用于小文件,大文件这样读取可能造成内存溢出 如果文件内容特别大,最好使用分片读取和逐行读取,但是逐行读取的文件要注意每行的内容不能太大,否则也会出现问题 二进制文件适合使用整个文件读取和分片读取 对文件内容需要处理最好选用分片读取和逐行读取