Я пытаюсь прочитать архив, который зарезервирован в потоковом режиме, на стандартный ввод, но я каким-то образом читаю в канале гораздо больше данных, чем отправляет tar.

Я запускаю свою команду так:

tar -cf - somefolder | ./my-go-binary

Исходный код такой:

package main

import (
    "bufio"
    "io"
    "log"
    "os"
)

// Read from standard input
func main() {
    reader := bufio.NewReader(os.Stdin)
    // Read all data from stdin, processing subsequent reads as chunks.
    parts := 0
    for {
        parts++
        data := make([]byte, 4<<20) // Read 4MB at a time
        _, err := reader.Read(data)
        if err == io.EOF {
            break
        } else if err != nil {
            log.Fatalf("Problems reading from input: %s", err)
        }
    }
    log.Printf("Total parts processed: %d\n", parts)
}

Для зарезервированной папки размером 100 МБ я получаю 1468 фрагментов по 4 МБ (это 6,15 ГБ)! Кроме того, кажется, не имеет значения, насколько велик массив data []byte: если я установлю размер блока на 40 МБ, я все равно получу ~ 1400 блоков данных по 40 МБ, что вообще не имеет смысла.

Что мне нужно сделать, чтобы правильно читать данные из os.Stdin с помощью Go?

19
atp 29 Ноя 2014 в 00:34

2 ответа

Лучший ответ

Ваш код неэффективен. Он выделяет и инициализирует data каждый раз в цикле.

for {
    data := make([]byte, 4<<20) // Read 4MB at a time
}

Код для вашего reader как io.Reader неверен. Например, вы игнорируете количество байтов, прочитанных _, err := reader.Read(data), и вы неправильно обрабатываете ошибки err.

Пакет io

import "io" 

введите Reader

type Reader interface {
        Read(p []byte) (n int, err error)
}

Reader - это интерфейс, который оборачивает базовый метод Read.

Read читает до len (p) байтов в p. Он возвращает количество прочитанных байтов (0 <= n <= len (p)) и обнаруженную ошибку. Даже если Read возвращает n

Когда Read обнаруживает ошибку или состояние конца файла после успешного чтения n> 0 байтов, он возвращает количество прочитанных байтов. Он может вернуть ошибку (отличную от nil) из того же вызова или вернуть ошибку (и n == 0) из последующего вызова. Примером этого общего случая является то, что Reader, возвращающий ненулевое количество байтов в конце входного потока, может возвращать либо err == EOF, либо err == nil. Следующее чтение должно вернуть 0, независимо от EOF.

Вызывающие должны всегда обрабатывать возвращенные n> 0 байтов, прежде чем рассматривать ошибку err. Это правильно обрабатывает ошибки ввода-вывода, которые возникают после чтения некоторых байтов, а также оба допустимых поведения EOF.

Реализации Read не рекомендуется возвращать счетчик нулевых байтов с ошибкой nil, за исключением случаев, когда len (p) == 0. Вызывающие операторы должны обрабатывать возврат 0 и nil как указание того, что ничего не произошло; в частности это не указывает на EOF.

Реализации не должны сохранять с.

Вот программа чтения файла модели, которая соответствует интерфейсу io.Reader:

package main

import (
    "bufio"
    "io"
    "log"
    "os"
)

func main() {
    nBytes, nChunks := int64(0), int64(0)
    r := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 4*1024)
    for {
        n, err := r.Read(buf[:cap(buf)])
        buf = buf[:n]
        if n == 0 {
            if err == nil {
                continue
            }
            if err == io.EOF {
                break
            }
            log.Fatal(err)
        }
        nChunks++
        nBytes += int64(len(buf))
        // process buf
        if err != nil && err != io.EOF {
            log.Fatal(err)
        }
    }
    log.Println("Bytes:", nBytes, "Chunks:", nChunks)
}

Выход:

2014/11/29 10:00:05 Bytes: 5589891 Chunks: 1365
42
peterSO 30 Ноя 2014 в 18:37
3
Спасибо, что также освещаете случаи ошибок в спецификации. Это, безусловно, будет работать, поскольку я пробовал его вариант. Спасибо.
 – 
atp
30 Ноя 2014 в 07:12

Прочтите документацию для чтения:

Чтение - чтение данных в стр. Возвращает количество байтов, прочитанных в p. Он вызывает Read не более одного раза в базовом Reader, поэтому n может быть меньше len (p). В EOF счетчик будет равен нулю, а err будет равен io.EOF.

Вы не читаете 4 МБ за раз. Вы предоставляете буферное пространство и отбрасываете целое число, которое сообщило бы вам, сколько на самом деле прочитано Read. Буферное пространство является максимальным, но обычно 128k, кажется, читается за вызов, по крайней мере, в моей системе. Попробуйте сами:

// Read from standard input
func main() {
    reader := bufio.NewReader(os.Stdin)
    // Read all data from stdin, passing the data as parts into the channel
    // for processing.
    parts := 0
    for {
        parts++
        data := make([]byte, 4<<20) // Read 4MB at a time
        amount , err := reader.Read(data)
        // WILL NOT BE 4MB!
        log.Printf("Read: %v\n", amount)
        if err == io.EOF {
            break
        } else if err != nil {
            log.Fatalf("Problems reading from input: %s", err)
        }
    }
    log.Printf("Total parts processed: %d\n", parts)
}

Вы должны реализовать логику для обработки изменяющихся объемов чтения.

8
user918176 29 Ноя 2014 в 00:52
Ах хорошо. Этот диагноз, безусловно, соответствует симптомам. Странно было то, что я также пытался использовать NewReaderSize() с 4 << 20 в качестве размера буфера, и я все еще получал только 71680 байт за раз при вызове Read.
 – 
atp
29 Ноя 2014 в 00:59
@ user918176: Это всего лишь часть спецификации интерфейса io.Reader. Например, вы игнорируете amount != 0 && err == io.EOF.
 – 
peterSO
29 Ноя 2014 в 01:48
@ user918176: Неэффективно выделять и инициализировать data каждый раз в цикле.
 – 
peterSO
29 Ноя 2014 в 01:53
Это правда, и я сразу заметил ненужные выделения. Однако это выходит за рамки вопроса.
 – 
user918176
29 Ноя 2014 в 14:26