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!