您可以使用streamreader读取普通文本文件,然后在保存当前位置后关闭流程读取器,然后再次打开streamreader并从该poision开始读取吗?
如果不是我可以使用什么来完成相同的情况而不锁定文件?
类似的东西:
var fs = File.Open(@"C:\testfile.txt", FileMode.Open, FileAccess.Read);
var sr = new StreamReader(fs);
Debug.WriteLine(sr.ReadLine());//Prints:firstline
var pos = fs.Position;
while (!sr.EndOfStream)
{
Debug.WriteLine(sr.ReadLine());
}
fs.Seek(pos, SeekOrigin.Begin);
Debug.WriteLine(sr.ReadLine());//Prints Nothing, i expect it to print SecondLine.
@lasseespeholt
这是我试过的代码
var position = -1;
StreamReaderSE sr = new StreamReaderSE(@"c:\testfile.txt");
Debug.WriteLine(sr.ReadLine());
position = sr.BytesRead;
Debug.WriteLine(sr.ReadLine());
Debug.WriteLine(sr.ReadLine());
Debug.WriteLine(sr.ReadLine());
Debug.WriteLine(sr.ReadLine());
Debug.WriteLine("Wait");
sr.BaseStream.Seek(position, SeekOrigin.Begin);
Debug.WriteLine(sr.ReadLine());
答案 0 :(得分:21)
我意识到这确实是迟来的,但我只是偶然发现了StreamReader
这个令人难以置信的缺陷;使用StreamReader
时无法可靠搜索的事实。就个人而言,我的具体需求是能够读取字符,但如果满足某个条件则“备份”;这是我正在解析的文件格式之一的副作用。
使用ReadLine()
不是一种选择,因为它仅适用于非常简单的解析作业。我必须支持可配置的记录/行分隔符序列并支持转义分隔符序列。另外,我不想实现自己的缓冲区,所以我可以支持“备份”和转义序列;这应该是StreamReader
的工作。
此方法按需计算基础字节流中的实际位置。它适用于UTF8,UTF-16LE,UTF-16BE,UTF-32LE,UTF-32BE和任何单字节编码(例如代码页1252,437,28591等),无论是否存在前同步码/ BOM。此版本不适用于UTF-7,Shift-JIS或其他可变字节编码。
当我需要寻找基础流中的任意位置时,我直接设置BaseStream.Position
,然后调用DiscardBufferedData()
让StreamReader
重新同步,以便下一个Read()
} / Peek()
来电。
友情提醒:不要随意设置BaseStream.Position
。如果你将一个字符平分,你将使下一个Read()
无效,对于UTF-16 / -32,你也会使这个方法的结果无效。
public static long GetActualPosition(StreamReader reader)
{
System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetField;
// The current buffer of decoded characters
char[] charBuffer = (char[])reader.GetType().InvokeMember("charBuffer", flags, null, reader, null);
// The index of the next char to be read from charBuffer
int charPos = (int)reader.GetType().InvokeMember("charPos", flags, null, reader, null);
// The number of decoded chars presently used in charBuffer
int charLen = (int)reader.GetType().InvokeMember("charLen", flags, null, reader, null);
// The current buffer of read bytes (byteBuffer.Length = 1024; this is critical).
byte[] byteBuffer = (byte[])reader.GetType().InvokeMember("byteBuffer", flags, null, reader, null);
// The number of bytes read while advancing reader.BaseStream.Position to (re)fill charBuffer
int byteLen = (int)reader.GetType().InvokeMember("byteLen", flags, null, reader, null);
// The number of bytes the remaining chars use in the original encoding.
int numBytesLeft = reader.CurrentEncoding.GetByteCount(charBuffer, charPos, charLen - charPos);
// For variable-byte encodings, deal with partial chars at the end of the buffer
int numFragments = 0;
if (byteLen > 0 && !reader.CurrentEncoding.IsSingleByte)
{
if (reader.CurrentEncoding.CodePage == 65001) // UTF-8
{
byte byteCountMask = 0;
while ((byteBuffer[byteLen - numFragments - 1] >> 6) == 2) // if the byte is "10xx xxxx", it's a continuation-byte
byteCountMask |= (byte)(1 << ++numFragments); // count bytes & build the "complete char" mask
if ((byteBuffer[byteLen - numFragments - 1] >> 6) == 3) // if the byte is "11xx xxxx", it starts a multi-byte char.
byteCountMask |= (byte)(1 << ++numFragments); // count bytes & build the "complete char" mask
// see if we found as many bytes as the leading-byte says to expect
if (numFragments > 1 && ((byteBuffer[byteLen - numFragments] >> 7 - numFragments) == byteCountMask))
numFragments = 0; // no partial-char in the byte-buffer to account for
}
else if (reader.CurrentEncoding.CodePage == 1200) // UTF-16LE
{
if (byteBuffer[byteLen - 1] >= 0xd8) // high-surrogate
numFragments = 2; // account for the partial character
}
else if (reader.CurrentEncoding.CodePage == 1201) // UTF-16BE
{
if (byteBuffer[byteLen - 2] >= 0xd8) // high-surrogate
numFragments = 2; // account for the partial character
}
}
return reader.BaseStream.Position - numBytesLeft - numFragments;
}
当然,这使用Reflection来获取私有变量,因此存在风险。但是,此方法适用于.Net 2.0,3.0,3.5,4.0,4.0.3,4.5,4.5.1,4.5.2,4.6和4.6.1。除了这个风险之外,唯一的另一个关键假设是底层字节缓冲区是byte[1024]
;如果Microsoft以错误的方式更改它,则该方法会中断为UTF-16 / -32。
已针对填充了Ažテ
(10字节:0x41 C5 BE E3 83 86 F0 A3 98 BA
)的UTF-8文件和填充A
的UTF-16文件(6字节:{{1 }})。关键在于沿着0x41 00 01 D8 37 DC
边界强制分割字符,所有这些都是不同的方式。
更新(2013-07-03):我修复了最初使用其他答案中已损坏代码的方法。此版本已针对包含需要使用代理项对的字符的数据进行了测试。将数据放入3个文件中,每个文件具有不同的编码;一个UTF-8,一个UTF-16LE和一个UTF-16BE。
UPDATE(2016-02):处理二等分字符的唯一正确方法是直接解释基础字节。 UTF-8处理得当,UTF-16 / -32工作(给定byteBuffer的长度)。
答案 1 :(得分:14)
是的,你可以,看到这个:
var sr = new StreamReader("test.txt");
sr.BaseStream.Seek(2, SeekOrigin.Begin); // Check sr.BaseStream.CanSeek first
更新的
请注意,您不一定要将sr.BaseStream.Position
用于任何有用的内容,因为StreamReader
使用缓冲区,因此它不会反映您实际读取的内容。我想你会在找到真正的位置时遇到问题。因为你不能只统计字符(不同的编码,因此字符长度)。我认为最好的方法是与FileStream
自己合作。
<强>更新强>
从这里使用TGREER.myStreamReader
:
http://www.daniweb.com/software-development/csharp/threads/35078
此类添加BytesRead
等(与ReadLine()
一起使用,但显然不与其他读取方法一起使用)
然后你可以这样做:
File.WriteAllText("test.txt", "1234\n56789");
long position = -1;
using (var sr = new myStreamReader("test.txt"))
{
Console.WriteLine(sr.ReadLine());
position = sr.BytesRead;
}
Console.WriteLine("Wait");
using (var sr = new myStreamReader("test.txt"))
{
sr.BaseStream.Seek(position, SeekOrigin.Begin);
Console.WriteLine(sr.ReadToEnd());
}
答案 2 :(得分:1)
来自MSDN:
StreamReader专为角色而设计 输入特定的编码, 而Stream类是设计的 用于字节输入和输出。使用 StreamReader用于读取行 来自标准文本文件的信息。
在大多数涉及StreamReader
的示例中,您将看到使用ReadLine()逐行读取。 Seek方法来自Stream
类,它主要用于读取或处理字节数据。
答案 3 :(得分:1)
如果您只想在文本流中搜索起始位置,我将此扩展添加到StreamReader,以便我可以确定应该在哪里进行流编辑。当然,这是基于字符作为逻辑的递增方面,但就我的目的而言,它很有效,可以根据字符串模式获取基于文本/ ASCII的文件中的位置。然后,您可以使用该位置作为阅读的起点,编写一个新文件,在起点之前排除数据。
可以将流中返回的位置提供给Seek,以便从基于文本的流读取中的该位置开始。有用。我测试过了。但是,在匹配算法期间匹配非ASCII Unicode字符时可能会出现问题。这是基于美国英语和相关的角色页面。
基础知识:它逐字符地扫描文本流,仅通过流向前查找顺序字符串模式(与字符串参数匹配)。一旦模式与字符串参数不匹配(即前进,char by char),它将重新开始(从当前位置)尝试获得匹配,char-by-char。如果在流中找不到匹配,它最终将退出。如果找到匹配,那么它将返回流中的当前“字符”位置,而不是StreamReader.BaseStream.Position,因为该位置在前面,基于StreamReader的缓冲。
如注释中所示,此方法将影响StreamReader的位置,并将在方法结束时将其设置回开头(0)。应使用StreamReader.BaseStream.Seek运行此扩展返回的位置。
注意:此扩展程序返回的位置也可以在处理文本文件时使用BinaryReader.Seek作为起始位置。实际上,在丢弃PJL头信息以使文件成为可由GhostScript使用的“正确”PostScript可读文件之后,我实际上使用此逻辑将PostScript文件重写回磁盘。 :)
在PostScript中搜索的字符串(在PJL标题之后)是:“%!PS-”,后跟“Adobe”和版本。
public static class StreamReaderExtension
{
/// <summary>
/// Searches from the beginning of the stream for the indicated
/// <paramref name="pattern"/>. Once found, returns the position within the stream
/// that the pattern begins at.
/// </summary>
/// <param name="pattern">The <c>string</c> pattern to search for in the stream.</param>
/// <returns>If <paramref name="pattern"/> is found in the stream, then the start position
/// within the stream of the pattern; otherwise, -1.</returns>
/// <remarks>Please note: this method will change the current stream position of this instance of
/// <see cref="System.IO.StreamReader"/>. When it completes, the position of the reader will
/// be set to 0.</remarks>
public static long FindSeekPosition(this StreamReader reader, string pattern)
{
if (!string.IsNullOrEmpty(pattern) && reader.BaseStream.CanSeek)
{
try
{
reader.BaseStream.Position = 0;
reader.DiscardBufferedData();
StringBuilder buff = new StringBuilder();
long start = 0;
long charCount = 0;
List<char> matches = new List<char>(pattern.ToCharArray());
bool startFound = false;
while (!reader.EndOfStream)
{
char chr = (char)reader.Read();
if (chr == matches[0] && !startFound)
{
startFound = true;
start = charCount;
}
if (startFound && matches.Contains(chr))
{
buff.Append(chr);
if (buff.Length == pattern.Length
&& buff.ToString() == pattern)
{
return start;
}
bool reset = false;
if (buff.Length > pattern.Length)
{
reset = true;
}
else
{
string subStr = pattern.Substring(0, buff.Length);
if (buff.ToString() != subStr)
{
reset = true;
}
}
if (reset)
{
buff.Length = 0;
startFound = false;
start = 0;
}
}
charCount++;
}
}
finally
{
reader.BaseStream.Position = 0;
reader.DiscardBufferedData();
}
}
return -1;
}
}
答案 4 :(得分:0)
如果您可以确定文本文件中如何处理换行符,则可以根据行长度和行尾字符添加读取的字节数。
File.WriteAllText("test.txt", "1234" + System.Environment.NewLine + "56789");
long position = -1;
long bytesRead = 0;
int newLineBytes = System.Environment.NewLine.Length;
using (var sr = new StreamReader("test.txt"))
{
string line = sr.ReadLine();
bytesRead += line.Length + newLineBytes;
Console.WriteLine(line);
position = bytesRead;
}
Console.WriteLine("Wait");
using (var sr = new StreamReader("test.txt"))
{
sr.BaseStream.Seek(position, SeekOrigin.Begin);
Console.WriteLine(sr.ReadToEnd());
}
对于更复杂的文本文件编码,您可能需要比这更好,但它对我有用。