Почему поток кажется ПУСТЫМ после ПЕРВОГО чтения из потока с помощью bufio.Reader?

  • Уважаемые коллеги, почему после ПЕРВОГО чтения из потока buf, (где buf - файл или любой другой буфер, который реализует os.Reader интерфейс) с помощью метода bufio.NewReader(buf).ReadString('\n'), хранилище buf стало ПУСТЫМ и во время второго чтения размер хранилища равен 0 ?

  • Мне объяснили это так - у структуры bufio.Reader := bufio.NewReader(buf) есть внутренний буфер по умолчанию 4096 байт и при вызове у bufio.Reader метода ReadString('\n') в внутренний буфер bufio.Reader считывается сразу string1\nstring2\n то есть все строки из файла , если их размер не превышает 4096.

  • Это нужно для Кэширования и уменьшения количества обращений к источнику данных(например файлу или , в нашем случае, байтовому потоку buf)

  • ВЕРНО ЛИ ЧТО так как в первом случае func fn1() мы использовали ДВА bufio.Reader, второй ничего уже прочитать не мог - все было сохранено в буфере первого bufio.Reader - мне объяснили так.

  • неужели после чтения из потока ОДНИМ читателем , поток\файл ОПУСТОШАЕТСЯ ? (или в потоке\файле навсегда устанавливается какой то "курсор" на КОНЕЦ файла или на том месте до которого считал данные ПЕРВЫЙ читатель) ? Неужели при создании нескольких читателей каждый из них НЕ сможет читать с самого начала файла\потока ?

  • Для примера привел func fn2() где все работает правильно, и повторное чтение уже происходит НЕ из источника данных buf, а из внутреннего буфера bufio.Reader, куда данные были кэшированы при прочтении ПЕРВОЙ строки

func fn1() {
    buf := bytes.NewBufferString("string1\nstring2\n")
    fmt.Printf("Размер буфера до чтения первой строки: %d\n", buf.Len()) // Размер буфера до чтения первой строки: 16

    str1, _ = bufio.NewReader(buf).ReadString('\n')
    fmt.Printf("Размер буфера после чтения первой строки: %d\n", buf.Len()) // Размер буфера после чтения первой строки: 0
    str2, _ = bufio.NewReader(buf).ReadString('\n')

    fmt.Println(str1, str2) // string1
}

func fn2() {
    buf := bytes.NewBufferString("string1\nstring2\n")
    fmt.Printf("Размер буфера до чтения первой строки: %d\n", buf.Len()) // Размер буфера до чтения первой строки: 16

    r := bufio.NewReader(buf)
    str1, _ = r.ReadString('\n')
    fmt.Printf("Размер буфера после чтения первой строки: %d\n", buf.Len()) // Размер буфера после чтения первой строки: 0
    fmt.Printf("Размер буфера bufio.Reader: %d\n", r.Buffered()) // Размер буфера bufio.Reader: 8
    str2, _ = r.ReadString('\n')

    fmt.Println(str1, str2) // string1 ... string2
}

Ответы (3 шт):

Автор решения: Solt

Вообще странно использовать две читалки одного потока.

Однако ваши рассуждения совершенно верны. Он не кажется пустым, а он опустошён на 4096 байтов.

В документации https://pkg.go.dev/bufio#NewReader сказано: Reader implements buffering for an io.Reader object.

И в исходниках https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:src/bufio/bufio.go

сказано defaultBufSize = 4096

Вам надо попробовать использовать NewScanner не похоже, что он буферизует

str1, _ = bufio.NewScanner(buf).Scan()
str2, _ = bufio.NewScanner(buf).Scan()

Не моё это дело, но всё-таки зачем отдельные читалки? Второй вариант у вас - совершенно правильное использование ридера.

→ Ссылка
Автор решения: Stanislav Volodarskiy

Когда вы отдаёте поток ридеру, вы больше не можете этим потоком распоряжаться. Ридер захватывает его и делает с ним что пожелает. Следовательно, один поток – один ридер.

Так вы можете прочитать данные два раза:

func fn3() {
    data := "string1\nstring2\n"            // данные файла

    buf1 := bytes.NewBufferString(data)     // поток
    reader1 := bufio.NewReader(buf1)        // читатель
    str1, _ = reader1.ReadString('\n')      // первая строка

    buf2 := bytes.NewBufferString(data)     // это второй поток
    reader2 := bufio.NewReader(buf2)        // это второй читатель
    reader2.ReadString('\n')                // пропускаем первую строку
    str2, _ := reader2.ReadString('\n')     // читаем вторую

    fmt.Println(str1, str2)                 // string1 string2
}

Переменные buf1, buf2 в этом коде бесполезны. Лучше их совсем не заводить. Сразу:

    reader1 := bufio.NewReader(bytes.NewBufferString(data))  // читатель
→ Ссылка
Автор решения: Pak Uula

Вы будете смеяться, но для обнуления buf даже нет нужды читать из него целую строку. Достаточно прочитать один байт, и всё, буфер обнуляется.

Чтобы узнать, что происходит внутри, можно сделать свой Reader, который логирует запросы на чтение:

type MyBuffer struct {
    b *bytes.Buffer
}

func (mb *MyBuffer) Read(p []byte) (n int, err error) {
    fmt.Printf("Размер буфера до чтения: %d, размер целевого буфера %d\n", mb.b.Len(), len(p))
    n, err = mb.b.Read(p)
    if err != nil {
        return n, err
    }
    fmt.Printf("Прочитано байт: %d\n", n)
    fmt.Printf("Размер буфера после чтения: %d\n", mb.b.Len()) // Размер буфера после чтения: 0
    return n, nil
}

func (mb MyBuffer) Len() int {
    return mb.b.Len()
}

func NewMyBuffer(b *bytes.Buffer) *MyBuffer {
    return &MyBuffer{b: b}
}

func fn3() {
    buf := NewMyBuffer(bytes.NewBufferString("string1\nstring2\n"))
    fmt.Printf("Размер буфера до создания читателя: %d\n", buf.Len()) // Размер буфера до создания читателя: 16
    r := bufio.NewReader(buf)

    fmt.Printf("Размер буфера до чтения первого байта: %d\n", buf.Len()) // Размер буфера до чтения первого байта: 16

    r.ReadByte()
    fmt.Printf("Размер буфера после чтения первого байта: %d\n", buf.Len()) // Размер буфера после чтения первого байта: 0
}

func main() {
    fn3()
}

Результат: https://go.dev/play/p/RxpqABs_0CV

Размер буфера до создания читателя: 16
Размер буфера до чтения первого байта: 16
Размер буфера до чтения: 16, размер целевого буфера 4096
Прочитано байт: 16
Размер буфера после чтения: 0
Размер буфера после чтения первого байта: 0

Что происходит: в программе вы запросили прочитать один байт, а читатель попросил у буфера вернуть 4096 байт. Неудивительно, что bytes.Buffer обнулился - за один запрос Read он выдал всё своё содержимое.

Причина в том, что функция bufio.Reader.Read возвращает данные из своего внутреннего буфера, и если тот пуст, то пытается его заполнить, вызывая метод bufio.Reader.fill

Этот метод пытается не более 100 раз (внутренняя константа maxConsecutiveEmptyReads) прочитать что-нибудь во внутренний буфер. Если получилось, то ура, в буфере что-то появилось, и можно вернуть байт из этого буфера.

В вашем случае строка слишком короткая, поэтому буферизующий читатель зачитывает её целиком в свой внутренний буфер при первом же обращении.

→ Ссылка