【问题标题】:Reading files with a BOM in Go在 Go 中使用 BOM 读取文件
【发布时间】:2014-02-17 17:59:00
【问题描述】:

我需要读取可能包含或不包含字节顺序标记的 Unicode 文件。我当然可以自己检查文件的前几个字节,如果找到一个 BOM,就丢弃它。但在此之前,是否有任何标准方法可以做到这一点,无论是在核心库中还是在第三方中?

【问题讨论】:

    标签: unicode go byte-order-mark


    【解决方案1】:

    没有标准的方式,IIRC(标准库确实是一个错误的层来实现这样的签入)所以这里有两个例子来说明你如何自己处理它。

    一种是在数据流上方使用缓冲读取器:

    import (
        "bufio"
        "os"
        "log"
    )
    
    func main() {
        fd, err := os.Open("filename")
        if err != nil {
            log.Fatal(err)
        }
        defer closeOrDie(fd)
        br := bufio.NewReader(fd)
        r, _, err := br.ReadRune()
        if err != nil {
            log.Fatal(err)
        }
        if r != '\uFEFF' {
            br.UnreadRune() // Not a BOM -- put the rune back
        }
        // Now work with br as you would do with fd
        // ...
    }
    

    另一种适用于实现io.Seeker 接口的对象的方法是读取前三个字节,如果它们不是BOM,则io.Seek() 回到开头,例如:

    import (
        "os"
        "log"
    )
    
    func main() {
        fd, err := os.Open("filename")
        if err != nil {
            log.Fatal(err)
        }
        defer closeOrDie(fd)
        bom := [3]byte
        _, err = io.ReadFull(fd, bom[:])
        if err != nil {
            log.Fatal(err)
        }
        if bom[0] != 0xef || bom[1] != 0xbb || bom[2] != 0xbf {
            _, err = fd.Seek(0, 0) // Not a BOM -- seek back to the beginning
            if err != nil {
                log.Fatal(err)
            }
        }
        // The next read operation on fd will read real data
        // ...
    }
    

    这是可能的,因为 *os.File 的实例(os.Open() 返回的内容)支持查找并因此实现 io.Seeker。请注意,对于 HTTP 响应的Body 读者来说,情况并非如此,因为您无法“倒带”它。 bufio.Buffer 通过执行一些缓冲(显然)来解决不可搜索流的这一特性——这就是允许你使用 UnreadRune() 的原因。

    请注意,这两个示例都假设我们正在处理的文件是用 UTF-8 编码的。如果您需要处理其他(或未知)编码,事情会变得更加复杂。

    【讨论】:

    • bufio 方法奏效了,我喜欢它将 BOM 视为单个符文而不是一组字节。
    • @kostix cloud 您解释了如何在第二种方法中导出条件?谢谢
    • @Anuruddha,抱歉,我未能解析您的问题——尤其是“导出条件”部分,这似乎是必不可少的。需要详细说明吗?可能是几句话。
    • @kostix 你使用的条件 bom[0] != 0xef || bom[1] != 0xbb || bom[1] != 0xbf
    • @Anuruddha,不,UTF-8 编码的 BOM 是具有特定顺序的特定值的三个字节,因此逻辑是“如果序列中的任何字节具有值,则它不能在那个位置,那不是 BOM”。
    【解决方案2】:

    Go 核心包中没有执行此操作的标准方法。遵循 Unicode 标准。

    Unicode Byte Order Mark (BOM) FAQ

    【讨论】:

    • 如果我自己生成所有文件和流,我当然会严格遵守 Unicode 标准。但像世界上的许多人一样,我被其他人生成的数据卡住了。
    【解决方案3】:

    您可以使用utfbom 包。它包装io.Reader,根据需要检测并丢弃 BOM。也可以返回BOM检测到的编码。

    【讨论】:

      【解决方案4】:

      我想我会在这里添加从 stringstrip Byte Order Mark 序列的方法——而不是乱用字节直接(如上图)。

      package main
      
      import (
          "fmt"
          "strings"
      )
      
      func main() {
          s := "\uFEFF is a string that starts with a Byte Order Mark"
          fmt.Printf("before: '%v' (len=%v)\n", s, len(s))
      
          ByteOrderMarkAsString := string('\uFEFF')
      
          if strings.HasPrefix(s, ByteOrderMarkAsString) {
      
              fmt.Printf("Found leading Byte Order Mark sequence!\n")
              
              s = strings.TrimPrefix(s, ByteOrderMarkAsString)
          }
          fmt.Printf("after: '%v' (len=%v)\n", s, len(s)) 
      }
      

      其他“字符串”函数也应该可以工作。

      这是打印出来的:

      before: ' is a string that starts with a Byte Order Mark (len=50)'
      Found leading Byte Order Mark sequence!
      after: ' is a string that starts with a Byte Order Mark (len=47)'
      

      干杯!

      【讨论】:

        猜你喜欢
        • 2011-08-22
        • 2014-03-04
        • 2012-11-15
        • 1970-01-01
        • 1970-01-01
        • 2012-02-04
        • 1970-01-01
        相关资源
        最近更新 更多