Чтение определенного количества байтов из буферизованного считывателя в golang

Я знаю об особой функции в golang из пакета bufio.

func (b *Reader) Peek(n int) ([]byte, error)

Peek возвращает следующие n байтов без продвижения считывателя. Байты перестают быть действительными при следующем вызове чтения. Если Peek возвращает меньше n байтов, он также возвращает ошибку, объясняющую, почему чтение короткое. Ошибка ErrBufferFull, если n больше, чем размер буфера b.

Мне нужно иметь возможность прочитать определенное количество байтов из устройства чтения, которое продвинет чтение вперед. По сути, идентична функции выше, но она расширяет возможности читателя. Кто-нибудь знает, как это сделать?


person Kirk Backus    schedule 01.12.2012    source источник


Ответы (6)


Обратите внимание, что метод bufio.Read вызывает базовый io.Read не более одного раза, а это означает, что он может вернуть n < len(p), не достигая EOF. Если вы хотите прочитать ровно len(p) байта или выдать ошибку, вы можете использовать io.ReadFull следующим образом:

n, err := io.ReadFull(reader, p)

Это работает, даже если ридер находится в буфере.

person monicuta    schedule 28.10.2013
comment
Это должен быть принятый ответ. Устраняет назойливые сомнения при коротком чтении, т. е. нет необходимости зацикливаться и проверять наличие io.EOF и т. д. У Doc здесь тоже есть отличный пример: golang.org/pkg/io/#ReadFull - person colm.anseo; 19.03.2018
comment
Возможно, стоит отметить, что io.ReadFull — это просто оболочка для этого вызова: io.ReadAtLeast(reader, p, len(p)) Также в случае io.ReadFull вы должны сначала определить p с его длиной, равной размеру байтов, которые вы хотите прочитать, но для io.ReadAtLeast длина p может быть любым, если он больше или равен размеру, который вы хотите прочитать. - person sepehr; 23.01.2019
comment
@sepehr прав. Формулировка вопроса заключалась в конкретном количестве байтов, то есть, если я правильно понимаю английский язык, это равно точному количеству байтов. Кроме того, вполне актуальный вопрос, если вы хотите реализовать фрейминг сообщений поверх потока. Точное или конкретное — это не то же самое, что больше или равно. Если мне нужно ровно 4 байта, то что, если он прочитает 400? Вопрос был именно об этом. - person latitov; 05.12.2020
comment
На самом деле, io.LimitReader полезен только в том случае, если вы хотите ограничить некоторый код, который вы не контролируете, например. некоторый код внешней библиотеки. Если это ваш код, то от него нет никакой пользы, так как он просто вызывает простой метод Read() once, который в любом случае не может прочитать больше, чем вы запросили. Теперь вопрос, люди спрашивают, и я пытался найти ответ: ГДЕ именно сказано, что чтение не может читать больше, чем запрошено? Ответ: НИГДЕ. В документации по Linux базовый уровень, как обычно говорят, так и предполагалось. Ничего в Go просто не говорите ни слова. Возможно, для ребят из Google это было слишком очевидно? - person latitov; 07.12.2020
comment
Я имею в виду, что в других местах люди предлагают использовать LimitReader, чтобы ограничить максимальное количество прочитанных байтов. На самом деле это просто избыточная система безопасности, поскольку io.ReadFull() фактически выполняет свою работу и считывает точно число, которое вы запросили. Тем не менее, если вы посмотрите на исходный код, вы будете заражены сомнениями, как указал @sepehr выше. ))) Чего мне не хватает, так это совета в документации Read(), в котором говорится, что на самом деле он не может читать больше, чем запрошено. - person latitov; 07.12.2020

func (b *Reader) Read(p []byte) (n int, err error)

http://golang.org/pkg/bufio/#Reader.Read

Количество прочитанных байтов будет ограничено len(p)

person Chris    schedule 01.12.2012
comment
Однако это не будет «всегда» читать определенное количество байтов, а только ограничит количество прочитанных байтов до len(p). - person dustinevan; 01.08.2017
comment
Кроме того, он может вообще не читаться. В соответствии с этим решением вам может потребоваться неоднократно вызывать Read, пока вы не получите ожидаемые данные. Не все читатели одинаковы. Этот ответ предполагает, что они есть. - person Inanc Gumus; 25.10.2019

TLDR:

my42bytes, err := ioutil.ReadAll(io.LimitReader(myReader, 42))

Полный ответ:

@monicuta упомянул io.ReadFull, который отлично работает. Здесь я предлагаю другой метод. Он работает, соединяя ioutil.ReadAll и io.LimitReader вместе. Давайте сначала прочитаем документ:

$ go doc ioutil.ReadAll
func ReadAll(r io.Reader) ([]byte, error)
     ReadAll reads from r until an error or EOF and returns the data it read. A
     successful call returns err == nil, not err == EOF. Because ReadAll is
     defined to read from src until EOF, it does not treat an EOF from Read as an
     error to be reported. 

$ go doc io.LimitReader
func LimitReader(r Reader, n int64) Reader
     LimitReader returns a Reader that reads from r but stops with EOF after n
     bytes. The underlying implementation is a *LimitedReader.

Итак, если вы хотите получить 42 байта из myReader, вы делаете это

import (
        "io"
        "io/ioutil"
)

func main() {
        // myReader := ...
        my42bytes, err := ioutil.ReadAll(io.LimitReader(myReader, 42))
        if err != nil {
                panic(err)
        }
        //...
}

Вот эквивалентный код с io.ReadFull

$ go doc io.ReadFull
func ReadFull(r Reader, buf []byte) (n int, err error)
    ReadFull reads exactly len(buf) bytes from r into buf. It returns the number
    of bytes copied and an error if fewer bytes were read. The error is EOF only
    if no bytes were read. If an EOF happens after reading some but not all the
    bytes, ReadFull returns ErrUnexpectedEOF. On return, n == len(buf) if and
    only if err == nil. If r returns an error having read at least len(buf)
    bytes, the error is dropped.
import (
        "io"
)

func main() {
        // myReader := ...
        buf := make([]byte, 42)
        _, err := io.ReadFull(myReader, buf)
        if err != nil {
                panic(err)
        }
        //...
}

Преимущество по сравнению с io.ReadFull заключается в том, что вам не нужно вручную создавать buf, где len(buf) — количество байтов, которые вы хотите прочитать, а затем передавать buf в качестве аргумента при чтении.

Вместо этого вы просто сообщаете io.LimitReader, что вам нужно не более 42 байтов из myReader, и вызываете ioutil.ReadAll, чтобы прочитать их все, возвращая результат в виде фрагмента байтов. В случае успеха возвращаемый фрагмент гарантированно будет иметь длину 42.

person navigaid    schedule 15.05.2019
comment
Что касается вашего последнего абзаца, длина фрагмента не гарантируется в 42 байта. io.Reader может вернуть меньше байтов, чем запрошено. ReadFull попытается прочитать столько байтов, сколько вы хотите, LimitReader ограничит байты, которые вы собираетесь прочитать. ReadFull упрощает обработку случая, когда вы действительно хотите прочитать N байтов. - person Inanc Gumus; 25.10.2019
comment
... или он вернет ошибку (хорошо). + ReadAll уже может сделать буфер большего размера, чем вам нужно. - person Inanc Gumus; 25.10.2019
comment
Это должен быть общепринятый подход. Возможно, небольшая переформулировка в конце помогла бы, но в остальном это решает проблему и отвечает на вопрос. Интересно, почему для этого нет простой функции. Спасибо @navigaid - person latitov; 05.12.2020
comment
На самом деле, io.LimitReader полезен только в том случае, если вы хотите ограничить некоторый код, который вы не контролируете, например. некоторый код внешней библиотеки. Если это ваш код, то от него нет никакой пользы, так как он просто вызывает простой метод Read() once, который в любом случае не может прочитать больше, чем вы запросили. Теперь вопрос, люди спрашивают, и я пытался найти ответ: ГДЕ именно сказано, что чтение не может читать больше, чем запрошено? Ответ: НИГДЕ. В документации по Linux базовый уровень, как обычно говорят, так и предполагалось. Ничего в Go просто не говорите ни слова. Возможно, для ребят из Google это было слишком очевидно? )))) - person latitov; 07.12.2020

Я предпочитаю Read(), особенно если вы собираетесь читать файлы любого типа, и это также может быть полезно при отправке данных фрагментами, ниже приведен пример, показывающий, как он используется.

fs, err := os.Open("fileName"); 

if err != nil{
    fmt.Println("error reading file")
    return
}

defer fs.Close()

reader := bufio.NewReader(fs)

buf := make([]byte, 1024)

for{
    v, _ := reader.Read(buf) //ReadString and ReadLine() also applicable or alternative

    if v == 0{
        return
    }
    //in case it is a string file, you could check its content here...
    fmt.Print(string(buf))
}
person Muhammad Soliman    schedule 02.12.2013

Передайте считывателю буфер размером n байт.

person zzzz    schedule 01.12.2012
comment
Это не гарантирует, что все n байта будут получены с помощью Read(). Это может быть меньше без EOF в некоторых крайних случаях, вызывающих плавающую ошибку. Лучше выделить буфер точного размера buf := make([]byte, n), а затем io.ReadAtLeast(reader, buf, len(buf)) - person Denis; 25.06.2020

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

n := 512
buff := make([]byte, n)
fs.Read(buff)  // fs is your reader. Can be like this fs, _ := os.Open('file')

func (b *Reader) Read(p []byte) (n int, err error)

Read считывает данные в p. Он возвращает количество байтов, прочитанных в p. Байты берутся не более чем из одного чтения на базовом устройстве чтения, поэтому n может быть меньше, чем len(p).

person Salvador Dali    schedule 04.07.2016