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 DNS服务器的简单实现操作
Apr 30 Golang
go类型转换及与C的类型转换方式
May 05 Golang
go mod 安装依赖 unkown revision问题的解决方案
May 06 Golang
go开发alertmanger实现钉钉报警
Jul 16 Golang
手把手教你导入Go语言第三方库
Aug 04 Golang
Go 语言中 20 个占位符的整理
Oct 16 Golang
简单聊聊Golang中defer预计算参数
Mar 25 Golang
深入理解 Golang 的字符串
May 04 Golang
Golang并发工具Singleflight
May 06 Golang
Go语言入门exec的基本使用
May 20 Golang
GO中sync包自由控制并发示例详解
Aug 05 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连mysql和oracle数据库性能比较
2006/10/09 PHP
PHP采集相关教程之一 CURL函数库
2010/02/15 PHP
PHP入门之常量简介和系统常量
2014/05/12 PHP
php操作xml入门之xml标签的属性分析
2015/01/23 PHP
PHP 返回13位时间戳的实现代码
2016/05/13 PHP
PHP有序表查找之二分查找(折半查找)算法示例
2018/02/09 PHP
如何通过PHP实现Des加密算法代码实例
2020/05/09 PHP
Uglifyjs(JS代码优化工具)入门 安装使用
2020/04/13 Javascript
javascript结合CSS实现苹果开关按钮特效
2015/04/07 Javascript
jQuery实现判断滚动条到底部
2015/06/23 Javascript
jQuery Ajax和getJSON获取后台普通json数据和层级json数据用法分析
2016/06/08 Javascript
JS本地刷新返回上一页代码
2016/07/25 Javascript
解决angularjs前后端分离调用接口传递中文时中文乱码的问题
2018/08/13 Javascript
vue动画打包后失效问题的解决方法
2018/09/18 Javascript
使用pm2自动化部署node项目的方法步骤
2019/01/28 Javascript
使用Vue开发自己的Chrome扩展程序过程详解
2019/06/21 Javascript
JS实现滑动插件
2020/01/15 Javascript
深入Python解释器理解Python中的字节码
2015/04/01 Python
Python 加密的实例详解
2017/10/09 Python
如何在sae中设置django,让sae的工作环境跟本地python环境一致
2017/11/21 Python
DRF跨域后端解决之django-cors-headers的使用
2019/01/27 Python
flask框架渲染Jinja模板与传入模板变量操作详解
2020/01/25 Python
Python表达式的优先级详解
2020/02/18 Python
python实现简单的购物程序代码实例
2020/03/03 Python
使用python批量转换文件编码为UTF-8的实现
2020/04/03 Python
纯CSS3实现的井字棋游戏
2020/11/25 HTML / CSS
Nicole Miller官方网站:纽约女装品牌
2019/09/14 全球购物
秋季开学典礼主持词
2014/03/19 职场文书
社区两委对照检查材料
2014/08/23 职场文书
我爱幼儿园演讲稿
2014/09/11 职场文书
先进班集体申报材料
2014/12/26 职场文书
大客户经理岗位职责
2015/04/09 职场文书
幼儿园教学工作总结2015
2015/05/12 职场文书
入党转正介绍人意见
2015/06/03 职场文书
apache基于端口创建虚拟主机的示例
2021/04/22 Servers
go 实现简易端口扫描的示例
2021/05/22 Golang