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

Go 如何按n个长度的块读取大型文件,go,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

我想读取大文本文件(接近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
}
我想这可以用更聪明的方法来完成,比如从文件中分阶段读取一定长度的块,但我不知道如何正确地完成。扫描字节并基于此进行分割。这将消除函数中除结果片分配之外的所有分配

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

}
func SplitSubN(s字符串,n int)[]字符串{
如果len=0{
归零
}
m:=0
i:=0
j:=1
var结果[]字符串
对于;ji{
结果=追加(结果,s[i:j])
}
返回结果
}
问题中指定的API要求应用程序在将从文件读取的[]字节转换为字符串时分配内存。通过将函数更改为处理字节,可以避免这种分配:

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

}
func SplitSubN(s[]字节,n int)[]字节{
如果len=0{
归零
}
m:=0
i:=0
j:=1
var结果[]字节
对于;ji{
结果=追加(结果,s[i:j])
}
返回结果
}

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

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

例如,如果您从一个文件中读取,比如说以
N
字节的块为单位,但最后一个多字节字符被部分读取(剩余部分将在下一次迭代中读取)

下面是一个解决方案,它通过给定的块读取文本文件,并以异步方式处理字符重叠:

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
}
主程序包
进口(
“fmt”
“io”
“日志”
“操作系统”
“unicode/utf8”
)
func main(){
数据,err:=ReadInChunks(“测试文件”,1024*16)
竞争:=假
竞争{
挑选{
下一个案例:=大小{
打破
}
read+=runeLen
t[i]=r
我++
nextBuffer=nextBuffer[l:]
}
返回t[:i],读取
}
如果文件是ACSII,则可以大大简化


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

拆分不仅占用大量内存,而且在此之前您正在将整个文件读入内存。您需要以流式方式读取文件。例如,使用读卡器。如果您需要n个字节,而不是n个符号,则会更容易-然后您可以使用循环将其读入n个字节的片中。对于您对n个符文的要求,最简单的方法是to按照@SergioTulentsev的建议,使用一个包含n个调用的循环,这对许多n个调用来说不是最优的,但是有效。顺便说一句,一种避免在保持代码简短和干净的同时将所有内容读入内存的方便方法是读取块并将结果放入字符串通道,然后另一个goroutine可以通过
为s:=范围通道读入通道>但是内存分配是一次只保留一个块。非常感谢!但是你能告诉我增量读取文件的方法的名称吗?我想了解它。使用file.read方法读取文件的一部分。你需要N个符文,还是可以读取舍入到最近符文的M个字节?N的值是多少?