ResponseWriter.Write 和 io.WriteString 有什么区别?

What's the difference between ResponseWriter.Write and io.WriteString?

我见过三种将内容写入 HTTP 响应的方法:

func Handler(w http.ResponseWriter, req *http.Request) {
    io.WriteString(w, "blabla.\n")
}

并且:

func Handler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("blabla\n"))
}

还有:

fmt.Fprintf(w, "blabla")

它们有什么区别?更喜欢用哪一个?

here(ResponseWriter)可以看出,它是一个带有Write([]byte) (int, error)方法的接口。

所以在 io.WriteStringfmt.Fprintf 中都将 Writer 作为第一个参数,这也是一个接口包装 Write(p []byte) (n int, err error) 方法

type Writer interface {
    Write(p []byte) (n int, err error)
}

所以当你调用 io.WriteString(w,"blah") check here

func WriteString(w Writer, s string) (n int, err error) {
  if sw, ok := w.(stringWriter); ok {
      return sw.WriteString(s)
  }
  return w.Write([]byte(s))
}

或fmt.Fprintf(w, "blabla") check here

func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
   p := newPrinter()
   p.doPrintf(format, a)
   n, err = w.Write(p.buf)
   p.free()
   return
}

您只是在间接调用 Write 方法,因为您在两种方法中都传递了 ResponseWriter 变量。

那么为什么不直接使用 w.Write([]byte("blabla\n")) 调用它呢?我希望你得到你的答案。

PS:如果您想将其作为 JSON 响应发送,还有一种不同的使用方式。

json.NewEncoder(w).Encode(wrapper)
//Encode take interface as an argument. Wrapper can be:
//wrapper := SuccessResponseWrapper{Success:true, Data:data}

io.Writer

输出流代表一个目标,您可以向其写入字节序列。在 Go 中,这是由通用 io.Writer 接口捕获的:

type Writer interface {
    Write(p []byte) (n int, err error)
}

所有具有这种单一 Write() 方法的东西都可以用作输出,例如磁盘上的文件 (os.File), a network connection (net.Conn) or an in-memory buffer (bytes.Buffer)。

用于配置HTTP响应和向客户端发送数据的http.ResponseWriter也是这样一个io.Writer,你要发送的数据(响应体)由调用(不一定只是一次)ResponseWriter.Write()(即实现通用io.Writer)。这是您对 http.ResponseWriter 接口(关于发送正文)实现的唯一保证。

WriteString()

现在 WriteString()。通常我们想将文本数据写入 io.Writer。是的,我们可以简单地将 string 转换为 []byte,例如

w.Write([]byte("Hello"))

按预期工作。然而,这是一个非常频繁的操作,因此有一个 "generally" 接受的方法,由 io.StringWriter interface (available since Go 1.12 捕获,在此之前它未导出):

type StringWriter interface {
    WriteString(s string) (n int, err error)
}

此方法可以写入 string 值而不是 []byte。因此,如果某些东西(也实现了 io.Writer)实现了这个方法,您可以简单地传递 string 值而无需 []byte 转换。 这似乎是对代码的轻微简化,但不仅如此。string 转换为 []byte 必须复制 string内容(因为 string 值在 Go 中是不可变的,在这里阅读更多相关信息:),所以如果 string 是 "bigger" and/or 你必须多次这样做。

根据 io.Writer 的性质和实现细节,可以写入 string 的内容而不将其转换为 []byte,从而避免上述情况开销。

例如,如果 io.Writer 是写入内存缓冲区的东西(bytes.Buffer 就是这样的例子),它可能会利用内置的 copy() 函数:

The copy built-in function copies elements from a source slice into a destination slice. (As a special case, it also will copy bytes from a string to a slice of bytes.)

copy() 可用于将 string 的内容(字节)复制到 []byte 而无需将 string 转换为 []byte,例如:

buf := make([]byte, 100)
copy(buf, "Hello")

现在有一个 "utility" 函数 io.WriteString() 可以将 string 写入 io.Writer。但它通过首先检查传递的(动态类型的)io.Writer 是否有一个 WriteString() 方法来做到这一点,如果有,将使用该方法(其实现可能更有效)。如果传递的io.Writer没有这样的方法,那么一般的convert-to-byte-slice-and-write-that方法将被用作"fallback" .

您可能认为 WriteString() 仅适用于内存缓冲区,但事实并非如此。 Web 请求的响应也经常被缓冲(使用内存缓冲区),因此它也可以在 http.ResponseWriter 的情况下提高性能。如果您查看 http.ResponseWriter 的实现:它是未导出的类型 http.responseserver.go 当前第 308 行)确实实现了 WriteString()(当前第 1212 行)所以它确实意味着改善。

总而言之,无论何时写string值,建议使用io.WriteString(),因为它可能更有效(更快)。

fmt.Fprintf()

您应该将此视为向要写入的数据添加更多格式的便捷方式,但性能稍差。

因此,如果您想以简单的方式创建格式化的 string,请使用 fmt.Fprintf(),例如:

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 个 % 字符),但只有一个会被发送,因为格式字符串 % 是一个特殊字符和 %% 只会在输出中产生一个 %

如果您只想使用 fmt 包编写 string,请使用不需要格式 string:

fmt.Fprint()
fmt.Fprint(w, "Hello")

使用 fmt 包的另一个好处是你也可以写其他类型的值,而不仅仅是 strings,例如

fmt.Fprint(w, 23, time.Now())

(当然,如何将任何值转换为 string 并最终转换为一系列字节的规则在 fmt 包的文档中定义明确。)

对于 "simple" 格式的输出,fmt 包可能没问题。对于复杂的输出文档,请考虑使用 text/template (for general text) and html/template(只要输出为 HTML)。

传递/移交http.ResponseWriter

为了完整起见,我们应该提到,您想要作为网络响应发送的内容通常是由支持 "streaming" 结果的 "something" 生成的。例如 JSON 响应,它是从结构或映射生成的。

在这种情况下,传递/移交你的 http.ResponseWriter 通常更有效,它是一个 io.Writer 给这个 something 如果它支持写结果即时 io.Writer

一个很好的例子就是生成 JSON 响应。当然,您可以使用 json.Marshal() 将对象编组到 JSON,其中 returns 是一个字节切片,您可以通过调用 ResponseWriter.Write().

简单地发送它

不过,让json包知道你有一个io.Writer会更高效,最终你要将结果发送给它。这样就没有必要首先在缓冲区中生成 JSON 文本,您只需将其写入响应然后丢弃。您可以创建一个新的 json.Encoder by calling json.NewEncoder() to which you can pass your http.ResponseWriter as an io.Writer, and calling Encoder.Encode(),然后将 JSON 结果直接写入您的响应编写器。

这里的一个缺点是如果生成 JSON 响应失败,您可能有一个部分发送/提交的响应,您无法收回。如果这对您来说是个问题,除了在缓冲区中生成响应之外,您别无选择,如果编组成功,那么您可以立即写入完整的响应。