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通过递归遍历生成树状结构的操作
Apr 28 Golang
对Golang中的FORM相关字段理解
May 02 Golang
golang gopm get -g -v 无法获取第三方库的解决方案
May 05 Golang
golang switch语句的灵活写法介绍
May 06 Golang
GoLang中生成UUID唯一标识的实现
May 08 Golang
Golang二维数组的使用方式
May 28 Golang
Golang生成Excel文档的方法步骤
Jun 09 Golang
如何利用golang运用mysql数据库
Mar 13 Golang
Golang jwt身份认证
Apr 20 Golang
GoFrame基于性能测试得知grpool使用场景
Jun 21 Golang
Go语言编译原理之源码调试
Aug 05 Golang
Go中使用gjson来操作JSON数据的实现
Aug 14 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
php图片处理:加水印、缩略图的实现(自定义函数:watermark、thumbnail)
2010/12/02 PHP
php微信公众号开发之欢迎老朋友
2018/10/20 PHP
Smarty模板变量与调节器实例详解
2019/07/20 PHP
网页设计常用的一些技巧
2006/12/22 Javascript
JavaScript 面向对象的 私有成员和公开成员
2010/05/13 Javascript
JavaScript prototype属性使用说明
2010/05/13 Javascript
jQuery的实现原理的模拟代码 -4 重要的扩展函数 extend
2010/08/03 Javascript
Jquery工作常用实例 使用AJAX使网页进行异步更新
2011/07/26 Javascript
js关闭父窗口时关闭子窗口
2013/04/01 Javascript
基于javascript实现页面加载loading效果
2020/09/15 Javascript
浅析Bootstrip的select控件绑定数据的问题
2016/05/10 Javascript
Angular2-primeNG文件上传模块FileUpload使用详解
2017/01/14 Javascript
js遍历json对象所有key及根据动态key获取值的方法(必看)
2017/03/09 Javascript
基于js原生和ajax的get和post方法以及jsonp的原生写法实例
2017/10/16 Javascript
脚手架vue-cli工程webpack的基本用法详解
2018/09/29 Javascript
10行代码实现微信小程序滑动tab切换
2018/12/28 Javascript
JavaScript实现页面高亮操作提示和蒙板
2021/01/04 Javascript
[01:20]DOTA2 2017国际邀请赛冠军之路无止竞
2017/06/19 DOTA
[01:06:18]DOTA2-DPC中国联赛 正赛 Phoenix vs Dynasty BO3 第二场 1月26日
2021/03/11 DOTA
Python模块学习 filecmp 文件比较
2012/08/27 Python
python算法学习之基数排序实例
2013/12/18 Python
python 请求服务器的实现代码(http请求和https请求)
2018/05/25 Python
Python中list查询及所需时间计算操作示例
2018/06/21 Python
用Q-learning算法实现自动走迷宫机器人的方法示例
2019/06/03 Python
Python匿名函数/排序函数/过滤函数/映射函数/递归/二分法
2019/06/05 Python
Python Numpy库datetime类型的处理详解
2019/07/13 Python
python 已知平行四边形三个点,求第四个点的案例
2020/04/12 Python
基于python获取本地时间并转换时间戳和日期格式
2020/10/27 Python
浅谈CSS3中的变形功能-transform功能
2017/12/27 HTML / CSS
南京迈特望C/C++面试题
2012/07/09 面试题
求职信的要素有哪些呢
2013/12/26 职场文书
学校课外活动总结
2014/05/08 职场文书
2014年保密工作总结
2014/11/22 职场文书
小学教师个人总结
2015/02/05 职场文书
《鸟的天堂》教学反思
2016/02/19 职场文书
正则表达式基础与常用验证表达式
2022/06/16 Javascript