Пакет bufio встроенных функций Go
Объяснение bufio
Пакет bufio
предоставляет буферизованный ввод/вывод, что делает работу с потоками данных более эффективной. Он особенно полезен для:
- Чтения больших файлов по частям
- Обработки текстовых данных построчно
- Разбивки входных данных на слова, символы и другие логические единицы
Почему использовать bufio
?
- Эффективность: Буферизация уменьшает количество системных вызовов
- Удобство: Готовые функции для распространенных задач (чтение строк, слов)
- Гибкость: Можно создавать собственные функции разделения
- Производительность: Особенно заметна при работе с большими файлами
Пример комплексного использования:
file, _ := os.Open("data.txt")
defer file.Close()
scanner := bufio.NewScanner(file)
scanner.Split(bufio.ScanWords) // Разбиваем по словам
wordCount := 0
for scanner.Scan() {
wordCount++
}
fmt.Printf("Файл содержит %d слов", wordCount)
Пакет bufio
- это мощный инструмент для эффективной обработки текстовых и бинарных данных в Go, который стоит использовать вместо базовых операций ввода-вывода, когда важны производительность и удобство работы.
Константы
const (
// maxscantokensize - максимальный размер, используемый для буфера токена
// Если пользователь не предоставит явный буфер с [scanner.buffer].
// фактический максимальный размер токена может быть меньше в качестве буфера
// может потребоваться включить, например, новую линию.
Maxscantokensize = 64 * 1024
)
Переменные
var (
ErrinValidunReadByte = errors.new ("Bufio: недействительное использование UnReadByte")
Errinvalidunreadrune = errors.new ("bufio: недействительное использование Undeardrune")
Errbufferfull = errors.new ("bufio: buffer full")
Errnegativecount = errors.new ("bufio: отрицательный счет")
)
var (
Errtoolong = errors.new ("bufio.scanner: токен слишком длинный")
Errnegativeadvance = errors.new ("bufio.scanner: splitfunc возвращает отрицательное значение сдвига") ")
ErradvancetOfar = errors.new ("bufio.scanner: splitfunc возвращает сдвиг, превышающий входные данные")
ErrbadReadCount = errors.new ("bufio.scanner: read возвращается невозможное количество")
)
Ошибки возвращаемые Scanner.
var errfinaltoken = errors.new ("Окончательный токен")
ErrFinalToken — это специальное контрольное значение ошибки. Оно предназначено для возврата функцией Split, чтобы указать, что сканирование должно быть остановлено без ошибки. Если токен, передаваемый с этой ошибкой, не равен nil, то этот токен является последним токеном.
Это значение полезно для досрочного прекращения обработки или когда необходимо передать последний пустой токен (который отличается от токена nil). Такое же поведение можно достичь с помощью пользовательского значения ошибки, но использование этого значения более удобно. Пример использования этого значения см. в примере emptyFinalToken.
Функции
func ScanBytes
func ScanBytes(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanBytes — это функция разделения для сканера, которая возвращает каждый байт в виде токена.
Назначение: Чтение входных данных побайтово.
Пример:
scanner := bufio.NewScanner(strings.NewReader("Go"))
scanner.Split(bufio.ScanBytes)
for scanner.Scan() {
fmt.Printf("%q ", scanner.Bytes())
}
// Вывод: 'G' 'o'
Когда использовать:
- Когда нужно обрабатывать каждый байт данных
- Для низкоуровневой обработки бинарных данных
func ScanLines
func ScanLines(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanLines — это функция разделения для сканера, которая возвращает каждую строку текста без конечного маркера конца строки. Возвращаемая строка может быть пустой. Маркер конца строки — это один необязательный возврат каретки, за которым следует один обязательный символ новой строки. В нотации регулярных выражений это \r?\n
. Последняя непустая строка ввода будет возвращена, даже если в ней нет символа новой строки.
Назначение: Чтение входных данных построчно (по умолчанию в Scanner
).
Пример:
scanner := bufio.NewScanner(strings.NewReader("line1\nline2\n"))
scanner.Split(bufio.ScanLines)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// Вывод:
// line1
// line2
Когда использовать:
- Для обработки логов
- Чтения конфигурационных файлов
- Обработки любого текста, организованного по строкам
func ScanRunes
func ScanRunes(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanRunes — это функция разделения для сканера, которая возвращает каждую руну, закодированную в UTF-8, в виде токена. Последовательность возвращаемых рун эквивалентна последовательности из цикла range над входом в виде строки, что означает, что ошибочные кодировки UTF-8 переводятся в U+FFFD = “\xef\xbf\xbd”. Из-за интерфейса Scan это делает невозможным для клиента отличить правильно закодированные руны-замены от ошибок кодирования.
Назначение: Чтение входных данных посимвольно (с поддержкой UTF-8).
Пример:
scanner := bufio.NewScanner(strings.NewReader("Привет"))
scanner.Split(bufio.ScanRunes)
for scanner.Scan() {
fmt.Printf("%q ", scanner.Text())
}
// Вывод: 'П' 'р' 'и' 'в' 'е' 'т'
Когда использовать:
- Для обработки Unicode-текстов
- Когда нужно работать с отдельными символами (рунами)
- Для анализа текста на уровне символов
func ScanWords
func ScanWords(data []byte, atEOF bool) (advance int, token []byte, err error)
ScanWords — это функция разделения для Scanner, которая возвращает каждое слово текста, разделенное пробелом, с удалением окружающих пробелов. Она никогда не возвращает пустую строку. Определение пробела устанавливается unicode.IsSpace.
Назначение: Чтение входных данных по словам.
Пример:
scanner := bufio.NewScanner(strings.NewReader("Hello world!"))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// Вывод:
// Hello
// world!
Когда использовать:
- Для анализа текста по словам
- При обработке естественного языка
- Для подсчета слов в тексте
Типы
type ReadWriter
type ReadWriter struct {
*Reader
*Writer
}
ReadWriter хранит указатели на Reader и Writer. Он реализует io.ReadWriter.
func NewReadWriter
func NewReadWriter(r *Reader, w *Writer) *ReadWriter
NewReadWriter выделяет новый ReadWriter, который отправляет данные в R и W.
type Reader
type Reader struct {
// contains filtered or unexported fields
}
Reader реализует буферизацию для объекта io.Reader. Новый Reader создается вызовом NewReader или NewReaderSize; в качестве альтернативы можно использовать нулевое значение Reader после вызова [Reset] для него.
Объяснение отличий Scanner, Reader и Writer
Сравнение Scanner
, Reader
и Writer
в пакете bufio
Основные различия
1. Reader
(буферизованное чтение)
Что делает:
- Читает данные порциями в буфер
- Предоставляет низкоуровневые методы (
Read
,ReadByte
,ReadLine
и др.) - Требует ручного управления процессом чтения
Когда использовать:
reader := bufio.NewReader(file)
// Чтение точного количества байт
buf := make([]byte, 1024)
n, err := reader.Read(buf)
// Чтение до разделителя
line, err := reader.ReadString('\n')
Лучше использовать когда:
- Нужен точный контроль над процессом чтения
- Требуется чтение данных фиксированного размера
- Работа с бинарными данными
- Когда нужно несколько раз переиспользовать один и тот же буфер
2. Scanner
(высокоуровневое сканирование)
Что делает:
- Автоматически разбивает входные данные на токены
- Предоставляет простой итерационный интерфейс
- Имеет встроенные функции для разделения на строки, слова, руны
Когда использовать:
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text()) // автоматическое чтение строк
}
Лучше использовать когда:
- Обработка текстовых данных построчно
- Нужно разделять входные данные по сложным правилам
- Работа с текстовыми форматами (логи, CSV и т.д.)
- Когда важна простота кода
3. Writer
(буферизованная запись)
Что делает:
- Накапливает данные в буфере перед записью
- Предоставляет методы для эффективной записи (
Write
,WriteString
,WriteByte
)
Когда использовать:
writer := bufio.NewWriter(file)
writer.WriteString("Hello, ")
writer.WriteString("World!\n")
writer.Flush() // запись из буфера в файл
Сравнительная таблица
Характеристика | Reader | Scanner | Writer |
---|---|---|---|
Уровень абстракции | Низкий | Высокий | Средний |
Управление буфером | Ручное | Автоматическое | Полуавтоматическое |
Чтение строк | ReadString(’\n') | Scan() + Text() | - |
Производительность | Очень высокая | Высокая | Высокая |
Удобство использования | Среднее | Очень высокое | Высокое |
Лучший случай использования | Бинарные данные, сети | Текстовые файлы, логи | Запись в файлы, сети |
Практические рекомендации
-
Используйте
Scanner
когда:- Обрабатываете текстовые файлы построчно
- Нужно разделять данные по словам/символам
- Хотите простой и читаемый код
// Подсчет слов в файле scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanWords) count := 0 for scanner.Scan() { count++ }
-
Используйте
Reader
когда:- Работаете с бинарными данными
- Нужно читать данные точными порциями
- Требуется низкоуровневый контроль
// Чтение заголовка файла reader := bufio.NewReader(file) header := make([]byte, 16) _, err := reader.Read(header)
-
Используйте
Writer
когда:- Нужно эффективно записывать много небольших фрагментов
- Важна производительность при записи
// Буферизованная запись writer := bufio.NewWriter(file) for i := 0; i < 1000; i++ { writer.WriteString(fmt.Sprintf("Line %d\n", i)) } writer.Flush()
Производительность
- Для больших файлов
Reader
обычно быстрееScanner
- Для текстовой обработки
Scanner
удобнее и часто достаточно быстр Writer
дает значительный прирост производительности при множественных мелких записях
Заключение
Выбор между Scanner
, Reader
и Writer
зависит от конкретной задачи:
- Простота и удобство →
Scanner
- Контроль и производительность →
Reader
/Writer
- Текстовые данные →
Scanner
- Бинарные данные →
Reader
- Эффективная запись →
Writer
func NewReader
func NewReader(rd io.Reader) *Reader
NewReader возвращает новый объект Reader, буфер которого имеет размер по умолчанию.
func NewReaderSize
func NewReaderSize(rd io.Reader, size int) *Reader
NewReaderSize возвращает новый Reader, буфер которого имеет как минимум указанный размер. Если аргумент io.Reader уже является Reader с достаточно большим размером, то возвращается базовый Reader.
func (*Reader) Buffered
func (b *Reader) Buffered() int
Buffered возвращает количество байтов, которые можно прочитать из текущего буфера.
func (*Reader) Discard
added in go1.5
func (b *Reader) Discard(n int) (discarded int, err error)
Discard пропускает следующие n байтов и возвращает количество пропущенных байтов.
Если Discard пропускает меньше n байтов, он также возвращает ошибку. Если 0 <= n <= b.Buffered(), Discard гарантированно выполнится успешно без чтения из базового io.Reader.
func (*Reader) Peek
func (b *Reader) Peek(n int) ([]byte, error)
Peek возвращает следующие n байтов, не продвигая считывающее устройство. Байты перестают быть действительными при следующем вызове чтения. При необходимости Peek прочитает больше байтов в буфер, чтобы сделать доступными n байтов. Если Peek возвращает меньше n байтов, он также возвращает ошибку, объясняющую, почему чтение является коротким. Ошибка ErrBufferFull возникает, если n больше размера буфера b.
Вызов Peek предотвращает выполнение вызова Reader.UnreadByte или Reader.UnreadRune до следующей операции чтения.
func (*Reader) Read
func (b *Reader) Read(p []byte) (n int, err error)
Read считывает данные в p. Он возвращает количество байтов, прочитанных в p. Байты берутся максимум из одного Read в базовом Reader, поэтому n может быть меньше len(p). Чтобы прочитать ровно len(p) байтов, используйте io.ReadFull(b, p). Если базовый Reader может возвращать ненулевое значение с io.EOF, то этот метод Read также может это делать; см. документацию io.Reader.
func (*Reader) ReadByte
func (b *Reader) ReadByte() (byte, error)
ReadByte считывает и возвращает один байт. Если байт недоступен, возвращает ошибку.
func (*Reader) ReadBytes
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
ReadBytes читает входные данные до первого появления разделителя delim, возвращая фрагмент, содержащий данные до разделителя включительно. Если ReadBytes обнаруживает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF). ReadBytes возвращает err != nil, если и только если возвращаемые данные не заканчиваются на delim. Для простых задач может быть более удобно использовать Scanner.
func (*Reader) ReadLine
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
ReadLine — это низкоуровневая примитивная функция чтения строк. Большинству вызывающих функций следует использовать Reader.ReadBytes(’\n’) или Reader.ReadString(’\n’) вместо нее, либо использовать Scanner.
ReadLine пытается вернуть одну строку, не включая байты конца строки. Если строка была слишком длинной для буфера, то устанавливается isPrefix и возвращается начало строки. Остальная часть строки будет возвращена при последующих вызовах. isPrefix будет false при возвращении последнего фрагмента строки. Возвращаемый буфер действителен только до следующего вызова ReadLine. ReadLine либо возвращает строку, отличную от nil, либо возвращает ошибку, но никогда и то, и другое одновременно.
Текст, возвращаемый из ReadLine, не включает конец строки («\r\n» или «\n»). Если ввод заканчивается без конечного конца строки, никаких указаний или ошибок не выдается. Вызов Reader.UnreadByte после ReadLine всегда отменяет чтение последнего прочитанного байта (возможно, символа, принадлежащего концу строки), даже если этот байт не является частью строки, возвращаемой ReadLine.
func (*Reader) ReadRune
func (b *Reader) ReadRune() (r rune, size int, err error)
ReadRune считывает один символ Unicode, закодированный в UTF-8, и возвращает руну и ее размер в байтах. Если закодированная руна недействительна, она потребляет один байт и возвращает unicode.ReplacementChar (U+FFFD) с размером 1.
func (*Reader) ReadSlice
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
ReadSlice читает до первого появления разделителя в входных данных, возвращая фрагмент, указывающий на байты в буфере. Байты перестают быть действительными при следующем чтении. Если ReadSlice встречает ошибку до нахождения разделителя, он возвращает все данные в буфере и саму ошибку (часто io.EOF). ReadSlice завершается с ошибкой ErrBufferFull, если буфер заполняется без delim. Поскольку данные, возвращаемые из ReadSlice, будут перезаписаны следующей операцией ввода-вывода, большинство клиентов должны использовать Reader.ReadBytes или ReadString вместо этого. ReadSlice возвращает err != nil, если и только если строка не заканчивается на delim.
func (*Reader) ReadString
func (b *Reader) ReadString(delim byte) (string, error)
ReadString читает входные данные до первого появления delim, возвращая строку, содержащую данные до разделителя включительно. Если ReadString встречает ошибку до нахождения разделителя, он возвращает данные, прочитанные до ошибки, и саму ошибку (часто io.EOF). ReadString возвращает err != nil тогда и только тогда, когда возвращаемые данные не заканчиваются на delim. Для простых задач может быть более удобно использовать Scanner.
func (*Reader) Reset
func (b *Reader) Reset(r io.Reader)
Reset удаляет все данные из буфера, сбрасывает все состояния и переключает буферизованный считыватель на чтение из r. Вызов Reset с нулевым значением Reader инициализирует внутренний буфер до размера по умолчанию. Вызов b.Reset(b) (то есть сброс Reader на себя) ничего не делает.
func (*Reader) Size
func (b *Reader) Size() int
Size возвращает размер базового буфера в байтах.
func (*Reader) UnreadByte
func (b *Reader) UnreadByte() error
UnreadByte отменяет чтение последнего байта. Отменить чтение можно только последнего прочитанного байта.
UnreadByte возвращает ошибку, если последний вызов метода Reader не был операцией чтения. В частности, методы Reader.Peek, Reader.Discard и Reader.WriteTo не считаются операциями чтения.
func (*Reader) UnreadRune
func (b *Reader) UnreadRune() error
UnreadRune снимает отметку о прочтении с последней руны. Если последний вызов метода Reader не был Reader.ReadRune, Reader.UnreadRune возвращает ошибку. (В этом отношении он более строг, чем Reader.UnreadByte, который снимает отметку о прочтении с последнего байта любой операции чтения.)
func (*Reader) WriteTo
func (b *Reader) WriteTo(w io.Writer) (n int64, err error)
WriteTo реализует io.WriterTo. Это может привести к нескольким вызовам метода Reader.Read базового Reader. Если базовый Reader поддерживает метод Reader.WriteTo, то вызывается базовый Reader.WriteTo без буферизации.
type Scanner
type Scanner struct {
// contains filtered or unexported fields
}
Scanner предоставляет удобный интерфейс для чтения данных, таких как файл текста, разбитый на строки с разделителями новой строки. Последовательные вызовы метода Scanner.Scan будут проходить по «токену» файла, пропуская байты между токенами. Спецификация токена определяется функцией разбиения типа SplitFunc; функция разбиения по умолчанию разбивает входные данные на строки с удалением символов окончания строки. Функции Scanner.Split определены в этом пакете для сканирования файла на строки, байты, руны с кодировкой UTF-8 и слова, разделенные пробелами. Клиент может вместо этого предоставить собственную функцию разделения.
Сканирование останавливается без возможности восстановления при EOF, первой ошибке ввода-вывода или токене, который слишком велик, чтобы поместиться в Scanner.Buffer. Когда сканирование останавливается, читатель может продвинуться произвольно далеко за последний токен. Программы, которым требуется больший контроль над обработкой ошибок или большими токенами, или которые должны выполнять последовательное сканирование на читателе, должны использовать bufio.Reader.
Объяснение Scanner
Тип Scanner
из пакета bufio
в Go
Scanner
предоставляет удобный интерфейс для чтения и разбора текстовых данных. Это один из самых полезных инструментов для обработки текста в Go.
Основные возможности
- Чтение данных по частям (строкам, словам, символам и т.д.)
- Гибкое разделение входных данных (можно использовать готовые или свои функции)
- Эффективная буферизация (работает быстро даже с большими файлами)
Создание Scanner
scanner := bufio.NewScanner(reader)
где reader
- это любой объект, реализующий интерфейс io.Reader
(файл, строка, сетевое соединение и т.д.)
Основные методы
1. Scan() bool
Продвигает сканер к следующему токену. Возвращает false
, когда сканирование завершено.
for scanner.Scan() {
// Обработка текущего токена
}
2. Text() string
Возвращает текущий токен как строку.
scanner := bufio.NewScanner(strings.NewReader("Hello\nWorld"))
for scanner.Scan() {
fmt.Println(scanner.Text())
}
// Вывод:
// Hello
// World
3. Bytes() []byte
Возвращает текущий токен как срез байтов.
scanner := bufio.NewScanner(strings.NewReader("Hello"))
scanner.Scan()
fmt.Printf("%q", scanner.Bytes()) // Вывод: "Hello"
4. Err() error
Возвращает первую ошибку, возникшую при сканировании.
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
5. Split(splitFunc SplitFunc)
Устанавливает функцию разделения. По умолчанию используется ScanLines
.
scanner := bufio.NewScanner(strings.NewReader("Hello World"))
scanner.Split(bufio.ScanWords) // Разделять по словам
Стандартные функции разделения
ScanLines
(по умолчанию)
scanner := bufio.NewScanner(strings.NewReader("line1\nline2\nline3"))
for scanner.Scan() {
fmt.Println(scanner.Text())
}
ScanWords
scanner := bufio.NewScanner(strings.NewReader("word1 word2 word3"))
scanner.Split(bufio.ScanWords)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
ScanRunes
scanner := bufio.NewScanner(strings.NewReader("Привет"))
scanner.Split(bufio.ScanRunes)
for scanner.Scan() {
fmt.Printf("%q ", scanner.Text())
}
// Вывод: 'П' 'р' 'и' 'в' 'е' 'т'
ScanBytes
scanner := bufio.NewScanner(strings.NewReader("Go"))
scanner.Split(bufio.ScanBytes)
for scanner.Scan() {
fmt.Printf("%q ", scanner.Bytes())
}
// Вывод: 'G' 'o'
Пользовательская функция разделения
Вы можете создать свою функцию разделения:
func atComma(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == ',' {
return i + 1, data[:i], nil
}
}
return 0, data, bufio.ErrFinalToken
}
func main() {
scanner := bufio.NewScanner(strings.NewReader("a,b,c,d"))
scanner.Split(atComma)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
// Вывод:
// a
// b
// c
// d
Полезные советы
-
Ограничение размера буфера:
scanner := bufio.NewScanner(reader) buf := make([]byte, 0, 64*1024) scanner.Buffer(buf, 1024*1024) // Максимальный размер токена 1MB
-
Обработка больших строк:
scanner := bufio.NewScanner(file) scanner.Buffer(make([]byte, 100), bufio.MaxScanTokenSize)
-
Подсчет строк в файле:
file, _ := os.Open("data.txt") defer file.Close() scanner := bufio.NewScanner(file) lineCount := 0 for scanner.Scan() { lineCount++ }
Scanner
идеально подходит для:
- Обработки логов
- Чтения конфигурационных файлов
- Анализа текстовых данных
- Разбора CSV и других текстовых форматов
Главное преимущество - это простота использования и эффективность при работе с большими объемами данных.
Пример пользовательский
Используйте сканер с настраиваемой функцией разделения (созданный с помощью ScanWords) для проверки 32-разрядного десятичного ввода.
package main
import (
"bufio"
"fmt"
"strconv"
"strings"
)
func main() {
// An artificial input source.
const input = "1234 5678 1234567901234567890"
scanner := bufio.NewScanner(strings.NewReader(input))
// Create a custom split function by wrapping the existing ScanWords function.
split := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
advance, token, err = bufio.ScanWords(data, atEOF)
if err == nil && token != nil {
_, err = strconv.ParseInt(string(token), 10, 32)
}
return
}
// Set the split function for the scanning operation.
scanner.Split(split)
// Validate the input
for scanner.Scan() {
fmt.Printf("%s\n", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Printf("Invalid input: %s", err)
}
}
Output:
1234
5678
Invalid input: strconv.ParseInt: parsing "1234567901234567890": value out of range
Пример EarlyStop
Используйте сканер с настраиваемой функцией разделения для разбора списка, разделенного запятыми, с пустым конечным значением, но останавливающимся на токене «STOP».
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"strings"
)
func main() {
onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
i := bytes.IndexByte(data, ',')
if i == -1 {
if !atEOF {
return 0, nil, nil
}
// If we have reached the end, return the last token.
return 0, data, bufio.ErrFinalToken
}
// If the token is "STOP", stop the scanning and ignore the rest.
if string(data[:i]) == "STOP" {
return i + 1, nil, bufio.ErrFinalToken
}
// Otherwise, return the token before the comma.
return i + 1, data[:i], nil
}
const input = "1,2,STOP,4,"
scanner := bufio.NewScanner(strings.NewReader(input))
scanner.Split(onComma)
for scanner.Scan() {
fmt.Printf("Got a token %q\n", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
}
Output:
Got a token "1"
Got a token "2"
Пример EmptyFinalToken
Используйте сканер с настраиваемой функцией разделения для разбора списка, разделенного запятыми, с пустым конечным значением.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
// Comma-separated list; last entry is empty.
const input = "1,2,3,4,"
scanner := bufio.NewScanner(strings.NewReader(input))
// Define a split function that separates on commas.
onComma := func(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if data[i] == ',' {
return i + 1, data[:i], nil
}
}
if !atEOF {
return 0, nil, nil
}
// There is one final token to be delivered, which may be the empty string.
// Returning bufio.ErrFinalToken here tells Scan there are no more tokens after this
// but does not trigger an error to be returned from Scan itself.
return 0, data, bufio.ErrFinalToken
}
scanner.Split(onComma)
// Scan.
for scanner.Scan() {
fmt.Printf("%q ", scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
}
Output:
"1" "2" "3" "4" ""
Пример Lines
Самый простой способ использования сканера — чтение стандартного ввода в виде набора строк.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
fmt.Println(scanner.Text()) // Println will add back the final '\n'
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading standard input:", err)
}
}
Пример Words
Используйте сканер для реализации простой утилиты подсчета слов, сканируя входные данные как последовательность токенов, разделенных пробелами.
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
// An artificial input source.
const input = "Now is the winter of our discontent,\nMade glorious summer by this sun of York.\n"
scanner := bufio.NewScanner(strings.NewReader(input))
// Set the split function for the scanning operation.
scanner.Split(bufio.ScanWords)
// Count the words.
count := 0
for scanner.Scan() {
count++
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "reading input:", err)
}
fmt.Printf("%d\n", count)
}
Output:
15
func NewScanner
func NewScanner(r io.Reader) *Scanner
NewScanner возвращает новый сканер для чтения из r. По умолчанию функция разделения — ScanLines.
func (*Scanner) Buffer
func (s *Scanner) Buffer(buf []byte, max int)
Buffer устанавливает начальный буфер, который будет использоваться при сканировании, и максимальный размер буфера, который может быть выделен во время сканирования. Максимальный размер токена должен быть меньше большего из max и cap(buf). Если max <= cap(buf), Scanner.Scan будет использовать только этот буфер и не будет выделять дополнительную память.
По умолчанию Scanner.Scan использует внутренний буфер и устанавливает максимальный размер токена равным MaxScanTokenSize.
Buffer вызывает панику, если его вызвать после начала сканирования.
func (*Scanner) Bytes
func (s *Scanner) Bytes() []byte
Bytes возвращает самый последний токен, сгенерированный вызовом Scanner.Scan. Базовый массив может указывать на данные, которые будут перезаписаны последующим вызовом Scan. Он не выполняет выделение памяти.
Пример
package main
import (
"bufio"
"fmt"
"os"
"strings"
)
func main() {
scanner := bufio.NewScanner(strings.NewReader("gopher"))
for scanner.Scan() {
fmt.Println(len(scanner.Bytes()) == 6)
}
if err := scanner.Err(); err != nil {
fmt.Fprintln(os.Stderr, "shouldn't see an error scanning a string")
}
}
Output:
true
func (*Scanner) Err
func (s *Scanner) Err() error
Err возвращает первую ошибку, не являющуюся EOF, которая была обнаружена Scanner.
func (*Scanner) Scan
func (s *Scanner) Scan() bool
Scan перемещает Scanner к следующему токену, который затем будет доступен через метод Scanner.Bytes или Scanner.Text. Он возвращает false, когда больше нет токенов, либо по достижении конца ввода, либо из-за ошибки. После того, как Scan возвращает false, метод Scanner.Err вернет любую ошибку, которая произошла во время сканирования, за исключением того, что если это было io.EOF, Scanner.Err вернет nil. Scan вызывает панику, если функция split возвращает слишком много пустых токенов, не продвигая ввод. Это обычный режим ошибки для сканеров.
func (*Scanner) Split
func (s *Scanner) Split(split SplitFunc)
Split устанавливает функцию разделения для сканера. Функция разделения по умолчанию — ScanLines.
Split вызывает панику, если его вызывают после начала сканирования.
func (*Scanner) Text
добавлено в go1.1
func (s *Scanner) Text() string
Text возвращает последний токен, сгенерированный вызовом Scanner.Scan, в виде вновь выделенной строки, содержащей его байты.
type SplitFunc
type SplitFunc func(data []byte, atEOF bool) (advance int, token []byte, err error)
SplitFunc — это сигнатура функции разделения, используемой для токенизации входных данных. Аргументами являются начальная подстрока оставшихся необработанных данных и флаг atEOF, который сообщает, есть ли у Reader еще данные для передачи. Возвращаемые значения — это количество байтов для продвижения входных данных и следующий токен для возврата пользователю, если таковой имеется, плюс ошибка, если таковая имеется.
Сканирование останавливается, если функция возвращает ошибку, и в этом случае часть ввода может быть отброшена. Если эта ошибка — ErrFinalToken, сканирование останавливается без ошибки. Токен, отличный от nil, доставленный с ErrFinalToken, будет последним токеном, а токен nil с ErrFinalToken немедленно останавливает сканирование.
В противном случае сканер продвигает ввод. Если токен не равен nil, сканер возвращает его пользователю. Если токен равен nil, сканер считывает больше данных и продолжает сканирование; если данных больше нет (если atEOF было true), сканер возвращается. Если данные еще не содержат полный токен, например, если при сканировании строк в них нет символа новой строки, SplitFunc может вернуть (0, nil, nil), чтобы сигнализировать сканеру о необходимости считывать больше данных в срез и повторить попытку с более длинным срезом, начиная с той же точки во входных данных.
Функция никогда не вызывается с пустым фрагментом данных, если atEOF не равно true. Однако, если atEOF равно true, данные могут быть непустыми и, как всегда, содержать необработанный текст.
Объяснение SplitFunc
Тип SplitFunc
в пакете bufio
SplitFunc
- это специальный тип функции, который определяет как сканер (Scanner
) должен разделять входные данные на токены. Это ключевой механизм, обеспечивающий гибкость работы с текстовыми данными.
Для чего нужен SplitFunc
?
-
Определение логики разделения данных:
- Где заканчивается один токен и начинается следующий
- Как обрабатывать неполные данные
- Как реагировать на ошибки
-
Поддержка различных форматов данных:
- Стандартные (строки, слова, символы)
- Пользовательские (CSV, логи, специфичные форматы)
-
Обработка сложных случаев:
- Данные, поступающие частями
- Нестандартные разделители
- Составные токены
Как работает SplitFunc
Функция принимает:
data []byte
- буфер с данными для анализаatEOF bool
- флаг, указывающий, что это конец данных
Возвращает:
advance int
- на сколько байт продвинуться в буфереtoken []byte
- выделенный токен (может быть nil)err error
- ошибка, если возникла
Примеры использования
1. Стандартные функции разделения
// Чтение по строкам (по умолчанию)
scanner.Split(bufio.ScanLines)
// Чтение по словам
scanner.Split(bufio.ScanWords)
// Чтение по символам (рунам)
scanner.Split(bufio.ScanRunes)
// Чтение по байтам
scanner.Split(bufio.ScanBytes)
2. Пользовательская функция разделения
Пример разделения по запятым:
func splitByComma(data []byte, atEOF bool) (advance int, token []byte, err error) {
// Ищем запятую в данных
for i := 0; i < len(data); i++ {
if data[i] == ',' {
// Возвращаем позицию после запятой и токен
return i + 1, data[:i], nil
}
}
// Если достигнут конец данных и есть что возвращать
if atEOF && len(data) > 0 {
return len(data), data, nil
}
// Запрашиваем больше данных
return 0, nil, nil
}
// Использование
scanner.Split(splitByComma)
3. Сложный пример - чтение CSV
func csvSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
if len(data) == 0 {
return 0, nil, nil
}
// Пропускаем кавычки, если есть
inQuotes := false
if data[0] == '"' {
inQuotes = true
data = data[1:]
}
for i := 0; i < len(data); i++ {
switch {
case inQuotes && data[i] == '"' && i+1 < len(data) && data[i+1] == '"':
i++ // пропускаем двойные кавычки
case inQuotes && data[i] == '"':
// Закрывающая кавычка
return i + 2, data[:i], nil
case !inQuotes && data[i] == ',':
// Разделитель полей
return i + 1, data[:i], nil
case !inQuotes && (data[i] == '\r' || data[i] == '\n'):
// Конец строки
return i + 1, data[:i], nil
}
}
if atEOF {
return len(data), data, nil
}
return 0, nil, nil
}
Когда создавать свой SplitFunc
?
-
Нестандартные форматы данных:
- Специфичные логи
- Двоичные протоколы
- Сложные разделители
-
Оптимизация обработки:
- Когда стандартные функции неэффективны
- Для обработки очень больших файлов
-
Специальная логика:
- Пропуск комментариев
- Обработка escape-последовательностей
- Валидация данных на лету
Особенности работы
-
Инкрементная обработка:
- Функция может вызываться много раз для одного токена
- Нужно корректно обрабатывать частичные данные
-
Управление буфером:
- Можно ограничивать максимальный размер токена
- Важно правильно указывать позицию продвижения
-
Обработка ошибок:
- Можно возвращать специальные ошибки
bufio.ErrFinalToken
- маркер последнего токена
SplitFunc
- это мощный механизм, который делает Scanner
чрезвычайно гибким инструментом для обработки текстовых данных в Go.
type Writer
type Writer struct {
// contains filtered or unexported fields
}
Writer реализует буферизацию для объекта io.Writer. Если при записи в Writer происходит ошибка, дальнейшая запись данных не будет приниматься, а все последующие операции записи и Writer.Flush будут возвращать ошибку. После записи всех данных клиент должен вызвать метод Writer.Flush, чтобы гарантировать, что все данные были переданы в базовый io.Writer.
Пример
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
w := bufio.NewWriter(os.Stdout)
fmt.Fprint(w, "Hello, ")
fmt.Fprint(w, "world!")
w.Flush() // Don't forget to flush!
}
Output:
Hello, world!
func NewWriter
func NewWriter(w io.Writer) *Writer
NewWriter возвращает новый Writer, буфер которого имеет размер по умолчанию. Если аргумент io.Writer уже является Writer с достаточно большим размером буфера, он возвращает базовый Writer.
func NewWriterSize
func NewWriterSize(w io.Writer, size int) *Writer
NewWriterSize возвращает новый Writer, буфер которого имеет как минимум указанный размер. Если аргумент io.Writer уже является Writer с достаточно большим размером, он возвращает базовый Writer.
func (*Writer) Available
func (b *Writer) Available() int
Available возвращает количество неиспользуемых байтов в буфере.
func (*Writer) AvailableBuffer
func (b *Writer) AvailableBuffer() []byte
AvailableBuffer возвращает пустой буфер с емкостью b.Available(). Этот буфер предназначен для добавления и передачи в следующий вызов Writer.Write. Буфер действителен только до следующей операции записи в b.
Пример
package main
import (
"bufio"
"os"
"strconv"
)
func main() {
w := bufio.NewWriter(os.Stdout)
for _, i := range []int64{1, 2, 3, 4} {
b := w.AvailableBuffer()
b = strconv.AppendInt(b, i, 10)
b = append(b, ' ')
w.Write(b)
}
w.Flush()
}
Output:
1 2 3 4
func (*Writer) Buffered
func (b *Writer) Buffered() int
Buffered возвращает количество байтов, которые были записаны в текущий буфер.
func (*Writer) Flush
func (b *Writer) Flush() error
Flush записывает все буферизованные данные в базовый io.Writer.
func (*Writer) ReadFrom
func (b *Writer) ReadFrom(r io.Reader) (n int64, err error)
ReadFrom реализует io.ReaderFrom. Если базовый writer поддерживает метод ReadFrom, то вызывается базовый ReadFrom. Если есть буферизованные данные и базовый ReadFrom, то буфер заполняется и записывается перед вызовом ReadFrom.
Пример
package main
import (
"bufio"
"bytes"
"fmt"
"strings"
)
func main() {
var buf bytes.Buffer
writer := bufio.NewWriter(&buf)
data := "Hello, world!\nThis is a ReadFrom example."
reader := strings.NewReader(data)
n, err := writer.ReadFrom(reader)
if err != nil {
fmt.Println("ReadFrom Error:", err)
return
}
if err = writer.Flush(); err != nil {
fmt.Println("Flush Error:", err)
return
}
fmt.Println("Bytes written:", n)
fmt.Println("Buffer contents:", buf.String())
}
Output:
Bytes written: 41
Buffer contents: Hello, world!
This is a ReadFrom example.
func (*Writer) Reset
func (b *Writer) Reset(w io.Writer)
Reset удаляет все невыгруженные данные из буфера, очищает все ошибки и сбрасывает b, чтобы записать его вывод в w. Вызов Reset с нулевым значением Writer инициализирует внутренний буфер до размера по умолчанию. Вызов w.Reset(w) (то есть сброс Writer к самому себе) ничего не делает.
func (*Writer) Size
added in go1.10
func (b *Writer) Size() int
Size возвращает размер базового буфера в байтах.
func (*Writer) Write
func (b *Writer) Write(p []byte) (nn int, err error)
Write записывает содержимое p в буфер. Он возвращает количество записанных байтов. Если nn < len(p), он также возвращает ошибку, объясняющую, почему запись не завершена.
func (*Writer) WriteByte
func (b *Writer) WriteByte(c byte) error
WriteByte записывает один байт.
func (*Writer) WriteRune
func (b *Writer) WriteRune(r rune) (size int, err error)
WriteRune записывает одну кодовую точку Unicode, возвращая количество записанных байтов и любую ошибку.
func (*Writer) WriteString
func (b *Writer) WriteString(s string) (int, error)
WriteString записывает строку. Он возвращает количество записанных байтов. Если количество меньше len(s), он также возвращает ошибку с объяснением, почему запись не завершена.