如何按n长度的块读取大文件

时间:2019-03-04 12:17:56

标签: go

我想读取大文本文件(近3GB)并将其拆分为n个符号长度的块。我试图读取文件并使用符文拆分,但它占用大量内存。

func SplitSubN(s string, n int) []string {
    sub := ""
    subs := []string{}
    runes := bytes.Runes([]byte(s))
    l := len(runes)
    for i, r := range runes {
        sub = sub + string(r)
        if (i+1)%n == 0 {
            subs = append(subs, sub)
            sub = ""
        } else if (i + 1) == l {
            subs = append(subs, sub)
        }
    }
    return subs
}

我想它可以以更智能的方式完成,就像从文件中分阶段读取一定长度的块一样,但是我不知道该怎么做。

2 个答案:

答案 0 :(得分:2)

扫描rune start字节并根据该字节进行拆分。这样可以消除函数中除结果片的分配以外的所有分配。

func SplitSubN(s string, n int) []string {
    if len(s) == 0 {
        return nil
    }
    m := 0
    i := 0
    j := 1
    var result []string
    for ; j < len(s); j++ {
        if utf8.RuneStart(s[j]) {
            if (m+1)%n == 0 {
                result = append(result, s[i:j])
                i = j
            }
            m++
        }
    }
    if j > i {
        result = append(result, s[i:j])
    }
    return result

}

问题中指定的API要求从文件读取的[] byte转换为字符串时,应用程序需要分配内存。可以通过将功能更改为按字节工作来避免这种分配:

func SplitSubN(s []byte, n int) [][]byte {
    if len(s) == 0 {
        return nil
    }
    m := 0
    i := 0
    j := 1
    var result [][]byte
    for ; j < len(s); j++ {
        if utf8.RuneStart(s[j]) {
            if (m+1)%n == 0 {
                result = append(result, s[i:j])
                i = j
            }
            m++
        }
    }
    if j > i {
        result = append(result, s[i:j])
    }
    return result

}

这两个功能都要求应用程序将整个文件插入到内存中。我认为可以,因为问题中的函数也可以。如果您一次只需要处理一个块,则可以对上述代码进行调整,以便在文件被增量读取时进行扫描。

答案 1 :(得分:0)

实际上,最有趣的部分不是解析块本身,而是处理重叠的字符。

例如,如果您从一个文件中读取N个字节的数据块,但是部分读取了最后一个多字节char(其余部分将在下一次迭代中读取)。

这是一种解决方案,它按给定的块读取文本文件并以异步方式处理重叠的字符:

package main

import (
    "fmt"
    "io"
    "log"
    "os"
    "unicode/utf8"
)

func main() {
    data, err := ReadInChunks("testfile", 1024*16)

    competed := false
    for ; !competed; {
        select {
        case next := <-data:
            if next == nil {
                competed = true
                break
            }
            fmt.Printf(string(next))
        case e := <-err:
            if e != nil {
                log.Fatalf("error: %s", e)
            }
        }
    }
}

func ReadInChunks(path string, readChunkSize int) (data chan []rune, err chan error) {
    var readChanel = make(chan []rune)
    var errorChanel = make(chan error)

    onDone := func() {
        close(readChanel)
        close(errorChanel)
    }

    onError := func(err error) {
        errorChanel <- err
        onDone()
    }

    go func() {
        if _, err := os.Stat(path); os.IsNotExist(err) {
            onError(fmt.Errorf("file [%s] does not exist", path))
            return
        }

        f, err := os.Open(path)
        if err != nil {
            onError(err)
            return
        }
        defer f.Close()

        readBuf := make([]byte, readChunkSize)
        reminder := 0

        for {
            read, err := f.Read(readBuf[reminder:])
            if err == io.EOF {
                onDone()
                return
            }
            if err != nil {
                onError(err)
            }

            runes, parsed := runes(readBuf[:reminder+read])
            if reminder = readChunkSize - parsed; reminder > 0 {
                copy(readBuf[:reminder], readBuf[readChunkSize-reminder:])
            }

            if len(runes) > 0 {
                readChanel <- runes
            }
        }
    }()

    return readChanel, errorChanel
}

func runes(nextBuffer []byte) ([]rune, int) {
    t := make([]rune, utf8.RuneCount(nextBuffer))
    i := 0
    var size = len(nextBuffer)
    var read = 0
    for len(nextBuffer) > 0 {
        r, l := utf8.DecodeRune(nextBuffer)
        runeLen := utf8.RuneLen(r)
        if read+runeLen > size {
            break
        }
        read += runeLen
        t[i] = r
        i++
        nextBuffer = nextBuffer[l:]
    }
    return t[:i], read
}

如果文件是ACSII,则可以大大简化。

或者,如果您需要支持unicode,则可以播放UTF-32(具有固定长度)或UTF-16(如果您不需要处理> 2字节,则可以将其视为固定大小)也是