Ответ 1
io.Writer
Выходной поток представляет цель, в которую вы можете записать последовательность байтов. В Go это фиксируется общим интерфейсом io.Writer
:
type Writer interface {
Write(p []byte) (n int, err error)
}
Все, что имеет этот единственный метод Write()
может быть использовано в качестве вывода, например, файл на вашем диске ( os.File
), сетевое соединение ( net.Conn
) или буфер в памяти ( bytes.Buffer
).
http.ResponseWriter
который используется для настройки HTTP-ответа и отправки данных клиенту, также является io.Writer
, данные, которые вы хотите отправить (тело ответа), собираются путем вызова (не обязательно только один раз) ResponseWriter.Write()
(для реализации общего io.Writer
). Это единственная гарантия, которую вы имеете о реализации интерфейса http.ResponseWriter
(относительно отправки тела).
WriteString()
Теперь WriteString()
к WriteString()
. Часто мы хотим записать текстовые данные в io.Writer
. Да, мы можем сделать это, просто преобразовав string
в []byte
, например
w.Write([]byte("Hello"))
который работает как ожидалось. Однако это очень частая операция, и поэтому существует "общепринятый" метод для этого, io.StringWriter
интерфейсом io.StringWriter
(доступен начиная с Go 1.12, до этого он не экспортировался):
type StringWriter interface {
WriteString(s string) (n int, err error)
}
Этот метод дает возможность записать string
значение вместо []byte
. Поэтому, если что-то (которое также реализует io.Writer
) реализует этот метод, вы можете просто передать string
значения без преобразования []byte
. Это кажется небольшим упрощением в коде, но это больше, чем это. Преобразование string
в []byte
должно сделать копию содержимого string
(поскольку string
значения неизменны в Go, подробнее об этом здесь: golang: [] byte (string) vs [] byte (* string)), поэтому есть некоторые накладные расходы, которые становятся заметными, если string
"больше" и/или вам приходится делать это много раз.
В зависимости от характера и подробностей реализации io.Writer
, может быть возможно записать содержимое string
без преобразования его в []byte
и, таким образом, избежать упомянутых выше издержек.
Например, если io.Writer
- это что-то, что пишет в буфер в памяти (например, bytes.Buffer
), он может использовать встроенную функцию copy()
:
Встроенная функция копирования копирует элементы из исходного слайса в целевой слайс. (В особом случае он также будет копировать байты из строки в секцию байтов.)
Функция copy()
может использоваться для копирования содержимого (байтов) string
в []byte
без преобразования string
в []byte
, например:
buf := make([]byte, 100)
copy(buf, "Hello")
Теперь есть "служебная" функция io.WriteString()
которая записывает string
в io.Writer
. Но он делает это, сначала проверяя, передан ли (динамический тип) io.Writer
метод WriteString()
, и если да, то он будет использоваться (реализация которого, вероятно, более эффективна). Если переданный io.Writer
не имеет такого метода, тогда общий метод convert-to-byte-slice-and-write-that будет использоваться как "запасной вариант".
Вы можете подумать, что этот WriteString()
будет преобладать только в случае буферов в памяти, но это не так. Ответы веб-запросов также часто буферизуются (с использованием буфера в памяти), поэтому это может улучшить производительность и в случае http.ResponseWriter
. И если вы посмотрите на реализацию http.ResponseWriter
: это не http.response
тип http.response
( server.go
настоящее время строка # 308), который действительно реализует WriteString()
(в настоящее время строка # 1212), так что это подразумевает улучшение.
В общем, всякий раз, когда вы пишете string
значения, рекомендуется использовать io.WriteString()
как это может быть более эффективным (быстрее).
fmt.Fprintf()
Вы должны смотреть на это как на удобный и простой способ добавить больше форматирования к данным, которые вы хотите записать, в обмен на то, чтобы быть несколько менее производительным.
Поэтому используйте fmt.Fprintf()
если вы хотите, чтобы форматированная string
создавалась простым способом, например:
name := "Bob"
age := 23
fmt.Fprintf(w, "Hi, my name is %s and I'm %d years old.", name, age)
Что приведет к записи следующей string
:
Hi, my name is Bob and I'm 23 years old.
Одна вещь, которую вы не должны забывать: fmt.Fprintf()
ожидает строку формата, поэтому она будет предварительно обработана и не будет записана как есть на выходе. В качестве быстрого примера:
fmt.Fprintf(w, "100 %%")
Вы ожидаете, что "100 %%"
будет записано в вывод (с 2 %
символов), но будет отправлен только один, так как в строке формата %
это специальный символ, а %%
приведет только к одному %
в выход.
Если вы просто хотите написать string
используя пакет fmt
, используйте fmt.Fprint()
которая не требует string
формата:
fmt.Fprint(w, "Hello")
Другое преимущество использования пакета fmt
заключается в том, что вы можете записывать значения и других типов, а не только string
s, например
fmt.Fprint(w, 23, time.Now())
(Конечно, правила преобразования любого значения в string
- и в конце концов в серию байтов - хорошо определены в документе пакета fmt
.)
Для "простых" форматированных выходных данных пакет fmt
может быть в порядке. Для сложных выходных документов рекомендуется использовать text/template
(для общего текста) и html/template
(всякий раз, когда вывод представляет собой HTML).
Передача/передача http.ResponseWriter
Для полноты следует отметить, что часто контент, который вы хотите отправить в виде веб-ответа, генерируется "чем-то", поддерживающим "потоковую передачу" результата. Примером может быть ответ JSON, который генерируется из структуры или карты.
В таких случаях часто более эффективно передавать/передавать ваш http.ResponseWriter
который является io.Writer
для этого, если он поддерживает запись результата в io.Writer
на лету.
Хорошим примером этого является генерация ответов JSON. Конечно, вы могли бы json.Marshal()
объект в JSON с помощью json.Marshal()
, который возвращает вам фрагмент байта, который вы можете просто отправить, вызвав ResponseWriter.Write()
.
Однако более эффективно сообщить пакету json
, что у вас есть io.Writer
, и в конечном итоге вы захотите отправить результат на него. Таким образом, нет необходимости сначала генерировать текст JSON в буфере, который вы просто записываете в свой ответ, а затем отбрасываете. Вы можете создать новый json.Encoder
, вызвав json.NewEncoder()
в который вы можете передать свой http.ResponseWriter
как io.Writer
, и вызвав Encoder.Encode()
после этого, непосредственно Encoder.Encode()
результат JSON в ваш Encoder.Encode()
записи ответов.
Одним из недостатков здесь является то, что в случае сбоя при генерации ответа JSON вы можете получить частично отправленный/подтвержденный ответ, который вы не сможете вернуть. Если это проблема для вас, у вас нет другого выбора, кроме генерации ответа в буфере, и если маршалинг завершился успешно, вы можете написать полный ответ сразу.