bufio в Go

Kate

Administrator
Команда форума
Сегодня мы рассмотрим замечательный пакет в Golang bufio. Пакет bufio — это стандартная библиотека Go, предназначенная для буферизации ввода-вывода. Почему буферизация важна? Представьте, что вы пытаетесь читать или записывать данные по одному байту за раз. Это утомительно и неэффективно. bufio помогает объединить множество мелких операций в более крупные блоки.

Пакет bufio имеет несколько основных структур и методов:

  • bufio.Reader — буферизованный ридер для чтения данных из io.Reader. Создается с помощью функции bufio.NewReader(r io.Reader) *bufio.Reader.
  • bufio.Writer — буферизованный писатель для записи данных в io.Writer. Создается через bufio.NewWriter(w io.Writer) *bufio.Writer. Он накапливает данные в буфере перед записью.
  • bufio.Scanner — удобный инструмент для построчного или токенизированного чтения данных. Создается с помощью bufio.NewScanner(r io.Reader) *bufio.Scanner. Его часто юзают для простых задач по чтению данных, таких как парсинг файлов построчно.
  • bufio.ReadWriter — комбинация bufio.Reader и bufio.Writer, позволяющая одновременно читать и писать данные. Создается через bufio.NewReadWriter(r *bufio.Reader, w *bufio.Writer) *bufio.ReadWriter.
Каждая из этих структур обладает набором методов.

Методы bufio.Reader

bufio.Reader имеет ряд методов для чтения данных:

  • Read(p []byte) (n int, err error) — читает до len(p) байт в p.
  • ReadByte() (byte, error) — читает один байт.
  • ReadBytes(delim byte) ([]byte, error) — читает до delim байта включительно.
  • ReadString(delim byte) (string, error) — аналогично ReadBytes, но возвращает строку.
  • Peek(n int) ([]byte, error) — позволяет взглянуть на следующие n байт без их чтения.
Методы bufio.Writer

bufio.Writer также имеет несколько полезных методов:

  • Write(p []byte) (n int, err error) — записывает p в буфер.
  • WriteString(s string) (n int, err error) — записывает строку в буфер.
  • Flush() error — сбрасывает буфер, записывая его содержимое в io.Writer.
Особенно важен метод Flush! Он гарантирует, что все данные, накопленные в буфере, будут записаны в целевой источник. Без вызова Flush данные могут остаться в буфере и никогда не попасть в файл или на экран.

Методы bufio.Scanner

bufio.Scanner предназначен для удобного и простого чтения данных:

  • Scan() bool — читает следующий токен.
  • Text() string — возвращает текст последнего токена.
  • Bytes() []byte — возвращает байты последнего токена.
  • Err() error — возвращает ошибку, если она произошла.
Методы bufio.ReadWriter

Комбинированная структура bufio.ReadWriter имеет методы для одновременного чтения и записи:

  • ReadString(delim byte) (string, error) — читает строку до delim.
  • WriteString(s string) (int, error) — записывает строку в буфер.
  • Flush() error — сбрасывает буфер.

Применение​

Чтение файла построчно с bufio.Reader​

Допустим нужно прочитать файл data.txt построчно и обработать каждую строку. Вот как это можно сделать с использованием bufio.Reader:

package main

import (
"bufio"
"fmt"
"os"
)

func main() {
file, err := os.Open("data.txt")
if err != nil {
fmt.Printf("Ошибка открытия файла: %v\n", err)
return
}
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("Ошибка закрытия файла: %v\n", err)
}
}()

reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err.Error() != "EOF" {
fmt.Printf("Ошибка чтения файла: %v\n", err)
}
break
}
processLine(line)
}
}

func processLine(line string) {
fmt.Print(line) // Здесь можно добавить любую обработку строки
}
Открываем файл с помощью os.Open и гарантируем его закрытие с помощью defer. Затем создаем буферизованный ридер с помощью bufio.NewReader, что повышает эффективность чтения. В бесконечном цикле читаем строки до символа \n и обрабатываем их функцией processLine.

Буферизованная запись в файл с bufio.Writer​

Запись данных в файл также может быть оптимизирована с помощью буферизации. Рассмотрим пример, где записываем 1000 строк в output.txt:

package main

import (
"bufio"
"fmt"
"os"
)

func main() {
file, err := os.Create("output.txt")
if err != nil {
fmt.Printf("Ошибка создания файла: %v\n", err)
return
}
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("Ошибка закрытия файла: %v\n", err)
}
}()

writer := bufio.NewWriter(file)
for i := 1; i <= 1000; i++ {
_, err := writer.WriteString(fmt.Sprintf("Строка номер %d\n", i))
if err != nil {
fmt.Printf("Ошибка записи: %v\n", err)
return
}
}

if err := writer.Flush(); err != nil {
fmt.Printf("Ошибка сброса буфера: %v\n", err)
}
}

bufio.Writer накапливает данные в буфере, уменьшая количество операций записи. Не забудьте вызвать Flush после записи, чтобы данные попали в файл. Без этого данные могут "застрять" в буфере, как кофе в забытом стакане.

bufio.Scanner для простого чтения​

Если нужно быстро и просто читать файл построчно без сложной обработки, то здесь хорошо зайдетbufio.Scanner.

package main

import (
"bufio"
"fmt"
"os"
)

func main() {
file, err := os.Open("scan_example.txt")
if err != nil {
fmt.Printf("Ошибка открытия файла: %v\n", err)
return
}
defer file.Close()

scanner := bufio.NewScanner(file)
lineNumber := 1
for scanner.Scan() {
fmt.Printf("Строка %d: %s\n", lineNumber, scanner.Text())
lineNumber++
}

if err := scanner.Err(); err != nil {
fmt.Printf("Ошибка сканирования: %v\n", err)
}
}

Комбинированное чтение и запись с bufio.ReadWriter​

Иногда возникает необходимость одновременно читать и записывать данные. Например, нужно читать входящие сообщения из файла и записывать обработанные данные обратно. Для этого хорошо подходит bufio.ReadWriter:

package main

import (
"bufio"
"fmt"
"os"
)

func main() {
file, err := os.OpenFile("readwriter.txt", os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
fmt.Printf("Ошибка открытия файла: %v\n", err)
return
}
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("Ошибка закрытия файла: %v\n", err)
}
}()

rw := bufio.NewReadWriter(bufio.NewReader(file), bufio.NewWriter(file))

// Чтение первой строки
line, err := rw.ReadString('\n')
if err != nil {
fmt.Printf("Ошибка чтения: %v\n", err)
return
}
fmt.Printf("Прочитано: %s", line)

// Переход в конец файла для записи
_, err = rw.WriteString("Добавленная строка\n")
if err != nil {
fmt.Printf("Ошибка записи: %v\n", err)
return
}

// Сброс буфера
if err := rw.Flush(); err != nil {
fmt.Printf("Ошибка сброса буфера: %v\n", err)
}
}

Прочие техники​

По дефолту Scanner разделяет ввод по строкам. Но что, если вам нужно разделить ввод по словам или по произвольным токенам? Легко:

scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
word := scanner.Text()
fmt.Println(word)
}
А если нужно разделять ввод по символу ,:

scanner := bufio.NewScanner(file)
scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i, b := range data {
if b == ',' {
return i + 1, data[:i], nil
}
}
if atEOF && len(data) > 0 {
return len(data), data, nil
}
return 0, nil, nil
})
for scanner.Scan() {
token := scanner.Text()
fmt.Println(token)
}
Иногда данные слишком большие для стандартного буфера. Можно увеличить размер буфера Scanner следующим образом:

scanner := bufio.NewScanner(file)
buf := make([]byte, 0, 1024*1024) // 1MB
scanner.Buffer(buf, 1024*1024)
Метод Peek позволяет взглянуть на следующие n байт без их чтения:

reader := bufio.NewReader(file)
peekBytes, err := reader.Peek(5)
if err != nil {
fmt.Printf("Ошибка Peek: %v\n", err)
return
}
fmt.Printf("Первые 5 байт: %s\n", string(peekBytes))

Советы​

После записи данных всегда вызывайте Flush. Это гарантирует, что все данные попадут в целевой источник. Забудете — и ваши данные останутся в буфере:

if err := writer.Flush(); err != nil {
fmt.Printf("Ошибка сброса буфера: %v\n", err)
}
Всегда проверяйте ошибки после операций чтения и записи:

line, err := reader.ReadString('\n')
if err != nil && err != io.EOF {
// Обработка ошибки
}
bufio не является потокобезопасным. Поэтому если вы используете его в многопоточной среде, нужно сделать синхронизацию через мьютексы или другие механизмы.

Используйте defer мудро: закрывайте файлы и сбрасывайте буферы с помощью defer:

defer func() {
if err := writer.Flush(); err != nil {
log.Fatalf("Ошибка сброса буфера: %v", err)
}
if err := file.Close(); err != nil {
log.Fatalf("Ошибка закрытия файла: %v", err)
}
}()
Комбинируйте с другими пакетами: bufio отлично сочетается с io, os, fmt и другими пакетами.

 
Сверху