我正在寻找一种可靠的方法来重置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个测试的解决方案(参见我自己的答案),但我想看看是否有更优雅或更快速的解决方案。
答案 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);
}