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 http使用踩过的坑与填坑指南
Apr 27 Golang
go语言中json数据的读取和写出操作
Apr 28 Golang
golang 实现两个结构体复制字段
Apr 28 Golang
Golang 如何实现函数的任意类型传参
Apr 29 Golang
解决Goland 同一个package中函数互相调用的问题
May 06 Golang
Go 语言下基于Redis分布式锁的实现方式
Jun 28 Golang
详解Go语言Slice作为函数参数的使用
Jul 02 Golang
Go语言基础知识点介绍
Jul 04 Golang
Golang表示枚举类型的详细讲解
Sep 04 Golang
深入理解go slice结构
Sep 15 Golang
一文搞懂Golang 时间和日期相关函数
Dec 06 Golang
GoFrame基于性能测试得知grpool使用场景
Jun 21 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保存和输出图片
2006/10/09 PHP
header导出Excel应用示例
2014/01/24 PHP
php中调用其他系统http接口的方法说明
2014/02/28 PHP
CI框架在CLI下执行占用内存过大问题的解决方法
2014/06/17 PHP
php通过文件流方式复制文件的方法
2015/03/13 PHP
必须收藏的23个php实用代码片段
2016/02/02 PHP
PHP中引用类型和值类型功能与用法示例
2019/02/26 PHP
php数组和链表的区别总结
2019/09/20 PHP
一次失败的jQuery优化尝试小结
2011/02/06 Javascript
JS获取下拉列表所选中的TEXT和Value的实现代码
2014/01/11 Javascript
jquery.ajax的url中传递中文乱码问题的解决方法
2014/02/07 Javascript
IE6兼容透明背景图片及解决方案
2015/08/19 Javascript
javascript中的作用域和闭包详解
2016/01/13 Javascript
javascript检测flash插件是否被禁用的方法
2016/01/14 Javascript
一步步教大家编写酷炫的导航栏js+css实现
2016/03/14 Javascript
JavaScript动态添加css样式和script标签
2016/07/19 Javascript
JavaScript实现大图轮播效果
2017/01/11 Javascript
js防刷新的倒计时代码 js倒计时代码
2017/09/06 Javascript
vue组件化中slot的基本使用方法
2019/05/01 Javascript
js new Date()实例测试
2019/10/31 Javascript
原生js无缝轮播插件使用详解
2020/03/09 Javascript
vue 封装 Adminlte3组件的实现
2020/03/18 Javascript
[02:12]DOTA2英雄基础教程 变体精灵
2013/12/16 DOTA
零基础写python爬虫之使用Scrapy框架编写爬虫
2014/11/07 Python
Python tkinter模块弹出窗口及传值回到主窗口操作详解
2017/07/28 Python
python3.7简单的爬虫实例详解
2019/07/08 Python
用python爬虫批量下载pdf的实现
2020/12/01 Python
python matplotlib工具栏源码探析二之添加、删除内置工具项的案例
2021/02/25 Python
HTML5 video 事件应用示例
2014/09/11 HTML / CSS
加拿大便宜的隐形眼镜商店:Clearly
2016/09/15 全球购物
全球采购的街头服饰和帽子:Urban Excess
2020/10/28 全球购物
如何判断一段程序是由C 编译程序还是由C++编译程序编译的
2013/08/04 面试题
HttpServlet类中的主要方法都有哪些?各自的作用是什么?
2014/03/16 面试题
党的群众路线教育实践活动对照检查材料
2014/09/22 职场文书
MySQL实现配置主从复制项目实践
2022/03/31 MySQL
python实现简单的三子棋游戏
2022/04/28 Python