텍스트 파일에서 스트림리더의 위치(라인 번호)를 어떻게 알 수 있나요?

StackOverflow https://stackoverflow.com/questions/829568

  •  06-07-2019
  •  | 
  •  

문제

예(실제 생활이 아닐 수도 있지만 요점을 말하자면):

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의 가상 확장 방법이 있습니다.이것이 가능한가?

물론 나는 계속해서 셀 수 있지만 그것은 문제가 아닙니다.

도움이 되었습니까?

해결책

모든 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. 선을 종료하는 대체 방법을 인식하지 못합니다. raw r 또는 n으로 파일을 읽을 때 readline () 동작과 일치하지 않습니다.
  3. read (char [], int, int), readblock, readline, readtoend와 같은 "블록"-레벨 메소드를 무시하지 않습니다. TexTreader 구현은 다른 모든 것을 읽기 ()로 라우팅하므로 올바르게 작동합니다. 그러나 더 나은 성능을 달성 할 수 있습니다
    • _inner 로의 라우팅 호출을 통해 이러한 방법을 우선합니다. 베이스 대신.
    • 캐릭터를 전달하는 것은 AdvancePosition에 읽습니다. 샘플 리드 블록 구현을 참조하십시오.

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;
}

다른 팁

스트리어 리더를 특정 라인으로 찾아야하는 유사한 문제에 대한 해결책을 찾는 동안이 게시물을 캠핑합니다. 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. 다양한 system.text.encoding 옵션을 사용하여 간단한 테스트를 수행했지만 이와 함께 사용하는 모든 데이터는 간단한 텍스트 파일 (ASCII)입니다.
  2. StreamReader.Readline () 메소드 만 사용하고 StreamReader 소스에 대한 간단한 검토는 다른 읽기 메소드를 사용할 때 여전히 작동한다는 것을 나타내는 것처럼 보이지만 실제로 해당 시나리오를 테스트하지는 않았습니다.

아니요, 실제로는 불가능합니다. "줄 번호"의 개념은 위치가 아니라 이미 읽은 실제 데이터를 기반으로합니다. 예를 들어, 독자를 임의의 위치로 찾으려면 해당 데이터를 읽지 않기 때문에 줄 번호를 결정할 수 없습니다.

이를 수행하는 유일한 방법은 직접 추적하는 것입니다.

아니.

기본 스트림 객체 (어느 쪽의 어느 지점에서든있을 수 있음)를 사용하여 모든 포를 찾을 수 있다고 생각하십시오. 이제 StreamReader가 보관 한 카운트에 어떤 영향을 미치는지 고려하십시오.

StreamReader가 가서 현재 어떤 라인에 있는지 알아 내야합니까? 파일 내 위치에 관계없이 여러 줄을 읽어야합니까?

이것들보다 더 많은 질문이 있습니다.

다음은 파일 위치를 등록하는 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 인코딩 된 파일에 대한 지원이 부족함).

나는 간단한 것을 찾고 여기에 왔습니다. 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();
    }
}

그런 다음 파일이라는 FileInfo 객체에서 말하면 정상적인 방법으로 만듭니다.

CountingReader reader = new CountingReader(file.OpenRead())

그리고 당신은 방금 읽었습니다 reader.LineNumber 재산.

BaseStream과 관련하여 이미 제시된 사항은 유효하고 중요합니다.그러나 텍스트를 읽고 텍스트의 어디에 있는지 알고 싶은 상황이 있습니다.쉽게 재사용할 수 있도록 클래스로 작성하는 것이 여전히 유용할 수 있습니다.

나는 지금 그런 수업을 쓰려고 노력했습니다.올바르게 작동하는 것 같지만 다소 느립니다.성능이 중요하지 않을 때는 괜찮을 것입니다. 저것 천천히, 아래를 참조하세요).

한 번에 문자를 읽든, 한 번에 하나의 버퍼를 읽든, 한 번에 한 줄씩 읽든 상관없이 동일한 논리를 사용하여 텍스트의 위치를 ​​추적합니다.이것을 버리면 성능이 더 좋아질 수 있다고 확신하지만 구현하기가 훨씬 쉬워졌습니다 ...그리고 나는 그 코드를 따르기를 바랍니다.

나는 ReadLine 메서드(이 구현의 가장 약한 점이라고 생각함)와 StreamReader의 매우 기본적인 성능 비교를 수행했는데 그 차이는 거의 10배 정도였습니다.StreamReaderEx 클래스를 사용하여 22MB/s를 얻었지만 StreamReader를 직접 사용하면(SSD가 장착된 노트북에서) 거의 9배에 달합니다.흥미로울 수는 있지만 적절한 읽기 테스트를 수행하는 방법을 모르겠습니다.어쩌면 각각 디스크 버퍼보다 ​​큰 2개의 동일한 파일을 사용하여 교대로 읽을 수도 있습니다..?최소한 내 간단한 테스트는 어떤 클래스가 테스트 파일을 먼저 읽는지에 관계없이 여러 번 실행할 때 일관된 결과를 생성합니다.

NewLine 기호의 기본값은 Environment.NewLine이지만 길이가 1 또는 2인 문자열로 설정할 수 있습니다.독자는 이 기호만을 개행 문자로 간주하는데, 이는 단점이 될 수 있습니다.적어도 나는 Visual Studio에서 내가 여는 파일에 "일관되지 않은 줄 바꿈이 있습니다"라는 메시지가 상당히 많이 표시된다는 것을 알고 있습니다.

Guard 클래스는 포함하지 않았습니다.이는 간단한 유틸리티 클래스이므로 이를 대체하는 방법은 문맥상 명확해야 합니다.이를 제거할 수도 있지만 일부 인수 확인이 손실되므로 결과 코드는 "올바른" 코드와는 거리가 멀게 됩니다.예를 들어, Guard.NotNull(s, "s")는 s가 null이 아닌지 확인하고, null인 경우 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 
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top