كيفية معرفة موقف(وهذا) من streamreader في ملف نصي?

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. لا تحقق منشئ حجة فارغة
  2. لا تعترف طرق بديلة لإنهاء خطوط.سوف تكون متناسقة مع ReadLine() السلوك عند قراءة ملفات مفصولة الخام أو .
  3. لا يلغي "بلوك"على مستوى أساليب مثل قراءة(char[], int, int) ، ReadBlock, ريدلاين ، ReadToEnd.TextReader تنفيذ يعمل بشكل صحيح حيث أنه طرق كل شيء آخر إلى قراءة () ؛ بيد أن أداء أفضل يمكن تحقيقه من خلال
    • تجاوز تلك الأساليب عن طريق توجيه المكالمات إلى _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;
}

نصائح أخرى

أنا كام عبر هذا المنصب في حين تبحث عن حل مشكلة مشابهة حيث كنت في حاجة للحصول 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. بينما كنت قد فعلت بعض الاختبارات البسيطة باستخدام النظام المختلفة.النص.خيارات الترميز ، تقريبا جميع البيانات تستهلك هذه هي ملفات نصية بسيطة (ASCII).
  2. أنا فقط من أي وقت مضى استخدام StreamReader.ReadLine() طريقة وبينما استعراض موجز مصدر StreamReader يبدو أنها تشير إلى هذا سوف لا تزال تعمل عند استخدام الآخر قراءة أساليب انا لم اختبر هذا السيناريو.

لا، ليس من الممكن حقا. ويستند مفهوم "رقم السطر" بناء على البيانات الفعلية وهذا ما تم بالفعل قراءة، وليس فقط الموقف. على سبيل المثال، إذا كنت لالتماس () القارئ إلى وضع التعسفي، انها ليست actuall الذهاب لقراءة تلك البيانات، لذلك لن يكون قادرا على تحديد رقم السطر.

والطريقة الوحيدة للقيام بذلك هي لتتبع ذلك بنفسك.

لا.

ونعتبر أن ذلك من الممكن أن يسعى إلى أي poisition باستخدام الكائن تيار الأساسي (التي يمكن أن تكون في أي وقت من أي خط). لنتأمل الآن ما من شأنه أن تفعل أي عدد تحتفظ بها StreamReader.

وإذا ما StreamReader ذهاب ومعرفة أي خط انها الآن فصاعدا؟ يجب أن تبقي فقط على عدد من خطوط قراءة، بغض النظر عن موقف داخل الملف؟

وهناك أسئلة أكثر من مجرد هذه التي من شأنها أن تجعل من هذا الكابوس لتنفيذ، إيمهو.

وهنا هو الرجل الذي نفذ 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 () ولا يهتمون باستخدام التماس () أو أي شيء، فقط جعل فئة فرعية بسيطة من 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، والفرق هو تقريبا لحجم. حصلت على 22 ميغا بايت / ثانية باستخدام صفي StreamReaderEx، ولكن ما يقرب من 9 مرات كما تستخدم كثيرا StreamReader مباشرة (على جهاز الكمبيوتر المحمول مجهزة SSD). في حين أنها يمكن أن تكون مثيرة للاهتمام، وأنا لا أعرف كيف لإجراء اختبار القراءة الصحيحة. ربما باستخدام 2 الملفات متطابقة، كل أكبر من المخزن المؤقت القرص، وقراءتها بالتناوب ..؟ ما لا يقل عن بلدي اختبار بسيط ينتج نتائج متسقة عند تشغيل عدة مرات، وبغض النظر عن الفئة التي يقرأ ملف اختبار أولا.

ويمكن ضبط الإعدادات الافتراضية رمز السطر الجديد لEnvironment.NewLine لكن إلى أي سلسلة من طول 1 أو 2. يعتبر القارئ فقط هذا الرمز كما سطر جديد، والذي قد يكون العيب. على الأقل أعرف البصرية ستوديو وقد دفعني عدد لا بأس به من الأوقات أن ملف I فتح "لديها أسطر جديدة تتعارض".

تجدر الإشارة بأنني غير المدرجة الطبقة الحرس. هذه هي فئة أداة بسيطة وينبغي أن يكون obvoius من السياق كيف ليحل محله. حتى يمكنك إزالته، ولكن كنت تفقد بعض التحقق من حجة وبالتالي الرمز الناتج سيكون أبعد من "صحيحة". على سبيل المثال، Guard.NotNull (ق "ق") ببساطة يتحقق ذلك الصورة ليست لاغية، ورمي في ArgumentNullException (مع اسم حجة "ليالي"، وبالتالي المعلمة الثانية) ينبغي أن يكون عليه الحال.

وكفى الثرثرة، وهنا رمز:

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