C# 如何知道文本文件中streamreader的位置(行号)?
举个例子(这可能不是真实的生活,但要说明我的观点):C# 如何知道文本文件中streamreader的位置(行号)?,c#,streamreader,C#,Streamreader,举个例子(这可能不是真实的生活,但要说明我的观点): GetLinePosition这里是streamreader的一个虚拟扩展方法。 这可能吗 当然我可以自己数数,但这不是问题。不,不太可能。“行号”的概念是基于已经读取的实际数据,而不仅仅是位置。例如,如果要将读取器搜索到任意位置,它实际上不会读取该数据,因此无法确定行号 要做到这一点,唯一的办法就是自己跟踪它。不 考虑到可以使用底层流对象(可能在任何行的任何点)查找任何位置。 现在考虑对流读取器保持的计数会有什么影响。< /P> Strea
GetLinePosition
这里是streamreader的一个虚拟扩展方法。
这可能吗
当然我可以自己数数,但这不是问题。不,不太可能。“行号”的概念是基于已经读取的实际数据,而不仅仅是位置。例如,如果要将读取器搜索到任意位置,它实际上不会读取该数据,因此无法确定行号 要做到这一点,唯一的办法就是自己跟踪它。不 考虑到可以使用底层流对象(可能在任何行的任何点)查找任何位置。 现在考虑对流读取器保持的计数会有什么影响。< /P> StreamReader应该去弄清楚它现在在哪一行吗? 它是否应该只读取多行,而不考虑文件中的位置
除了这些问题,还有更多的问题要解决,imho。这里有一个人用ReadLine()方法实现了一个StreamReader,该方法可以注册文件位置 我想应该从StreamReader继承,然后将额外的方法添加到特殊类以及一些属性(_lineLength+_bytesRead):
//读取一行。一行定义为一系列字符,后跟
//回车符('\r')、换行符('\n')或回车符
//紧接着是换行。结果字符串不存在
//包含终止回车和/或换行符。返回
//如果已到达输入流的结尾,则值为null。
//
///
公共重写字符串读取行()
{
_线宽=0;
//if(流==null)
//__错误。ReaderClosed();
if(charPos==charLen)
{
if(ReadBuffer()==0)返回null;
}
StringBuilder sb=null;
做
{
int i=charPos;
做
{
char ch=charBuffer[i];
int-EolChars=0;
如果(ch='\r'| | ch='\n')
{
EolChars=1;
字符串s;
如果(sb!=null)
{
sb.Append(charBuffer,charPos,i-charPos);
s=sb.ToString();
}
其他的
{
s=新字符串(charBuffer、charPos、i-charPos);
}
charPos=i+1;
if(ch='\r'&&(charPos0))
{
if(charBuffer[charPos]='\n')
{
charPos++;
EolChars=2;
}
}
_lineLength=s.长度+EolChars;
_字节读取=_字节读取+_行长度;
返回s;
}
i++;
}而(i0);
字符串ss=sb.ToString();
_lineLength=不锈钢长度;
_字节读取=_字节读取+_行长度;
返回ss;
}
认为代码中存在一个小错误,因为字符串的长度用于计算文件位置,而不是使用实际读取的字节(不支持UTF8和UTF16编码的文件)。为任何文本阅读器提供行计数包装器非常容易:
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++;
}
}
}
缺点(为了简洁起见):
- 通过将调用路由到_inner来重写这些方法。而不是基地
- 将读取的字符传递到高级位置。请参见ReadBlock实现示例:
public override int ReadBlock(char[]缓冲区、int索引、int计数){
var readCount=_inner.ReadBlock(缓冲区、索引、计数);
对于(int i=0;i
已经针对基流提出的观点是有效且重要的。但是,在某些情况下,您需要阅读文本并知道自己在文本中的位置。将其作为一个类编写以便于重用仍然是有用的
我现在试着写这样一门课。它似乎工作正常,但速度相当慢。当性能不重要时(速度没有那么慢,请参见下文),它应该很好
我使用相同的逻辑来跟踪文本中的位置,不管您是一次读取一个字符、一次读取一个缓冲区还是一次读取一行。虽然我相信这可以
// 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;
}
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;
}
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
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();
}
}
CountingReader reader = new CountingReader(file.OpenRead())
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);
}
}