为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
萧十郎 2022-01-17 18:25:18
我见过三种将内容写入 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")他们之间有什么区别?首选使用哪一个?
查看完整描述

2 回答

?
慕斯709654

TA贡献1840条经验 获得超5个赞

io.Writer

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


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()。通常我们想将文本数据写入io.Writer. 是的,我们可以简单地通过将 转换string为 a来做到这一点[]byte,例如


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

按预期工作。然而,这是一个非常频繁的操作,因此io.StringWriter接口捕获了一个“普遍”接受的方法(从Go 1.12开始可用,在此之前它未被导出):


type StringWriter interface {

    WriteString(s string) (n int, err error)

}

此方法提供了写入string值而不是的可能性[]byte。所以如果某些东西(也实现了io.Writer)实现了这个方法,你可以简单地传递string值而无需[]byte转换。这似乎是对代码的轻微简化,但不仅如此。将 a 转换string为[]byte必须复制string内容(因为string值在 Go 中是不可变的,请在此处阅读有关它的更多信息:golang: []byte(string) vs []byte(*string)),因此会产生一些开销如果string“更大”和/或您必须多次执行此操作,则很明显。


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


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


复制内置函数将元素从源切片复制到目标切片。(作为一种特殊情况,它还将字节从字符串复制到字节片。)


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


buf := make([]byte, 100)

copy(buf, "Hello")

现在有一个“实用程序”函数io.WriteString()可以将 astring写入io.Writer. 但它首先检查传递的(动态类型)io.Writer是否有WriteString()方法,如果有,将使用该方法(其实现可能更有效)。如果传递io.Writer的没有这样的方法,那么一般的convert-to-byte-slice-and-write-that方法将被用作“后备”。


您可能认为这WriteString()只会在内存缓冲区的情况下占优势,但事实并非如此。Web 请求的响应也经常被缓冲(使用内存缓冲区),因此它也可以提高性能http.ResponseWriter。如果您查看 : 的实现,http.ResponseWriter它是未导出的类型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是您也可以编写其他类型的值,而不仅仅是strings,例如


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

(当然,如何将任何值转换为一个string- 并最终转换为一系列字节的规则在fmt包的文档中得到了很好的定义。)


对于“简单”格式的输出,fmt包可能没问题。对于复杂的输出文档,请考虑使用text/template(对于一般文本)和html/template(只要输出是 HTML)。


传递/移交 http.ResponseWriter

为了完整起见,我们应该提到,您希望作为 Web 响应发送的内容通常是由支持“流式传输”结果的“某物”生成的。一个示例可能是从结构或映射生成的 JSON 响应。


在这种情况下,如果它支持将结果写入即时消息http.ResponseWriter,则传递/移交您的内容通常会更有效。io.Writerio.Writer


一个很好的例子是生成 JSON 响应。当然,您可以使用 将一个对象编组为 JSON json.Marshal(),它会返回一个字节切片,您可以通过调用简单地发送它ResponseWriter.Write()。


然而,让json包知道你有一个io.Writer,并且最终你想将结果发送给它会更有效。这样就不必首先在缓冲区中生成 JSON 文本,您只需将其写入响应然后丢弃。json.Encoder您可以通过调用来创建一个新的json.NewEncoder(),您可以将您的http.ResponseWriteras传递给它io.Writer,然后调用Encoder.Encode()将直接将 JSON 结果写入您的响应编写器。


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


查看完整回答
反对 回复 2022-01-17
?
慕田峪7331174

TA贡献1828条经验 获得超13个赞

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


所以 inio.WriteString和fmt.Fprintf两者都将Writer作为第一个参数,这也是一种接口包装Write(p []byte) (n int, err error)方法


type Writer interface {

    Write(p []byte) (n int, err error)

}

因此,当您调用 io.WriteString(w,"blah") 时,请检查此处


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")在这里检查


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

}

您只是ResponseWriter在两种方法中传递变量时间接调用 Write Method。


所以为什么不直接使用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}


查看完整回答
反对 回复 2022-01-17
  • 2 回答
  • 0 关注
  • 547 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信