将StreamReader返回到他的BaseStream具有BOM时开始

时间:2011-06-24 12:33:15

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

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

可以使用任何编码创建原始StreamReader,并将detectEncodingFromByteOrderMarks设置为true或false。此外,可以在事先调用重置之前进行读取。

Stream可以是随机文本,以字节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个测试的解决方案(参见我自己的答案),但我想看看是否有更优雅或更快速的解决方案。

2 个答案:

答案 0 :(得分: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);
    }

答案 1 :(得分:0)

这可以解决问题,而无需创建新的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);
    }