我想读取大文本文件(近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
}
我想它可以以更智能的方式完成,就像从文件中分阶段读取一定长度的块一样,但是我不知道该怎么做。
答案 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字节,则可以将其视为固定大小)也是