10

一个例子(这可能不是现实生活,但要说明我的观点):

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的一个假想扩展方法。这可能吗?

当然,我可以自己数数,但这不是问题。

4

7 回答 7

27

我在寻找类似问题的解决方案时遇到了这篇文章,我需要在特定行中寻找 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", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
    readonly static FieldInfo byteLenField = typeof(StreamReader).GetField("byteLen", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
    readonly static FieldInfo charBufferField = typeof(StreamReader).GetField("charBuffer", BindingFlags.NonPublic | 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. 虽然我使用各种 Systems.Text.Encoding 选项进行了一些简单的测试,但我使用的几乎所有数据都是简单的文本文件(ASCII)
  2. 我只使用过该StreamReader.ReadLine()方法,虽然对 StreamReader 源的简要回顾似乎表明在使用其他读取方法时这仍然有效,但我还没有真正测试过这种情况。
于 2014-04-09T23:10:09.767 回答
11

不,真的不可能。“行号”的概念是基于已经读取的实际数据,而不仅仅是位置。例如,如果您要将读取器 Seek() 到任意位置,它实际上不会读取该数据,因此它无法确定行号。

做到这一点的唯一方法是自己跟踪它。

于 2009-05-06T13:36:34.240 回答
8

为任何 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++;
        }
    }
}

缺点(为简洁起见):

  1. 不检查构造函数参数是否为 null
  2. 不识别终止行的替代方法。读取由原始 \r 或 \n 分隔的文件时,将与 ReadLine() 行为不一致。
  3. 不覆盖“块”级方法,如 Read(char[], int, int)、ReadBlock、ReadLine、ReadToEnd。TextReader 实现工作正常,因为它将其他所有内容路由到 Read(); 但是,可以通过以下方式获得更好的性能
    • 通过将调用路由到 _inner 来覆盖这些方法。而不是基地。
    • 将读取的字符传递给 AdvancePosition。请参阅示例 ReadBlock 实现:

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;
}
于 2012-01-16T07:18:53.233 回答
5

不。

考虑使用底层流对象(可以在任何行的任何点)寻找任何位置是可能的。现在考虑一下这会对 StreamReader 保存的任何计数产生什么影响。

StreamReader 是否应该去找出它现在在哪一行?无论文件中的位置如何,它是否应该只读取多行?

恕我直言,除了这些之外,还有更多的问题会使这成为实施的噩梦。

于 2009-05-06T13:37:57.547 回答
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 编码文件的支持)。

于 2010-10-06T07:59:35.970 回答
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属性。

于 2014-02-18T23:00:24.570 回答
1

已经针对 BaseStream 提出的观点是有效且重要的。但是,在某些情况下,您想阅读文本并知道您在文本中的位置。将其编写为一个类以使其易于重用仍然很有用。

我现在试着写这样一个类。它似乎工作正常,但速度相当慢。当性能不重要时应该没问题(它不是那么慢,见下文)。

无论您是一次读取一个字符、一次读取一个缓冲区还是一次读取一行,我都使用相同的逻辑来跟踪文本中的位置。虽然我确信通过放弃它可以使其执行得更好,但它更容易实现......并且我希望遵循代码。

我对StreamReader的ReadLine方法(我相信这是这个实现的最薄弱点)做了一个非常基础的性能比较,差异几乎是一个数量级。我使用我的 StreamReaderEx 类获得了 22 MB/s,但直接使用 StreamReader 的速度几乎是 9 倍(在我配备 SSD 的笔记本电脑上)。虽然这可能很有趣,但我不知道如何进行正确的阅读测试;也许使用 2 个相同的文件,每个文件都大于磁盘缓冲区,并交替读取它们..?至少我的简单测试在我多次运行时会产生一致的结果,无论哪个类首先读取测试文件。

NewLine 符号默认为 Environment.NewLine 但可以设置为长度为 1 或 2 的任何字符串。读者仅将此符号视为换行符,这可能是一个缺点。至少我知道 Visual Studio 已经多次提示我打开的文件“有不一致的换行符”。

请注意,我没有包括 Guard 类;这是一个简单的实用程序类,从上下文中应该知道如何替换它。您甚至可以删除它,但您会丢失一些参数检查,因此生成的代码将远离“正确”。例如,Guard.NotNull(s, "s") 只是检查 s 是否为空,如果是这种情况,则抛出 ArgumentNullException(参数名称为“s”,因此是第二个参数)。

废话不多说,代码如下:

公共类 StreamReaderEx : StreamReader
{
    // 换行符(魔法值 -1:“未使用”)。
    诠释新线1,新线2;

    // 读取的最后一个字符是 NewLine 符号的第一个字符,并且我们使用的是两个字符的符号。
    bool insideNewLine;

    // StringBuilder 用于 ReadLine 实现。
    StringBuilder lineBuilder = new StringBuilder();


    public StreamReaderEx(string path, string newLine = "\r\n") : base(path)
    {
        初始化(新线);
    }


    public StreamReaderEx(Stream s, string newLine = "\r\n") : base(s)
    {
        初始化(新线);
    }


    公共字符串换行符
    {
        得到 { 返回 "" + (char)newLine1 + (char)newLine2; }
        私人集
        {
            Guard.NotNull(value, "value");
            Guard.Range(value.Length, 1, 2, "仅支持 1 到 2 个字符的换行符。");

            newLine1 = 值[0];
            newLine2 = (value.Length == 2 ? value[1] : -1);
        }
    }


    公共 int LineNumber { 获取;私人套装;}
    公共 int LinePosition { 获取;私人套装;}


    公共覆盖 int Read()
    {
        int next = base.Read();
        trackTextPosition(下一个);
        下一个返回;
    }


    public override int Read(char[] buffer, int index, int count)
    {
        int n = base.Read(缓冲区,索引,计数);
        对于 (int i = 0; 我
于 2012-01-23T13:04:18.250 回答