一个例子(可能不是现实生活,但要说明我的观点):
public void StreamInfo(StreamReader p)
{
string info = string.Format(
"The supplied streamreaer read : {0}\n at line {1}",
p.ReadLine(),
p.GetLinePosition()-1);
}
GetLinePosition
这里是streamreader的虚构扩展方法。
这可能吗?
当然,我可以自己计算,但这不是问题。
答案 0 :(得分:21)
我正在寻找解决类似问题的解决方案,我需要寻找特定行的StreamReader。我最终创建了两个扩展方法来获取和设置StreamReader上的位置。它实际上并没有提供行号计数,但实际上,我只是抓住每个ReadLine()之前的位置,如果该行感兴趣,那么我保留起始位置以便稍后设置回到这样的行:
var index = streamReader.GetPosition();
var line1 = streamReader.ReadLine();
streamReader.SetPosition(index);
var line2 = streamReader.ReadLine();
Assert.AreEqual(line1, line2);
和重要的部分:
public static class StreamReaderExtensions
{
readonly static FieldInfo charPosField = typeof(StreamReader).GetField("charPos", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | BindingFlags.DeclaredOnly);
readonly static FieldInfo byteLenField = typeof(StreamReader).GetField("byteLen", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | BindingFlags.DeclaredOnly);
readonly static FieldInfo charBufferField = typeof(StreamReader).GetField("charBuffer", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | BindingFlags.DeclaredOnly);
public static long GetPosition(this StreamReader reader)
{
//shift position back from BaseStream.Position by the number of bytes read
//into internal buffer.
int byteLen = (int)byteLenField.GetValue(reader);
var position = reader.BaseStream.Position - byteLen;
//if we have consumed chars from the buffer we need to calculate how many
//bytes they represent in the current encoding and add that to the position.
int charPos = (int)charPosField.GetValue(reader);
if (charPos > 0)
{
var charBuffer = (char[])charBufferField.GetValue(reader);
var encoding = reader.CurrentEncoding;
var bytesConsumed = encoding.GetBytes(charBuffer, 0, charPos).Length;
position += bytesConsumed;
}
return position;
}
public static void SetPosition(this StreamReader reader, long position)
{
reader.DiscardBufferedData();
reader.BaseStream.Seek(position, SeekOrigin.Begin);
}
}
这对我来说效果很好,取决于你使用反射的容忍度它认为这是一个相当简单的解决方案。
注意事项:
答案 1 :(得分:11)
不,不太可能。 “行号”的概念基于已经读取的实际数据,而不仅仅是位置。例如,如果您要将读者Seek()置于任意位置,那么它不会实际读取该数据,因此无法确定行号。
这样做的唯一方法就是自己跟踪它。
答案 2 :(得分:7)
为任何TextReader提供行计数包装非常容易:
public class PositioningReader : TextReader {
private TextReader _inner;
public PositioningReader(TextReader inner) {
_inner = inner;
}
public override void Close() {
_inner.Close();
}
public override int Peek() {
return _inner.Peek();
}
public override int Read() {
var c = _inner.Read();
if (c >= 0)
AdvancePosition((Char)c);
return c;
}
private int _linePos = 0;
public int LinePos { get { return _linePos; } }
private int _charPos = 0;
public int CharPos { get { return _charPos; } }
private int _matched = 0;
private void AdvancePosition(Char c) {
if (Environment.NewLine[_matched] == c) {
_matched++;
if (_matched == Environment.NewLine.Length) {
_linePos++;
_charPos = 0;
_matched = 0;
}
}
else {
_matched = 0;
_charPos++;
}
}
}
缺点(为了简洁起见):
public override int ReadBlock(char[] buffer, int index, int count) {
var readCount = _inner.ReadBlock(buffer, index, count);
for (int i = 0; i < readCount; i++)
AdvancePosition(buffer[index + i]);
return readCount;
}
答案 3 :(得分:5)
没有
考虑使用底层流对象(可以在任何行中的任何点)寻找任何poisition都是可能的。 现在考虑一下StreamReader保留的任何计数会做什么。
StreamReader应该去找出它现在在哪条线上? 它是否应该只读取多行,而不管文件中的位置是什么?
除了这些问题之外,还有更多的问题会让这成为一个噩梦,imho。
答案 4 :(得分:3)
这是一个用ReadLine()方法实现StreamReader的人,它注册文件位置。
http://www.daniweb.com/forums/thread35078.html
我想应该从StreamReader继承,然后将额外的方法添加到特殊类以及一些属性(_lineLength + _bytesRead):
// Reads a line. A line is defined as a sequence of characters followed by
// a carriage return ('\r'), a line feed ('\n'), or a carriage return
// immediately followed by a line feed. The resulting string does not
// contain the terminating carriage return and/or line feed. The returned
// value is null if the end of the input stream has been reached.
//
/// <include file='doc\myStreamReader.uex' path='docs/doc[@for="myStreamReader.ReadLine"]/*' />
public override String ReadLine()
{
_lineLength = 0;
//if (stream == null)
// __Error.ReaderClosed();
if (charPos == charLen)
{
if (ReadBuffer() == 0) return null;
}
StringBuilder sb = null;
do
{
int i = charPos;
do
{
char ch = charBuffer[i];
int EolChars = 0;
if (ch == '\r' || ch == '\n')
{
EolChars = 1;
String s;
if (sb != null)
{
sb.Append(charBuffer, charPos, i - charPos);
s = sb.ToString();
}
else
{
s = new String(charBuffer, charPos, i - charPos);
}
charPos = i + 1;
if (ch == '\r' && (charPos < charLen || ReadBuffer() > 0))
{
if (charBuffer[charPos] == '\n')
{
charPos++;
EolChars = 2;
}
}
_lineLength = s.Length + EolChars;
_bytesRead = _bytesRead + _lineLength;
return s;
}
i++;
} while (i < charLen);
i = charLen - charPos;
if (sb == null) sb = new StringBuilder(i + 80);
sb.Append(charBuffer, charPos, i);
} while (ReadBuffer() > 0);
string ss = sb.ToString();
_lineLength = ss.Length;
_bytesRead = _bytesRead + _lineLength;
return ss;
}
认为代码中存在一个小错误,因为字符串的长度用于计算文件位置而不是使用读取的实际字节数(缺少对UTF8和UTF16编码文件的支持)。
答案 5 :(得分:2)
我来这里寻找简单的东西。如果您只是使用ReadLine()并且不关心使用Seek()或其他任何东西,那么只需创建StreamReader的简单子类
class CountingReader : StreamReader {
private int _lineNumber = 0;
public int LineNumber { get { return _lineNumber; } }
public CountingReader(Stream stream) : base(stream) { }
public override string ReadLine() {
_lineNumber++;
return base.ReadLine();
}
}
然后你用正常的方式,比如来自名为file
的FileInfo对象CountingReader reader = new CountingReader(file.OpenRead())
您只需阅读reader.LineNumber
属性。
答案 6 :(得分:1)
已经针对BaseStream做出的分数是有效且重要的。但是,在某些情况下,您需要阅读文本并知道文本的位置。将它作为一个类写入以使其易于重用仍然是有用的。
我现在试着写这样一堂课。它似乎工作正常,但它相当慢。当性能不是至关重要时(它不是 慢,见下文)应该没问题。
我使用相同的逻辑来跟踪文本中的位置,无论您一次读取一个字符,一次读取一个缓冲区,还是一次读取一行。虽然我确信通过放弃这个可以让它更好地执行,但它使实现起来更容易......而且,我希望,遵循代码。
我对ReadLine方法进行了非常基本的性能比较(我认为这是此实现中最薄弱的一点)与StreamReader,差异几乎是一个数量级。我使用我的StreamReaderEx类获得了22 MB / s,但使用StreamReader直接使用了近9倍(在我配备SSD的笔记本电脑上)。虽然它可能很有趣,但我不知道如何进行正确的阅读测试;也许使用2个相同的文件,每个文件都大于磁盘缓冲区,并交替读取它们。至少我的简单测试在我多次运行时产生一致的结果,并且无论哪个类首先读取测试文件。
NewLine符号默认为Environment.NewLine,但可以设置为长度为1或2的任何字符串。阅读器仅将此符号视为换行符,这可能是一个缺点。至少我知道Visual Studio已经提示我很多次我打开的文件“有不一致的换行符”。
请注意,我没有加入Guard类;这是一个简单的实用程序类,它应该是从上下文如何替换它的obvoius。您甚至可以将其删除,但是您将丢失一些参数检查,因此生成的代码将远离“正确”。例如,Guard.NotNull(s,“s”)只是检查s是否为空,抛出ArgumentNullException(参数名称为“s”,因此是第二个参数),如果是这种情况。
足够的唠叨,这是代码:
public class StreamReaderEx : StreamReader { // NewLine characters (magic value -1: "not used"). int newLine1, newLine2; // The last character read was the first character of the NewLine symbol AND we are using a two-character symbol. bool insideNewLine; // StringBuilder used for ReadLine implementation. StringBuilder lineBuilder = new StringBuilder(); public StreamReaderEx(string path, string newLine = "\r\n") : base(path) { init(newLine); } public StreamReaderEx(Stream s, string newLine = "\r\n") : base(s) { init(newLine); } public string NewLine { get { return "" + (char)newLine1 + (char)newLine2; } private set { Guard.NotNull(value, "value"); Guard.Range(value.Length, 1, 2, "Only 1 to 2 character NewLine symbols are supported."); newLine1 = value[0]; newLine2 = (value.Length == 2 ? value[1] : -1); } } public int LineNumber { get; private set; } public int LinePosition { get; private set; } public override int Read() { int next = base.Read(); trackTextPosition(next); return next; } public override int Read(char[] buffer, int index, int count) { int n = base.Read(buffer, index, count); for (int i = 0; i