欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Golang 实现超大文件读取的两种方法

程序员文章站 2022-06-18 11:54:56
golang超大文件读取的两个方案流处理方式分片处理去年的面试中我被问到超大文件你怎么处理,这个问题确实当时没多想,回来之后仔细研究和讨论了下这个问题,对大文件读取做了一个分析比如我们有一个log文件...

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)

看代码吧~

文件大小

Golang 实现超大文件读取的两种方法

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>

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