fmt.Stringer
Этот интерфейс часто используется, когда нужно одной строчкой залогировать сложный объект. Определение интерфейса лежит в пакете fmt.
Для примера возьмём структуру User
и допишем к ней реализацию интерфейса fmt.Stringer
:
Код выведет:
Hello, user with email example@yandex.ru
Функция fmt.Printf
использовала реализацию интерфейса.
Пакет io
Пакет io
предназначен для реализации средств ввода-вывода, однако в нём есть несколько удобных интерфейсов, которые применяются и для других целей.
io.Reader
Этот интерфейс описывает чтение из любого потока данных: сети, файловой системы или буфера. Определение интерфейса лежит в пакете io.
Метод Read
считывает в переданный слайс байт данные из источника. В качестве источника могут выступать любые данные, которые описаны в типе. То есть считываем их структуры и записываем в байты. Количество считанных байт неявно задаётся размером буфера — длиной слайса.
Объясним возможности интерфейса на примере. Есть буфер — и нужно прочитать байты из него. В пакете strings лежит функция strings.NewReader
, которая оборачивает обычную строку в структуру strings.Reader
. Эта структура имеет метод Read
, значит, она реализует интерфейс io.Reader
:
Удобство применения io.Reader
в том, что его пользователь может вообще не знать, откуда берутся данные: из файла, сети или генерируются на лету. Интерфейс описывает унифицированный метод работы с ними.
io.Writer
Этот интерфейс означает запись в любой возможный поток данных: сетевой сокет, файл или буфер. Определение интерфейса лежит в пакете io.
C этим интерфейсом ситуация, обратная io.Reader
. Он позволяет записать переданный ему слайс байт куда-то. Куда именно — определяется реализацией.
Для примера соберём большую строку из подстрок, вот только не через оператор +=
, потому что тогда на каждую итерацию будет лишняя копия всей строки. В пакете strings есть структура strings.Builder
для сборки строки без избыточного копирования. Эта структура имеет метод Write
, значит, она реализует интерфейс io.Writer
:
Приведём пример реализации интерфейса Write
. Предположим, что мы хотим посчитать хеш от некоторого массива байт или наборов массивов. Для простоты возьмём упрощённую функцию хеширования:
Функции-утилиты для io.Reader и io.Writer
io.Copy
Функция копирует все байты из io.Reader
в io.Writer
.
Данные будут считываться до тех пор, пока функция Read
не вернёт вторым аргументом ошибку. Если в качестве ошибки будет возвращено значение io.EOF
, то выполнение функции закончится без ошибок. Также будет возвращено количество байт.
io.EOF происходит от end of frame (конец файла) — исторически так назывался специальный символ, который означал конец файла.
Приведём простой пример. Напишем функцию, копирующую содержимое одного файла в другой:
Структура типа os.File
реализует интерфейсы io.Reader
и io.Writer
.
Было бы просто считать весь исходный файл в память и затем скопировать его в новый. Но если исходный файл занимает сотни гигабайт? io.Copy
работает умнее, считывая и записывая данные небольшими кусочками, поэтому для подобных операций рекомендуется использовать именно её.
io.CopyN
Функция копирует все байты из io.Reader
в io.Writer
, но не более n
байт. То же самое, что и Copy
, но с ограничением — можно использовать с источниками данных, которые слишком большие или вообще бесконечные. Например, напишем функцию, которая будет сохранять данные из нашего генератора случайных чисел в файл.
Если бы мы использовали Copy
, то программа продолжила бы работать до переполнения диска.
io.ReadAll
Функция считывает все байты из io.Reader
. Чтение закончится, когда io.Reader
вернёт io.EOF
.
io.ReadAtLeast
Функция считывает байты из io.Reader
c ограничением: если прочитанных байт оказалось меньше, чем n
, вернётся ошибка io.ErrUnexpectedEOF
. Это используется при парсинге бинарных данных, чтобы гарантировать, что нужное минимальное количество байт будет вычитано.
📂 Go | Последнее изменение: 28.08.2024 13:25