【问题标题】:Return StreamReader to Beginning when his BaseStream has BOM当他的 BaseStream 有 BOM 时,将 StreamReader 返回到开始
【发布时间】:2018-11-08 21:02:11
【问题描述】:

我正在寻找一种可靠的方法来将 StreamReader 重置为开始,特别是当他的底层 BaseStream 以 BOM 开头时,但在没有 BOM 时也必须工作。创建一个从流的开头读取的新 StreamReader 也是可以接受的。

可以使用任何编码创建原始 StreamReader,并将 detectEncodingFromByteOrderMarks 设置为 true 或 false。此外,在调用重置之前,可能已经完成或未完成读取。

流可以是随机文本,以字节 0xef,0xbb,0xbf 开头的文件可以是具有 BOM 的文件或以有效字符序列开头的文件(例如  如果使用 ISO-8859-1 编码),取决于创建 StreamReader 时使用的参数。

我见过other solutions,但是当 BaseStream 以 BOM 开头时,它们无法正常工作。 StreamReader 记得它已经检测到 BOM,并且执行读取时返回的第一个字符是特殊 BOM 字符。

我也可以创建一个新的 StreamReader,但我不知道原始 StreamReader 是在 detectEncodingFromByteOrderMarks 设置为 true 还是设置为 false 的情况下创建的。

这是我首先尝试的:

    //fails with TestMethod1
    void ResetStream1(ref StreamReader sr) {
        sr.BaseStream.Position = 0;
        sr.DiscardBufferedData();
    }

    //fails with TestMethod2
    void ResetStream2(ref StreamReader sr) {
        sr.BaseStream.Position = 0;
        sr = new StreamReader(sr.BaseStream, sr.CurrentEncoding, true);
    }

    //fails with TestMethod3
    void ResetStream3(ref StreamReader sr) {
        sr.BaseStream.Position = 0;
        sr = new StreamReader(sr.BaseStream, sr.CurrentEncoding, false);
    }

这些是最好的方法:

    Stream StreamWithBOM = new MemoryStream(new byte[] {0xef,0xbb,0xbf,(byte)'X'});


    [TestMethod]
    public void TestMethod1() {
        StreamReader sr=new StreamReader(StreamWithBOM);
        int before=sr.Read(); //reads X

        ResetStream(ref sr);
        int after=sr.Read();

        Assert.AreEqual(before, after);
    }

    [TestMethod]
    public void TestMethod2() {
        StreamReader sr = new StreamReader(StreamWithBOM,Encoding.GetEncoding("ISO-8859-1"),false);
        int before = sr.Read(); //reads ï

        ResetStream(ref sr);
        int after = sr.Read();

        Assert.AreEqual(before, after);
    }

    [TestMethod]
    public void TestMethod3() {
        StreamReader sr = new StreamReader(StreamWithBOM, Encoding.GetEncoding("ISO-8859-1"), true);
        int expected = (int)'X'; //no Read() done before reset

        ResetStream(ref sr);
        int after = sr.Read();

        Assert.AreEqual(expected, after);
    }

最后,我找到了一个解决方案(见我自己的答案),它通过了所有 3 个测试,但我想看看是否有可能更优雅或更快的解决方案。

【问题讨论】:

    标签: .net stream reset streamreader byte-order-mark


    【解决方案1】:
        //pass all 3 tests
        void ResetStream(ref StreamReader sr){
            sr.Read(); //ensure that BOM is detected if configured to do so
            sr.BaseStream.Position=0;
            sr=new StreamReader(sr.BaseStream, sr.CurrentEncoding, false);
        }
    

    【讨论】:

    • 可以,但不要太频繁。
    • 这里的 False 有什么作用?和@HansPassant 为什么不呢?
    【解决方案2】:

    无需创建新的 StreamReader 即可解决问题:

      void ResetStream(StreamReader sr)
      {
          sr.BaseStream.Position = sr.CurrentEncoding.GetPreamble().Length;
          sr.DiscardBufferedData();
      }
    

    如果没有 BOM,GetPreamble() 将返回一个空字节数组。

    这应该在有或没有 BOM 的情况下工作,因为 UTF8Encoding 类(以及其他,例如 UTF32Encoding、UnicodeEncoding)有一个内部字段,用于跟踪是否包含 BOM,并且在您第一次执行 Read() 时由 StreamReader 设置。

    但是,您似乎需要在关闭 BOM 标识符标志的情况下将 Encoding 传递给 StreamReader 构造函数,然后它将正确检测 BOM 的存在。如果您只是简单地将流作为唯一参数传递,如上面的 TestMethod1 中所示,那么出于某种原因,即使您的流没有 BOM,它也会将 CurrentEncoding 设置为带有 BOM 的 UTF8。将 detectEncodingFromByteOrderMarks 设置为 true 也无济于事,因为它默认为 true。

    下面的测试都通过了,因为 UTF8Encoding 的默认设置是关闭 BOM。

        Stream StreamWithBOM = new MemoryStream(new byte[] { 0xef, 0xbb, 0xbf, (byte)'X' });
        Stream StreamWithoutBOM = new MemoryStream(new byte[] { (byte)'X' });
    
        [TestMethod]
        public void TestMethod4()
        {
            StreamReader sr = new StreamReader(StreamWithBOM, new UTF8Encoding());
            int before = sr.Read(); //reads X
    
            ResetStream(sr);
            int after = sr.Read();
    
            Assert.AreEqual(before, after);
        }
    
        [TestMethod]
        public void TestMethod5()
        {
            StreamReader sr = new StreamReader(StreamWithoutBOM, new UTF8Encoding());
            int before = sr.Read(); //reads X
    
            ResetStream(sr);
            int after = sr.Read();
    
            Assert.AreEqual(before, after);
        }
    

    【讨论】:

      猜你喜欢
      • 2011-01-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-08
      • 1970-01-01
      • 1970-01-01
      • 2013-06-16
      相关资源
      最近更新 更多