Привет, Хабр! Сегодня мы рассмотрим замечательный пакет в 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». В рамках курса в январе пройдут открытые уроки, на которые приглашаем всех желающих. Записаться можно на странице курса по ссылкам ниже:
ссылка на оригинал статьи https://habr.com/ru/articles/868658/
Добавить комментарий