io.Reader представляет собой интерфес с одним методом Read. По сути описывает любой тип из которого можно прочитать поток байтов.

type Reader interface {
	Read(p []byte) (n int, err error)
}

Read считывает до len(buf) байтов в buf и возвращает количество прочитанных байтов. При завершении потока возвращается ошибка io.EOF.

Стандартная библиотека предоставляет множество реализаций io.Reader (включая байтовые буферы в памяти , файлы и сетевые соединения ), а Readers принимаются в качестве аргументов многими функциями (включая реализации клиента и сервера HTTP ).

Чтение напрямую из байтового потока

Воспользуемся методом io.Reader.Read:

r := strings.NewReader("abcde")

buf := make([]byte, 4)
for {
	n, err := r.Read(buf)
	fmt.Println(n, err, buf[:n])
	if err == io.EOF {
		break
	}
}
4 <nil> [97 98 99 100]
1 <nil> [101]
0 EOF []

При помощи io.ReadFull можно прочитать ровно len(buf) байтов в buf

r := strings.NewReader("abcde")

buf := make([]byte, 4)
if _, err := io.ReadFull(r, buf); err != nil {
	log.Fatal(err)
}
fmt.Println(buf)

if _, err := io.ReadFull(r, buf); err != nil {
	fmt.Println(err)
}
[97 98 99 100]
unexpected EOF

А чтобы прочитать все данные надо воспользоваться io.ReadAll

r := strings.NewReader("abcde")

buf, err := io.ReadAll(r)
if err != nil {
	log.Fatal(err)
}
fmt.Println(buf)
[97 98 99 100 101]

Буферизированное чтение

Типы bufio.Reader и bufio.Scanner обертывают Reader, создавая другой Reader, который также реализует интерфейс, но обеспечивает буферизацию и некоторую помощь для текстового ввода.

В этом примере мы используем bufio.Scanner для подсчета количества слов в тексте.

const input = `Beware of bugs in the above code;
I have only proved it correct, not tried it.`

scanner := bufio.NewScanner(strings.NewReader(input))
scanner.Split(bufio.ScanWords) // Set up the split function.

count := 0
for scanner.Scan() {
    count++
}
if err := scanner.Err(); err != nil {
    fmt.Println(err)
}
fmt.Println(count)
16

Неявное использование io.Reader

В то время как io.Reader.Read дает нам обобщенный способ чтения из множества источников данных, в проектах на Go часто Read напрямую не используется; вместо этого у нас есть код, который строится поверх этого метода. Вот некоторые примеры:

  • Если вы хотите прочитать все байты из источника в один длинный байтовый срез, вы можете передать свой Reader в io.ReadAll.
  • Если вы хотите прочитать файл построчно, вы можете использовать bufio.Scanner для чтения строки данных с помощью метода Scan, и Scanner будет обрабатывать поиск новых строк, чтобы вашему коду не приходилось думать об этом.
  • Для работы с изображениями можно декодировать Reader в image.Image.
  • А для веб приложений вы можеnt десериализировать данные из JSON при помощи json.Decoder.Decode.
  • Если вы читаете байты, сжатые с помощью gzip, вы можете обернуть io.Reader в gzip.Reader .

Зачем использовать этот код, а не Read напрямую? Поскольку io.Reader - это интерфейс низкого уровня, его задача - читать данные из источника и копировать их в байтовый срез, но его не волнует, какие байты вы читаете. Фактически работа с этими байтами осуществляется кодом, вызывающим Read.

Как видите, io.Reader - действительно универсальный интерфейс! Вот почему в коде Go вы увидите io.Reader повсюду, и вы можете использовать код, который работает с методом Read, для работы с любым форматом данных, потрясающая абстракция интерфейса Go!