问题

比如我们有一个log文件,运行了几年,有100G之大。按照我们之前的操作可能代码会这样写:

1
2
3
4
5
6
7
func ReadFile(filePath string) []byte{
    content, err := ioutil.ReadFile(filePath)
    if err != nil {
        log.Println("Read error")
    }
    return content
}

上面的代码读取几兆的文件可以,但是如果大于你本身及其内存,那就直接翻车了。因为上面的代码,是把文件所有的内容全部都读取到内存之后返回,几兆的文件,你内存够大可以处理,但是一旦上几百兆的文件,就没那么好处理了.

按行读取

逐行读取文件有两个函数,分别为bufio包中的 ReadString 和 ReadLine 函数

ReadString

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func ReadString(filename string) error {
    f, _ := os.Open(filename)
    defer f.Close()
    r := bufio.NewReader(f)
    for {
        line, err := r.ReadString('\n')
        if err != nil {
            if err == io.EOF{
                fmt.Println(line)//注意如果遇到了io.EOF,line中保存了遇到了EOF之前的数据,可以直接输出
                return nil
            }
            return err
        }
        fmt.Println(line)
    }
}

ReadLine

ReadLine尝试返回一行数据,不包括行尾标志的字节。如果行太长超过了缓冲,返回值isPrefix会被设为true,并返回行的前面一部分。该行剩下的部分将在之后的调用中返回。返回值isPrefix会在返回该行最后一个片段时才设为false。返回切片是缓冲的子切片,只在下一次读取操作之前有效。ReadLine要么返回一个非nil的line,要么返回一个非nil的err,两个返回值至少一个非nil。默认缓冲为4096

ReadLine 读取文件更快,原因是由于 ReadString 后端调用 ReadBytes,而 ReadBytes 多次使用 copy 方法造成大量耗时。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
func ReadLine(filename string) {
    f, _ := os.Open(filename)
    defer f.Close()
    r := bufio.NewReader(f)
    for {
        _, err := readLine(r)
        if err != nil {
            break
        }
    }
}

此函数主要解决单行字节数大于4096的情况

1
2
3
4
5
6
7
8
9
func readLine(r *bufio.Reader) (string, error) {
    line, isprefix, err := r.ReadLine()
    for isprefix && err == nil {
        var bs []byte
        bs, isprefix, err = r.ReadLine()
        line = append(line, bs...)
    }
    return string(line), err
}

分片读取

第二个方案就是分片处理,当读取的是二进制文件,没有换行符的时候,使用下面的方案一样处理大文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
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", err.Error())
            os.Exit(1)
        case nr == 0: // EOF
            return nil
        case nr > 0:
            handle(s[0:nr])
        }
    }
    return nil
}

转载

http://www.voidcn.com/article/p-dzsrqswp-bnv.html

https://golangcaff.com/articles/110/two-schemes-for-reading-golang-super-large-files?order_by=vote_count&