跟踪流式阅读器的行的位置

Posted

技术标签:

【中文标题】跟踪流式阅读器的行的位置【英文标题】:Tracking the position of the line of a streamreader 【发布时间】:2012-04-28 16:14:45 【问题描述】:

我需要跟踪从流阅读器读取的行的位置。当我说reader.ReadLine() 时,我需要知道该行在文件中的位置,然后我还希望能够从我之前跟踪的位置读取文件。

这可能吗?

【问题讨论】:

你需要这个做什么,文件有多大,... 【参考方案1】:

您可以通过以下三种方式之一:

1) 编写您自己的 StreamReader。这是一个很好的起点:How to know position(linenumber) of a streamreader in a textfile?

2) StreamReader 类有两个非常重要的私有变量,称为 charPos 和 charLen,它们在定位实际“读取”位置时需要,而不仅仅是流的底层位置。您可以使用反射来获取建议的值here

Int32 charpos = (Int32) s.GetType().InvokeMember("charPos", 
BindingFlags.DeclaredOnly | 
BindingFlags.Public | BindingFlags.NonPublic | 
BindingFlags.Instance | BindingFlags.GetField
 ,null, s, null); 

Int32 charlen= (Int32) s.GetType().InvokeMember("charLen", 
BindingFlags.DeclaredOnly | 
BindingFlags.Public | BindingFlags.NonPublic | 
BindingFlags.Instance | BindingFlags.GetField
 ,null, s, null);

return (Int32)s.BaseStream.Position-charlen+charpos;

3) 只需将整个文件读入字符串数组即可。像这样的:

char[] CRLF = new char[2]  '\n', '\r' ;
TextReader tr = File.OpenText("some path to file");
string[] fileLines = tr.ReadToEnd().Split(CRLF);

另一种可能性(与#3 相同的行)是读取行并将行存储在数组中。当你想读取前一行时,只需使用数组即可。

【讨论】:

感谢您的回复,但我已经尝试了解决方案#2,但我认为这并没有给我当前使用 streamreader.ReadLine() 方法读取的当前位置/行。还有有没有办法从指定的行/位置开始读取文件?提前致谢。 @johnnie - 可能通过设置 BaseStream.Position = N 并使用 StreamReader.Read()。我认为保留自己的缓存行集合会更好,这样您就可以逐行重新读取任何行,而无需返回文件本身。但是,这可能会导致大文件出现内存问题。 当 1024*N 边界将扩展字符分成 2 块时,您确定选项 2) 是安全的吗? 选项 #2 是我使用的,但代码错误。我目前使用的是这里:***.com/a/17457085/530545 选项 2 在使用不会为字符到字节创建 1 对 1 编码的编码时无法正常工作。在此处查看我的解决方案:***.com/a/22975649/718033(编辑:我的解决方案似乎与格兰杰的非常相似)【参考方案2】:

跟踪 StreamReader 的实际位置(以字节为单位):

readonly static FieldInfo charPosField = typeof(StreamReader).GetField("charPos", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
readonly static FieldInfo charLenField = typeof(StreamReader).GetField("charLen", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);
readonly static FieldInfo charBufferField = typeof(StreamReader).GetField("charBuffer", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);

static long ActualPosition(StreamReader reader)

    var charBuffer = (char[])charBufferField.GetValue(reader);
    var charLen = (int)charLenField.GetValue(reader);
    var charPos = (int)charPosField.GetValue(reader);

    return reader.BaseStream.Position - reader.CurrentEncoding.GetByteCount(charBuffer, charPos, charLen-charPos);

不客气。

【讨论】:

对于 .NET Core,所有字段名都有下划线前缀,见source.dot.net/#System.Private.CoreLib/…【参考方案3】:

也许这对你有帮助

  public class StreamLineReader : IDisposable
    
        const int BufferLength = 1024;

        Stream _Base;
        int _Read = 0, _Index = 0;
        byte[] _Bff = new byte[BufferLength];

        long _CurrentPosition = 0;
        int _CurrentLine = 0;

        /// <summary>
        /// CurrentLine number
        /// </summary>
        public long CurrentPosition  get  return _CurrentPosition;  
        /// <summary>
        /// CurrentLine number
        /// </summary>
        public int CurrentLine  get  return _CurrentLine;  
        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="stream">Stream</param>
        public StreamLineReader(Stream stream)  _Base = stream; 
        /// <summary>
        /// Count lines and goto line number
        /// </summary>
        /// <param name="goToLine">Goto Line number</param>
        /// <returns>Return true if goTo sucessfully</returns>
        public bool GoToLine(int goToLine)  return IGetCount(goToLine, true) == goToLine; 
        /// <summary>
        /// Count lines and goto line number
        /// </summary>
        /// <param name="goToLine">Goto Line number</param>
        /// <returns>Return the Count of lines</returns>
        public int GetCount(int goToLine)  return IGetCount(goToLine, false); 
        /// <summary>
        /// Internal method for goto&Count
        /// </summary>
        /// <param name="goToLine">Goto Line number</param>
        /// <param name="stopWhenLine">Stop when found the selected line number</param>
        /// <returns>Return the Count of lines</returns>
        int IGetCount(int goToLine, bool stopWhenLine)
        
            _Base.Seek(0, SeekOrigin.Begin);
            _CurrentPosition = 0;
            _CurrentLine = 0;
            _Index = 0;
            _Read = 0;

            long savePosition = _Base.Length;

            do
            
                if (_CurrentLine == goToLine)
                
                    savePosition = _CurrentPosition;
                    if (stopWhenLine) return _CurrentLine;
                
            
            while (ReadLine() != null);

            // GoToPosition

            int count = _CurrentLine;

            _CurrentLine = goToLine;
            _Base.Seek(savePosition, SeekOrigin.Begin);

            return count;
        
        /// <summary>
        /// Read Line
        /// </summary>
        /// <returns></returns>
        public string ReadLine()
        
            bool found = false;

            StringBuilder sb = new StringBuilder();
            while (!found)
            
                if (_Read <= 0)
                
                    // Read next block
                    _Index = 0;
                    _Read = _Base.Read(_Bff, 0, BufferLength);
                    if (_Read == 0)
                    
                        if (sb.Length > 0) break;
                        return null;
                    
                

                for (int max = _Index + _Read; _Index < max; )
                
                    char ch = (char)_Bff[_Index];
                    _Read--; _Index++;
                    _CurrentPosition++;

                    if (ch == '\0' || ch == '\n')
                    
                        found = true;
                        break;
                    
                    else if (ch == '\r') continue;
                    else sb.Append(ch);
                
            

            _CurrentLine++;
            return sb.ToString();
        
        /// <summary>
        /// Free resources
        /// </summary>
        public void Dispose()
        
            if (_Base != null)
            
                _Base.Close();
                _Base.Dispose();
                _Base = null;
            
        
    

用途:

 using (StreamLineReader st = new StreamLineReader(File.OpenRead("E:\\log.txt")))
        
            bool ok = st.GoToLine(1);
            int count= st.GetCount(0);

            string w0 = st.ReadLine();
            string w1 = st.ReadLine();
            string w2 = st.ReadLine();
            string w3 = st.ReadLine();
        

【讨论】:

【参考方案4】:

您可以使用的另一种模式是在您想要更改位置时使用新的StreamReader。这应该适用于任何版本的 .net,并且不需要太多代码。

using var stream = File.Open("file.txt");

using (var reader = new StreamReader(stream, Encoding.UTF8, true, 4096, leaveOpen: true) 
    reader.ReadLine();


stream.Seek(0, SeekLocation.Begin);

using (var reader = new StreamReader(stream, Encoding.UTF8, true, 4096, leaveOpen: true) 
    // read the same line again
    reader.ReadLine();

【讨论】:

以上是关于跟踪流式阅读器的行的位置的主要内容,如果未能解决你的问题,请参考以下文章

shell脚本修改文本中匹配行之前的行的方法

read_csv() 中的 S3 阅读器是先将文件下载到磁盘还是使用流式传输?

CSV阅读器(Python)中的“行包含NULL字节”

译SQLskills SQL101:Trace FlagsERRORLOGUpdate Statistics

如何在 Flutter 中自动滚动到 SingleChildScrollView 内的行的位置

JAVA电子邮件跟踪像素跟踪阅读时间