1

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

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

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

我见过其他解决方案,但是当 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 个测试,但我想看看是否有可能更优雅或更快速的解决方案。

4

2 回答 2

2
    //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);
    }
于 2011-06-24T12:34:05.410 回答
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);
    }
于 2018-11-08T12:13:31.407 回答