Golang读取文件和处理超大文件
简介Golang读取文件和处理超大文件
在日常编码中,常常也会遇到如何读取文件,其实读取文件看是简单,但是如果文件是一个特别大的文件,那么如何办呢?本文主要讲解下如何读取文件?
- 整个文件读取
- 适用场景:文件较小
- 按照每行读取
- 适用场景:如果是大文件,文件内容有严格的分行,可以使用分行读取
- 按照块读取
- 使用场景:超大文件
文件准备
$ 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
总结:
- 从以上三种读取文件的方式可以看出,整个文件读取的效率是非常高的,但是这种方式只适用于小文件,大文件这样读取可能造成内存溢出
- 如果文件内容特别大,最好使用分片读取和逐行读取,但是逐行读取的文件要注意每行的内容不能太大,否则也会出现问题
- 二进制文件适合使用整个文件读取和分片读取
- 对文件内容需要处理最好选用分片读取和逐行读取