一尘不染

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

go

我已经看到了将内容写入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")

它们之间有什么区别?首选使用哪一个?


阅读 237

收藏
2020-07-02

共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()。通常,我们希望将文本数据写入io.Writer。是的,我们可以简单地将转换string[]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转换。
这似乎只是代码上的次要简化,但不仅限于此。
将转换string[]byte必须复制string内容(因为stringGo中的值是不可变的,因此请在此处了解更多信息:golang:[]
byte(string)vs [] byte(*
string)
),因此会产生一些开销如果string“较大”和/或您必须多次执行此操作,则该提示会很明显。

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

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

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

copy()可用于一个内容(字节)复制string到一个[]byte不转换string[]byte,例如:

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

现在有一个“实用程序”功能io.WriteString(),可将a
string写入io.Writer。但这是通过首先检查传递的(动态类型)io.Writer是否具有WriteString()方法来实现的,如果有,将使用该方法(其实现可能更有效)。如果所传递的io.Writer内容没有这种方法,则将常规的
转换为字节切片并写入的 方法用作“后备”。

您可能会认为这WriteString()仅在内存缓冲区中才有效,但事实并非如此。Web请求的响应也经常被缓冲(使用内存中的缓冲区),因此在有情况下也可以提高性能http.ResponseWriter。而且,如果您看一下的实现http.ResponseWriter:它是未导出的类型http.responseserver.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())

(当然,如何stringfmt包的文档中定义如何将任何值最终转换为字节以及最终转换为字节序列的规则。)

对于“简单”格式的输出,fmt程序包可能没问题。对于复杂的输出文档,请考虑使用text/template(对于常规文本)和html/template(无论何时输出为HTML)。

传递/交接 http.ResponseWriter

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

在这种情况下,它往往更有效的传递/移交http.ResponseWriter这是io.Writer这个 东西
,如果它支持将结果写到一个io.Writer关于即时。

一个很好的例子是生成JSON响应。当然,您可以使用来将对象编组为JSON
json.Marshal(),这将返回一个字节片,您只需调用即可发送该字节片ResponseWriter.Write()

但是,让json程序包知道您有个io.Writer,这样效率更高,最终您想将结果发送给该包。这样,就不必先在缓冲区中生成JSON文本,您只需将其写入响应中然后丢弃即可。您可以json.Encoder通过调用json.NewEncoder()来创建新http.ResponseWriterio.Writer,并将用作,然后调用Encoder.Encode(),然后直接将JSON结果写入响应编写器。

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

2020-07-02