bufio в Go
Привет, Хабр! Сегодня мы рассмотрим замечательный пакет в 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
и другими пакетами.
Если у вас есть интересные примеры использования bufio
— отправляйте их в комментарии!
Изучить Go — от основ и внутреннего устройства до создания микросервисов и взаимодействия с другими системами — можно на онлайн-курсе «Golang Developer. Professional». В рамках курса в январе пройдут открытые уроки, на которые приглашаем всех желающих. Записаться можно на странице курса по ссылкам ниже:
13 января: Составляем индивидуальный план развития Go-инженера от Junior до Middle. Подробнее
22 января: Кошелек или жизнь? Фича или баг? Хелсчеки в k8s. Подробнее