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


Posted in Golang onApril 27, 2021

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>

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

Golang 相关文章推荐
golang中切片copy复制和等号复制的区别介绍
Apr 27 Golang
golang中的空slice案例
Apr 27 Golang
golang gopm get -g -v 无法获取第三方库的解决方案
May 05 Golang
Go 语言结构实例分析
Jul 04 Golang
基于Go语言构建RESTful API服务
Jul 25 Golang
Go语言并发编程 sync.Once
Oct 16 Golang
golang中的struct操作
Nov 11 Golang
Go语言特点及基本数据类型使用详解
Mar 21 Golang
Go归并排序算法的实现方法
Apr 06 Golang
Golang 遍历二叉树
Apr 19 Golang
Golang jwt身份认证
Apr 20 Golang
golang中的空slice案例
Apr 27 #Golang
Go语言切片前或中间插入项与内置copy()函数详解
golang中切片copy复制和等号复制的区别介绍
Apr 27 #Golang
go语言中切片与内存复制 memcpy 的实现操作
Apr 27 #Golang
Go语言中的UTF-8实现
Apr 26 #Golang
golang中实现给gif、png、jpeg图片添加文字水印
Apr 26 #Golang
Go语言带缓冲的通道实现
Apr 26 #Golang
You might like
解决MySQL中文输出变成问号的问题
2008/06/05 PHP
让PHP以ROOT权限执行系统命令的方法
2011/02/10 PHP
PHP判断图片格式的七种方法小结
2013/06/03 PHP
thinkphp5使html5实现动态跳转的例子
2019/10/16 PHP
php设计模式之正面模式实例分析【星际争霸游戏案例】
2020/03/24 PHP
利用jquery的获取JS文件中的字符串内容
2012/02/14 Javascript
document.write的几点使用心得
2014/05/14 Javascript
js中跨域方法原理详解
2015/07/19 Javascript
JS传递对象数组为参数给后端,后端获取的实例代码
2016/06/28 Javascript
利用fecha进行JS日期处理
2016/11/21 Javascript
layui选项卡效果实现代码
2017/05/19 Javascript
vue axios登录请求拦截器
2018/04/02 Javascript
小程序实现发表评论功能
2018/07/06 Javascript
vue中轮训器的使用
2019/01/27 Javascript
jQuery设置下拉框显示与隐藏效果的方法分析
2019/09/15 jQuery
对Layer弹窗使用及返回数据接收的实例详解
2019/09/26 Javascript
JS定时器如何实现提交成功提示功能
2020/06/12 Javascript
[01:55]2014DOTA2国际邀请赛 BBC正赛第一天总结
2014/07/10 DOTA
[49:21]完美世界DOTA2联赛循环赛 Ink Ice vs LBZS BO2第二场 11.05
2020/11/06 DOTA
跟老齐学Python之集合(set)
2014/09/24 Python
python数据结构之列表和元组的详解
2017/09/23 Python
numpy实现合并多维矩阵、list的扩展方法
2018/05/08 Python
想学python 这5本书籍你必看!
2018/12/11 Python
详解Python中的内建函数,可迭代对象,迭代器
2019/04/29 Python
python打造爬虫代理池过程解析
2019/08/15 Python
Python 通过截图匹配原图中的位置(opencv)实例
2019/08/27 Python
Python脚本操作Excel实现批量替换功能
2019/11/20 Python
Python使用PyQt5/PySide2编写一个极简的音乐播放器功能
2020/02/07 Python
试解释COMMIT操作和ROLLBACK操作的语义
2014/07/25 面试题
.NET程序员的几道面试题
2012/06/01 面试题
小学生自我评价范例
2013/09/24 职场文书
护士长2014年终工作总结
2014/11/11 职场文书
2014年保安个人工作总结
2014/11/13 职场文书
财产分割协议书
2016/03/22 职场文书
如何用Navicat操作MySQL
2021/05/12 MySQL
MySQL中正则表达式(REGEXP)使用详解
2022/07/07 MySQL