io.Write应该返回写入的字节数和错误信息。

huangapple go评论69阅读模式
英文:

What should io.Write returns?

问题

我正在学习Go语言并进行一些测试,当我在代码中发现这种奇怪的行为时。我创建了两种类型来展示我的发现,并在两者上实现了io.Write接口。这个小程序下载网页的内容并将其打印到控制台。

BrokenConsoleWriter通过fmt.Println跟踪写入的字节数和可能的错误,并返回它。另一方面,ConsoleWriter简单地忽略fmt.Println的返回值,并返回切片的总长度以及错误的nil值。

当我运行程序时,BrokenConsoleWriter没有打印出整个HTML内容,而ConsoleWriter却打印了。为什么会发生这种情况?

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

type ConsoleWriter struct{}
type BrokenConsoleWriter struct{}

func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
	bytesWritten, error := fmt.Println(string(p))
	return bytesWritten, error
}

func (c ConsoleWriter) Write(p []byte) (n int, err error) {
	fmt.Println(string(p))
	return len(p), nil
}

func main() {
	const url = "https://www.nytimes.com/"
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("Some error occurred:", err)
		os.Exit(1)
	}

	//io.Copy(ConsoleWriter{}, resp.Body)
	io.Copy(BrokenConsoleWriter{}, resp.Body)
}
英文:

I was learning go and doing some tests with the language when I found this weird behavior in my code. I create two types to demonstrate my findings and implemented the interface io.Write on both. This little program downloads the content of a web page and prints it to the console.
The BrokenConsoleWriter keeps track of the bytes written by fmt.Println and any error it may "throw" and returns it. On the other hand, the ConsoleWriter simply ignore the return of fmt.Println and returns the total length of the slice and nil for the error.

When I run the program, the BrokenConsoleWriter doesn't print the entire html content while that ConsoleWriter does. Why is this happening?

package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

type ConsoleWriter struct{}
type BrokenConsoleWriter struct{}

func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
	bytesWritten, error := fmt.Println(string(p))
	return bytesWritten, error
}

func (c ConsoleWriter) Write(p []byte) (n int, err error) {
	fmt.Println(string(p))
	return len(p), nil
}

func main() {
	const url = "https://www.nytimes.com/"
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("Some error occurred:", err)
		os.Exit(1)
	}

	//io.Copy(ConsoleWriter{}, resp.Body)
	io.Copy(BrokenConsoleWriter{}, resp.Body)
}

答案1

得分: 4

Write()方法是实现io.Write()的,该方法的文档说明如下:

> Write方法从p中将len(p)个字节写入到底层数据流中。**它返回从p中写入的字节数(0 <= n <= len(p))**以及导致写入提前停止的任何错误。如果返回的n < len(p),Write方法必须返回非nil的错误。Write方法不能修改切片数据,即使是临时修改。
>
> 实现不得保留p。

因此,你的Write()方法必须报告你从传递给你的p切片中处理了多少字节。而不是报告你生成了多少字节给其他源。

这就是你的BrokenConsoleWriter.Write()实现的错误之处:你没有报告你从p中处理了多少字节,而是报告了fmt.Prinln()实际写入的字节数。而且,由于fmt.Prinln()在打印其参数后还会打印一个换行符,它返回的值肯定不适用于BrokenConsoleWriter.Write()

请注意,fmt.Prinln()如果只有一个string参数,它会将该字符串写出并附加一个换行符,在Unix系统上是一个字符\n,在Windows上是\r\n。因此,在Unix系统上,如果从其返回值中减去1,你也可以得到正确的行为:

func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
    bytesWritten, error := fmt.Println(string(p))
    return bytesWritten - 1, error
}

还要注意,输入已经被格式化为行,所以通过使用fmt.Prinln()来随机插入换行符可能会导致无效的文档。请改用fmt.Print()而不是fmt.Println()

func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
    bytesWritten, error := fmt.Print(string(p))
    return bytesWritten, error
}

但是,这种解决方案的正确性仍取决于fmt.Print()的实现。正确的解决方案是报告len(p),因为这就是发生的情况:你处理了输入切片的len(p)个字节(全部)。

英文:

The Write() method is to implement io.Write() which documents that:

> Write writes len(p) bytes from p to the underlying data stream. It returns the number of bytes written from p (0 <= n <= len(p)) and any error encountered that caused the write to stop early. Write must return a non-nil error if it returns n < len(p). Write must not modify the slice data, even temporarily.
>
> Implementations must not retain p.

So your Write() method must report how many bytes you processed from the p slice passed to you. Not how many bytes you generate to some other source.

And this is the error with your BrokenConsoleWriter.Write() implementation: you don't report how many bytes you process from p, you report how many bytes fmt.Prinln() actually writes. And since fmt.Prinln() also prints a newline after printing its arguments, the value it returns will surely be not valid for BrokenConsoleWriter.Write().

Note that fmt.Prinln() with a single string argument will write out that string and append a newline, which on unix systems is a single character \n, and \r\n on Windows. So on unix systems you also get a correct behavior if you subtract 1 from its return value:

func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
    bytesWritten, error := fmt.Println(string(p))
    return bytesWritten - 1, error
}

Also note that the input is already formatted into lines, so you inserting newlines "randomly" by using fmt.Prinln() may even result in an invalid document. Do use fmt.Print() instead of fmt.Println():

func (b BrokenConsoleWriter) Write(p []byte) (n int, err error) {
	bytesWritten, error := fmt.Print(string(p))
	return bytesWritten, error
}

But the correctness of this solution still depends on the implementation of fmt.Print(). The correct solution is to report len(p) because that's what happened: you processed len(p) bytes of the input slice (all of it).

huangapple
  • 本文由 发表于 2022年8月10日 01:24:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/73295838.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定