Запускаем программу
Создайте файл main.go и сохраните в нём программу. Она копирует данные между реализациями интерфейсов, не привязываясь к конкретному файлу или сетевому соединению.
package main
import (
"bufio"
"bytes"
"fmt"
"io"
"strings"
)
func main() {
source := strings.NewReader("Казань\nТомск\nПермь\n")
var copied bytes.Buffer
byteCount, err := io.Copy(&copied, source)
if err != nil {
fmt.Println("ошибка копирования:", err)
return
}
scanner := bufio.NewScanner(strings.NewReader(copied.String()))
lineNumber := 0
for scanner.Scan() {
lineNumber++
fmt.Printf("%d. %s\n", lineNumber, scanner.Text())
}
if err := scanner.Err(); err != nil {
fmt.Println("ошибка чтения строк:", err)
return
}
fmt.Println("прочитано байт:", byteCount)
}
Ожидаемый вывод содержит три пронумерованных города и число прочитанных байтов — 35. Важно, что это число измеряет байты в UTF-8, поэтому оно не равно числу букв для русских слов.
Reader и Writer описывают поведение
Интерфейс io.Reader требует метод Read(p []byte) (n int, err error): реализация читает до размера переданного буфера и сообщает, сколько байтов действительно записала в него. Интерфейс io.Writer требует Write(p []byte) (n int, err error) и сообщает, сколько байтов принял. Файл, строка, буфер и сетевое соединение могут реализовать эти же контракты, поэтому функция может работать с поведением, а не с конкретным типом.
Буфер передают читающему объекту; читатель не обязан заполнять его целиком за один вызов. Используйте только диапазон p[:n]: оставшиеся элементы буфера не содержат новых данных. При прямом цикле чтения обрабатывайте сначала n > 0, а затем ошибку. Последний вызов может одновременно вернуть байты и io.EOF; эти байты ещё нужно сохранить. io.EOF означает нормальный конец потока, а другая ошибка сообщает о сбое.
Если вручную пишете в io.Writer, проверяйте и err, и число n: по контракту короткая запись должна сопровождаться ошибкой. Если реализация всё же вернула n < len(p) без ошибки, считайте это io.ErrShortWrite. Высокоуровневые функции из пакета io обычно снимают с вызывающего кода необходимость самостоятельно склеивать короткие чтения и записи.
Буферизированное чтение
Пакет bufio оборачивает Reader буфером и добавляет удобные способы чтения. bufio.Reader подходит, когда нужно читать строку или разделённые данные; метод ReadString('\n') может вернуть непустую строку вместе с io.EOF, если последняя строка не заканчивается переводом строки. Сначала используйте возвращённые данные, затем обработайте ошибку.
Для последовательного чтения токенов часто удобен bufio.Scanner. Метод Scan перемещает сканер к следующему токену, а Text возвращает его текст. После цикла обязательно проверяйте Err: завершение сканирования бывает вызвано как концом входа, так и ошибкой. У Scanner есть максимальный размер токена по умолчанию; для очень длинной строки настройте буфер через Buffer до начала чтения или выберите Reader.
Копирование и вывод
Функция io.Copy(dst, src) читает из источника и записывает в назначение до конца или ошибки. В программе строковый Reader выступает источником, а bytes.Buffer — Writer. Вызов io.Copy корректно обрабатывает короткие операции на пути копирования и возвращает число переданных байтов. Для настоящего файла после успешного os.Open обычно регистрируют defer file.Close(); ошибку открытия проверяют до вызова методов файла.