Golang 实现超大文件读取的两种方法
- 作者: 我叫小小猫
- 来源: 51数据库
- 2021-06-25
golang超大文件读取的两个方案
流处理方式
分片处理
去年的面试中我被问到超大文件你怎么处理,这个问题确实当时没多想,回来之后仔细研究和讨论了下这个问题,对大文件读取做了一个分析
比如我们有一个log文件,运行了几年,有100g之大。按照我们之前的操作可能代码会这样写:
func readfile(filepath string) []byte{
content, err := ioutil.readfile(filepath)
if err != nil {
log.println("read error")
}
return content
}
上面的代码读取几兆的文件可以,但是如果大于你本身及其内存,那就直接翻车了。因为上面的代码,是把文件所有的内容全部都读取到内存之后返回,几兆的文件,你内存够大可以处理,但是一旦上几百兆的文件,就没那么好处理了。
那么,正确的方法有两种
第一个是使用流处理方式代码如下
func readfile(filepath string, handle func(string)) error {
f, err := os.open(filepath)
defer f.close()
if err != nil {
return err
}
buf := bufio.newreader(f)
for {
line, err := buf.readline("\n")
line = strings.trimspace(line)
handle(line)
if err != nil {
if err == io.eof{
return nil
}
return err
}
return nil
}
}
第二个方案就是分片处理
当读取的是二进制文件,没有换行符的时候,使用下面的方案一样处理大文件
func readbigfile(filename string, handle func([]byte)) error {
f, err := os.open(filename)
if err != nil {
fmt.println("can't opened this file")
return err
}
defer f.close()
s := make([]byte, 4096)
for {
switch nr, err := f.read(s[:]); true {
case nr < 0:
fmt.fprintf(os.stderr, "cat: error reading: %s\n
补充:golang 读取大文件处理sync.pool + bufio.newreader(f)
看代码吧~
文件大小

package main
import (
"bufio"
"fmt"
"io"
//"math"
"os"
"strings"
"sync"
"time"
)
func main() {
/*
文件数据样例
{"remark": "来电时间: 2021/04/15 13:52:07客户电话:13913xx39xx ", "no": "600020510132021101310210547639", "title": "b-ae0e-0242ac100907", "call_in_date": "2021-04-15 13:52:12", "name": "张三", "_date": "2021-06-15", "name": "张三", "meet": "1"}
1、我们取出 call_in_date": "2021-04-15 13:52:1的数据写入另一个文件
*/
var (
s time.time //当前时间
file *os.file
filestat os.fileinfo
err error
lastlinesize int64
)
s = time.now()
if file, err = os.open("/users/zhangsan/downloads/log.txt");err != nil{
fmt.println(err)
}
defer func() {
err = file.close() //close after checking err
}()
//querystarttime, err := time.parse("2006-01-02t15:04:05.0000z", starttimearg)
//if err != nil {
// fmt.println("could not able to parse the start time", starttimearg)
// return
//}
//
//queryfinishtime, err := time.parse("2006-01-02t15:04:05.0000z", finishtimearg)
//if err != nil {
// fmt.println("could not able to parse the finish time", finishtimearg)
// return
//}
/**
* {name:"log.log", size:911100961, mode:0x1a4,
modtime:time.time{wall:0x656c25c, ext:63742660691,
loc:(*time.location)(0x1192c80)}, sys:syscall.stat_t{dev:16777220,
mode:0x81a4, nlink:0x1, ino:0x118cba7, uid:0x1f5, gid:0x14, rdev:0,
pad_cgo_0:[4]uint8{0x0, 0x0, 0x0, 0x0}, atimespec:syscall.timespec{sec:1607063899, nsec:977970393},
mtimespec:syscall.timespec{sec:1607063891, nsec:106349148}, ctimespec:syscall.timespec{sec:1607063891,
nsec:258847043}, birthtimespec:syscall.timespec{sec:1607063883, nsec:425808150},
size:911100961, blocks:1784104, blksize:4096, flags:0x0, gen:0x0, lspare:0, qspare:[2]int64{0, 0}}
*
*/
if filestat, err = file.stat();err != nil {
return
}
filesize := filestat.size()//72849354767
offset := filesize - 1
//检测是不是都是空行 只有\n
for {
var (
b []byte
n int
char string
)
b = make([]byte, 1)
//从指定位置读取
if n, err = file.readat(b, offset);err != nil {
fmt.println("error reading file ", err)
break
}
char = string(b[0])
if char == "\n" {
break
}
offset--
//获取一行的大小
lastlinesize += int64(n)
}
var (
lastline []byte
logslice []string
logslice1 []string
)
//初始化一行大小的空间
lastline = make([]byte, lastlinesize)
_, err = file.readat(lastline, offset)
if err != nil {
fmt.println("could not able to read last line with offset", offset, "and lastline size", lastlinesize)
return
}
//根据条件进行区分
logslice = strings.split(strings.trim(string(lastline),"\n"),"next_pay_date")
logslice1 = strings.split(logslice[1],""")
if logslice1[2] == "2021-06-15"{
process(file)
}
fmt.println("\ntime taken - ", time.since(s))
fmt.println(err)
}
func process(f *os.file) error {
//读取数据的key,减小gc压力
linespool := sync.pool{new: func() interface{} {
lines := make([]byte, 250*1024)
return lines
}}
//读取回来的数据池
stringpool := sync.pool{new: func() interface{} {
lines := ""
return lines
}}
//一个文件对象本身是实现了io.reader的 使用bufio.newreader去初始化一个reader对象,存在buffer中的,读取一次就会被清空
r := bufio.newreader(f) //
//设置读取缓冲池大小 默认16
r = bufio.newreadersize(r,250 *1024)
var wg sync.waitgroup
for {
buf := linespool.get().([]byte)
//读取reader对象中的内容到[]byte类型的buf中
n, err := r.read(buf)
buf = buf[:n]
if n == 0 {
if err != nil {
fmt.println(err)
break
}
if err == io.eof {
break
}
return err
}
//补齐剩下没满足的剩余
nextuntillnewline, err := r.readbytes('\n')
//fmt.println(string(nextuntillnewline))
if err != io.eof {
buf = append(buf, nextuntillnewline...)
}
wg.add(1)
go func() {
processchunk(buf, &linespool, &stringpool)
wg.done()
}()
}
wg.wait()
return nil
}
func processchunk(chunk []byte, linespool *sync.pool,stringpool *sync.pool) {
//做相应的处理
}
执行
go run test2.go "2020-01-01t00:00:00.0000z" "2020-02-02t00:00:00.0000z" /users/zhangsan/go/src/workspace/test/log.log
eof time taken - 20.023517675s <nil>
以上为个人经验,希望能给大家一个参考,也希望大家多多支持。如有错误或未考虑完全的地方,望不吝赐教。
推荐阅读
