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>

以上为个人经验,希望能给大家一个参考,也希望大家多多支持www.887551.com。如有错误或未考虑完全的地方,望不吝赐教。