ResponseWriter.Write和io.WriteString之间的区别是什么?

时间:2016-06-16 15:25:20

标签: string http go slice

我已经看到了三种向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 个答案:

答案 0 :(得分:49)

io.Writer

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

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

具有此单Write()方法的所有内容都可以用作输出,例如磁盘上的文件(os.File),网络连接(net.Conn)或者 - 内存缓冲区(bytes.Buffer)。

用于配置HTTP响应并将数据发送到客户端的http.ResponseWriter也是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必须复制{{1} } content(因为string值在Go中是不可变的,请在此处阅读更多相关信息:golang: []byte(string) vs []byte(*string)),因此如果string是"更大&#,则会有一些明显的开销变得明显34;和/或你必须多次这样做。

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

例如,如果[]byte是写入内存缓冲区的内容(io.Writer就是这样的例子),它可以使用内置的copy()函数:

  

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

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

[]byte

现在有一个"实用程序"将buf := make([]byte, 100) copy(buf, "Hello") 写入string的函数io.WriteString()。但它通过首先检查传递io.Writer的(动态类型)是否具有io.Writer方法来实现此目的,如果是,则将使用(其实现可能更有效)。如果传递的WriteString()没有这样的方法,那么一般的转换为字节切片和写入方法将被用作"回退"

你可能会认为这个io.Writer只会在内存缓冲区中占优势,但事实并非如此。 Web请求的响应也经常被缓冲(使用内存缓冲区),因此在WriteString()的情况下也可以提高性能。如果你看一下http.ResponseWriter的实现:它是未导出的类型http.ResponseWriterserver.go当前行#308),它确实实现了http.response(当前行# 1212)因此它确实意味着改进。

总而言之,无论何时编写WriteString()值,建议使用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 %%") 写入输出(包含2个"100 %%"个字符),但只会发送一个,因为格式字符串%是一个特殊字符和%只会在输出中生成一个%%

如果您只想使用%包撰写string,请使用不需要格式为fmt的{​​{3}}:

string

使用fmt.Fprint()包的另一个好处是您也可以编写其他类型的值,而不仅仅是fmt.Fprint(w, "Hello") s,例如。

string

(当然,如何将任何值转换为fmt.Fprint(w, 23, time.Now()) - 以及最终的一系列字节的规则 - 已在string包的文档中明确定义。)

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

传递/移交fmt

为了完整起见,我们应该提一下,您希望作为网络响应发送的内容通常由"""生成。支持"流媒体"结果。一个示例可以是JSON响应,它是从结构或映射生成的。

在这种情况下,如果它支持将结果写入到http.ResponseWriter http.ResponseWriter这个某事,通常会更有效率一个io.Writer即时。

这方面的一个很好的例子是生成JSON响应。当然,您可以使用html/template将对象编组为JSON,它会返回一个字节切片,您只需通过调用io.Writer即可发送。

但是,让ResponseWriter.Write()包知道您拥有json更有效率,最终您希望将结果发送给它。这样就不必首先在缓冲区中生成JSON文本,然后将其写入响应中然后丢弃。您可以致电json.Marshal()创建新的json.Encoder,将io.Writer作为http.ResponseWriter传递给io.Writer,之后调用json.NewEncoder()将直接编写JSON结果你的回复作者。

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

答案 1 :(得分:6)

here(ResponseWriter)可以看出,它是io.WriteString方法的界面。

因此,在fmt.FprintfWrite(p []byte) (n int, err error)中,两者都将Writer作为第一个参数,这也是一个包裹type Writer interface { Write(p []byte) (n int, err error) } 方法的接口

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))
}

所以当你调用io.WriteString(w,“blah”)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
}

或fmt.Fprintf(w,“blabla”)check here

ResponseWriter

当你在两种方法中传递w.Write([]byte("blabla\n"))变量时,你只是间接地调用Write方法。

所以为什么不直接使用json.NewEncoder(w).Encode(wrapper) //Encode take interface as an argument. Wrapper can be: //wrapper := SuccessResponseWrapper{Success:true, Data:data} 来调用它。我希望你得到答案。

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

rethinking::map()