io.Reader无限循环与fmt.Fscan

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

io.Reader endless loop with fmt.Fscan

问题

不确定为什么,但我的io.Reader实现似乎有些问题?

io.Reader的文档说明返回非零字节数和非nil错误应该是可以的:

它可以在同一次调用中返回(非nil)错误,也可以在后续调用中返回错误(且n == 0)。这种一般情况的一个实例是,返回输入流末尾的非零字节数的Reader可以返回err == EOF或err == nil。下一次读取应该返回0,EOF。

调用者在考虑错误err之前应始终处理返回的n > 0字节。这样做可以正确处理在读取一些字节后发生的I/O错误,以及两种允许的EOF行为。

但是对于fmt.Fscan来说,这并不起作用,而是导致程序挂起:

package main

import (
	"fmt"
	"io"
)

type Byte byte

func (b Byte) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, io.ErrShortBuffer
	}
	p[0] = byte(b)
	return 1, io.EOF
}

func main() {
	var n int
	b := Byte('9')
	z, err := fmt.Fscan(b, &n)
	fmt.Println(n, z, err)
}

当然,如果我改为使用io.EOF单独返回零字节计数,它就能正常工作:

type Byte struct {
	v   byte
	eof bool
}

func (b *Byte) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, io.ErrShortBuffer
	}
	if b.eof {
		return 0, io.EOF
	}
	p[0] = b.v
	b.eof = true
	return 1, nil
}

func main() {
	var n int
	b := Byte{v: '9'}
	z, err := fmt.Fscan(&b, &n)
	fmt.Println(n, z, err)
}

我的原始实现有缺陷吗?还是说我应该不依赖于io.Reader的这个特定的文档行为,而是在没有更多数据可读时始终单独返回0, io.EOF

英文:

Not sure why, but my io.Reader implementation appears to be flawed somehow?

The docs for io.Reader state that returning a non-zero byte count and a non-nil error should be ok:

> It may return the (non-nil) error from the same call or return the error (and n == 0) from a subsequent call. An instance of this general case is that a Reader returning a non-zero number of bytes at the end of the input stream may return either err == EOF or err == nil. The next Read should return 0, EOF.
>
> Callers should always process the n > 0 bytes returned before considering the error err. Doing so correctly handles I/O errors that happen after reading some bytes and also both of the allowed EOF behaviors.

But this doesn't work for fmt.Fscan, instead hanging the program:

package main

import (
	"fmt"
	"io"
)

type Byte byte

func (b Byte) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, io.ErrShortBuffer
	}
	p[0] = byte(b)
	return 1, io.EOF
}

func main() {
	var n int
	b := Byte('9')
	z, err := fmt.Fscan(b, &n)
	fmt.Println(n, z, err)
}

Of course, it works if i instead return a zero byte count with io.EOF separately:

type Byte struct {
	v   byte
	eof bool
}

func (b *Byte) Read(p []byte) (n int, err error) {
	if len(p) == 0 {
		return 0, io.ErrShortBuffer
	}
	if b.eof {
		return 0, io.EOF
	}
	p[0] = b.v
	b.eof = true
	return 1, nil
}

func main() {
	var n int
	b := Byte{v: '9'}
	z, err := fmt.Fscan(&b, &n)
	fmt.Println(n, z, err)
}

Is there a flaw in my original implementation, or should i simply not depend on this particular bit of documented behavior of io.Reader and always separately return 0, io.EOF when there is no more data to read?

答案1

得分: 3

fmt.Scanf确实能正确处理返回计数和io.EOF,但是你的读取器在io.EOF之后仍然返回值。

因为扫描器的实现依赖于使用io.ReadFull,而io.ReadFull又使用了io.ReadAtLeast,所以你需要一个更完整的实现来处理重复读取。你可以通过使用跟踪EOF的扩展版本,并在第一次Read时返回io.EOF来进行测试,它仍然可以按预期与fmt.Fscan一起工作。

以下是文档中的关键摘录:

io.ReadFull
> ...它不会将来自Read的EOF视为要报告的错误

io.ReadAtLeast
> 只有在没有读取任何字节时,错误才是EOF。

因为这些io辅助函数需要自己解释io.EOF,它们的调用者只能查找实际返回的数据,而你的读取器继续返回数据,它们将无限重复调用。通过在你的读取器上重复调用io.ReadAll可以很容易地证明这一点,每次调用都会返回另一个值。

b := Byte('9')
fmt.Println(io.ReadAll(b))
fmt.Println(io.ReadAll(b))
fmt.Println(io.ReadAll(b))

// [57] <nil>
// [57] <nil>
// [57] <nil>

https://go.dev/play/p/It_4BWLsSP8

英文:

fmt.Scanf does handle returning the count and io.EOF correctly, but your reader continues to return values even after io.EOF.

Because the scanner implementation relies on using io.ReadFull, which uses io.ReadAtLeast, you are going need a more complete implementation to work with repeated reads. You can test this by taking your extended version which tracks EOF, and return io.EOF on the first Read and it will still work with fmt.Fscan as expected.

Key excerpts from the docs:

io.ReadFull:
> ... it does not treat an EOF from Read as an error to be reported

io.ReadAtLeast:
> The error is EOF only if no bytes were read.

Because these io helpers need to interpret io.EOF themselves, their callers can only look for the actual data returned, and since your reader continues to return data, they will repeat the call indefinitely. This is easily demonstrated by calling io.ReadAll repeatedly on your reader, which returns another value every time.

b := Byte(&#39;9&#39;)
fmt.Println(io.ReadAll(b))
fmt.Println(io.ReadAll(b))
fmt.Println(io.ReadAll(b))

// [57] &lt;nil&gt;
// [57] &lt;nil&gt;
// [57] &lt;nil&gt;

https://go.dev/play/p/It_4BWLsSP8

huangapple
  • 本文由 发表于 2023年5月10日 03:53:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/76212940.html
匿名

发表评论

匿名网友

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

确定