Как читать большой файл по блокам длиной n - PullRequest
0 голосов
/ 04 марта 2019

Я хочу прочитать и разбить большой текстовый файл (около 3 ГБ) на блоки длиной 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 голосов
/ 05 марта 2019

На самом деле, самая интересная часть - это не сам анализ чанка, а обработка перекрывающихся символов.

Например, если вы читаете из файла, скажем, кусками 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
}

Этоможет быть значительно упрощен, если файл ACSII.

Альтернативно, если вам нужна поддержка юникода, вы можете играть в Aroud UTF-32 (с фиксированной длиной) или UTF-16 (если вам не нужно обрабатывать> 2 байта, вы можете рассматривать это какфиксированный размер)

0 голосов
/ 04 марта 2019

Сканирование на запуск руны байтов и разделение на основе этого.Это исключает все выделения внутри функции, кроме выделения результирующего среза.

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, указанный в вопросе, требует, чтобы приложение выделяло память при преобразовании байта [], считанного из файла, в строку,Этого распределения можно избежать, изменив функцию для работы с байтами:

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

}

Обе эти функции требуют, чтобы приложение сбрасывало весь файл в память.Я предполагаю, что это нормально, потому что функция в вопросе делает то же самое.Если вам нужно обрабатывать только один блок за раз, то приведенный выше код можно адаптировать для сканирования по мере чтения файла.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...