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!