【问题标题】:How to be definite about the number of whitespace fmt.Fscanf consumes?如何确定 fmt.Fscanf 消耗的空白数量?
【发布时间】:2013-04-05 18:44:53
【问题描述】:

我正在尝试在 Go 中实现 PPM 解码器。 PPM 是一种图像格式,由纯文本标题和一些二进制图像数据组成。标头看起来像这样(来自spec):

每个 PPM 图像包含以下内容:

  1. 用于识别文件类型的“幻数”。 ppm 图像的幻数是两个字符“P6”。
  2. 空格(空格、TAB、CR、LF)。
  3. 宽度,格式为十进制的 ASCII 字符。
  4. 空白。
  5. 高度,同样是 ASCII 十进制。
  6. 空白。
  7. 最大颜色值 (Maxval),同样以 ASCII 十进制表示。必须小于 65536 且大于零。
  8. 单个空白字符(通常是换行符)。

我尝试使用fmt.Fscanf 函数解码此标头。以下调用 fmt.Fscanf 解析标题(不解决下面解释的警告):

var magic string
var width, height, maxVal uint

fmt.Fscanf(input,"%2s %d %d %d",&magic,&width,&height,&maxVal)

fmtdocumentation 声明:

注意:Fscan 等可以在输入后读取一个字符(符文) return,这意味着调用扫描例程的循环可能会跳过一些 的输入。这通常只有在没有空间时才会出现问题 输入值之间。如果读者提供给Fscan 实现 ReadRune,该方法将用于读取字符。如果读者 还实现了UnreadRune,该方法将用于保存 字符和连续调用不会丢失数据。附上ReadRuneUnreadRune 方法给没有这种能力的读者,使用 bufio.NewReader.

由于最后一个空格之后的下一个字符已经是图像数据的开头,所以我必须确定在阅读MaxVal 后消耗了多少空格fmt.Fscanf。我的代码必须在调用者提供的任何阅读器上工作,并且它的部分内容不得超过标头的末尾,因此将内容包装到缓冲阅读器中不是一种选择;缓冲阅读器从输入中读取的内容可能比我实际想要阅读的内容更多。

一些测试表明,最后解析一个虚拟字符可以解决问题:

var magic string
var width, height, maxVal uint
var dummy byte

fmt.Fscanf(input,"%2s %d %d %d%c",&magic,&width,&height,&maxVal,&dummy)

这能保证按照规范工作吗?

【问题讨论】:

    标签: go scanf off-by-one


    【解决方案1】:

    不,我认为这不安全。虽然它现在可以工作,但文档声明该函数保留将值读取一个字符的权利,除非您有 UnreadRune() 方法。

    通过将您的阅读器包装在bufio.Reader 中,您可以确保阅读器具有UnreadRune() 方法。然后,您需要自己阅读最后的空白。

    buf := bufio.NewReader(input)
    fmt.Fscanf(buf,"%2s %d %d %d",&magic,&width,&height,&maxVal)
    buf.ReadRune() // remove next rune (the whitespace) from the buffer.
    


    编辑:

    正如我们在聊天中所讨论的,您可以假设 dummy char 方法有效,然后编写一个测试,以便您知道它何时停止工作。测试可以是这样的:

    func TestFmtBehavior(t *testing.T) {
        // use multireader to prevent r from implementing io.RuneScanner
        r := io.MultiReader(bytes.NewReader([]byte("data  ")))
    
        n, err := fmt.Fscanf(r, "%s%c", new(string), new(byte))
        if n != 2 || err != nil {
            t.Error("failed scan", n, err)
        }
    
        // the dummy char read 1 extra char past "data".
        // one byte should still remain
        if n, err := r.Read(make([]byte, 5)); n != 1 {
            t.Error("assertion failed", n, err)
        }
    }
    

    【讨论】:

    • 如果我将阅读器包装到 bufio.Reader 中,我能否确定从 底层阅读器 消耗的输入不会超过必要的输入?我无法控制我的功能需要什么样的阅读器,但我必须确保我不会消耗太多。
    • 不,您无法控制从底层阅读器读取的内容。获得阅读器后立即将其包装在缓冲区中可能是个好主意。然后只考虑底层阅读器的缓冲区。
    • 这并没有给我任何控制权。我仍然不能确定输入消耗了多少字节。如果输入是一个网络流,我的程序可能会以这种方式停止,因为它期望更多的数据然后另一端发送?如果我输入流包含我需要通过其他方式读出的 PPM 文件之后的数据怎么办?
    • 输入后最多只消耗一个字节。
    • 然后呢?如何确定这个字节是否被消耗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-04
    • 1970-01-01
    • 1970-01-01
    • 2010-09-06
    • 1970-01-01
    • 2014-03-03
    • 1970-01-01
    相关资源
    最近更新 更多