web-dev-qa-db-ja.com

StreamReaderとシーク

streamreaderを使用して通常のテキストファイルを読み取り、読み取りの途中で現在の位置を保存した後でstreamreaderを閉じてから、streamreaderを再度開いて、その位置から読み取りを開始できますか?

そうでない場合は、ファイルをロックせずに同じケースを達成するために他に何を使用できますか?

このようなもの:

 var fs = File.Open(@"C:\testfile.txt", FileMode.Open, FileAccess.Read);
        var sr = new StreamReader(fs);
        Debug.WriteLine(sr.ReadLine());//Prints:firstline
        var pos = fs.Position;
        while (!sr.EndOfStream)
        {
            Debug.WriteLine(sr.ReadLine());
        }
        fs.Seek(pos, SeekOrigin.Begin);
        Debug.WriteLine(sr.ReadLine());//Prints Nothing, i expect it to print SecondLine.

@lasseespeholt

これが私が試したコードです

            var position = -1;
        StreamReaderSE sr = new StreamReaderSE(@"c:\testfile.txt");
        Debug.WriteLine(sr.ReadLine());
        position = sr.BytesRead;
        Debug.WriteLine(sr.ReadLine());
        Debug.WriteLine(sr.ReadLine());
        Debug.WriteLine(sr.ReadLine());
        Debug.WriteLine(sr.ReadLine());
        Debug.WriteLine("Wait");
        sr.BaseStream.Seek(position, SeekOrigin.Begin);
        Debug.WriteLine(sr.ReadLine());
12
Stacker

はい、できます。これを参照してください。

var sr = new StreamReader("test.txt");
sr.BaseStream.Seek(2, SeekOrigin.Begin); // Check sr.BaseStream.CanSeek first

更新:StreamReaderはバッファを使用するため、実際に読んだ内容が反映されないため、必ずしもsr.BaseStream.Positionを有用なものに使用できるとは限らないことに注意してください。 。本当の位置を見つけるのに問題があると思います。文字だけを数えることはできないからです(異なるエンコーディング、したがって文字の長さ)。 FileStream´s自身を操作するのが最善の方法だと思います。

更新:ここからTGREER.myStreamReaderを使用します: http://www.daniweb.com/software-development/csharp/ threads/35078 このクラスはBytesReadなどを追加し(ReadLine()で機能しますが、他の読み取りメソッドでは機能しないようです)、次のように実行できます。

File.WriteAllText("test.txt", "1234\n56789");

long position = -1;

using (var sr = new myStreamReader("test.txt"))
{
    Console.WriteLine(sr.ReadLine());

    position = sr.BytesRead;
}

Console.WriteLine("Wait");

using (var sr = new myStreamReader("test.txt"))
{
    sr.BaseStream.Seek(position, SeekOrigin.Begin);
    Console.WriteLine(sr.ReadToEnd());
}
14
Lasse Espeholt

これは本当に遅れていることに気づきましたが、私はStreamReaderのこの信じられないほどの欠陥に偶然出くわしました。 StreamReaderを使用するときに確実にシークできないという事実。個人的には、文字を読み取る機能が必要ですが、特定の条件が満たされた場合は「バックアップ」する必要があります。これは、私が解析しているファイル形式の1つの副作用です。

ReadLine()を使用することは、本当に些細な解析ジョブでのみ役立つため、オプションではありません。構成可能なレコード/行区切り文字シーケンスをサポートし、エスケープ区切り文字シーケンスをサポートする必要があります。また、「バックアップ」とエスケープシーケンスをサポートできるように、独自のバッファを実装したくありません。それはStreamReaderの仕事でなければなりません。

このメソッドは、基になるバイトストリームの実際の位置をオンデマンドで計算します。プリアンブル/ BOMの存在に関係なく、UTF8、UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE、および任意のシングルバイトエンコーディング(コードページ1252、437、28591など)で機能します。このバージョンは、UTF-7、Shift-JIS、またはその他の可変バイトエンコーディングでは機能しません。

基になるストリーム内の任意の位置をシークする必要がある場合は、直接_BaseStream.Position_を設定し、DiscardBufferedData()を呼び出して、次のRead()/Peek()呼び出し。

そして、フレンドリーなリマインダー:_BaseStream.Position_を任意に設定しないでください。文字を二等分すると、次のRead()が無効になり、UTF-16/-32の場合は、このメソッドの結果も無効になります。

_public static long GetActualPosition(StreamReader reader)
{
    System.Reflection.BindingFlags flags = System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetField;

    // The current buffer of decoded characters
    char[] charBuffer = (char[])reader.GetType().InvokeMember("charBuffer", flags, null, reader, null);

    // The index of the next char to be read from charBuffer
    int charPos = (int)reader.GetType().InvokeMember("charPos", flags, null, reader, null);

    // The number of decoded chars presently used in charBuffer
    int charLen = (int)reader.GetType().InvokeMember("charLen", flags, null, reader, null);

    // The current buffer of read bytes (byteBuffer.Length = 1024; this is critical).
    byte[] byteBuffer = (byte[])reader.GetType().InvokeMember("byteBuffer", flags, null, reader, null);

    // The number of bytes read while advancing reader.BaseStream.Position to (re)fill charBuffer
    int byteLen = (int)reader.GetType().InvokeMember("byteLen", flags, null, reader, null);

    // The number of bytes the remaining chars use in the original encoding.
    int numBytesLeft = reader.CurrentEncoding.GetByteCount(charBuffer, charPos, charLen - charPos);

    // For variable-byte encodings, deal with partial chars at the end of the buffer
    int numFragments = 0;
    if (byteLen > 0 && !reader.CurrentEncoding.IsSingleByte)
    {
        if (reader.CurrentEncoding.CodePage == 65001) // UTF-8
        {
            byte byteCountMask = 0;
            while ((byteBuffer[byteLen - numFragments - 1] >> 6) == 2) // if the byte is "10xx xxxx", it's a continuation-byte
                byteCountMask |= (byte)(1 << ++numFragments); // count bytes & build the "complete char" mask
            if ((byteBuffer[byteLen - numFragments - 1] >> 6) == 3) // if the byte is "11xx xxxx", it starts a multi-byte char.
                byteCountMask |= (byte)(1 << ++numFragments); // count bytes & build the "complete char" mask
            // see if we found as many bytes as the leading-byte says to expect
            if (numFragments > 1 && ((byteBuffer[byteLen - numFragments] >> 7 - numFragments) == byteCountMask))
                numFragments = 0; // no partial-char in the byte-buffer to account for
        }
        else if (reader.CurrentEncoding.CodePage == 1200) // UTF-16LE
        {
            if (byteBuffer[byteLen - 1] >= 0xd8) // high-surrogate
                numFragments = 2; // account for the partial character
        }
        else if (reader.CurrentEncoding.CodePage == 1201) // UTF-16BE
        {
            if (byteBuffer[byteLen - 2] >= 0xd8) // high-surrogate
                numFragments = 2; // account for the partial character
        }
    }
    return reader.BaseStream.Position - numBytesLeft - numFragments;
}
_

もちろん、これはリフレクションを使用してプライベート変数を取得するため、リスクが伴います。ただし、この方法は.Net 2.0、3.0、3.5、4.0、4.0.3、4.5、4.5.1、4.5.2、4.6、および4.6.1で機能します。そのリスクを超えて、他の唯一の重要な仮定は、基礎となるバイトバッファが_byte[1024]_であるということです。 Microsoftが間違った方法で変更すると、UTF-16/-32のメソッドが機能しなくなります。

これは、_Ažテ????_(10バイト:_0x41 C5 BE E3 83 86 F0 A3 98 BA_)で満たされたUTF-8ファイルおよび_A????_(6バイト:_0x41 00 01 D8 37 DC_)で満たされたUTF-16ファイルに対してテストされています。 。重要なのは、_byte[1024]_の境界に沿って文字を強制的に断片化することです。

PDATE(2013-07-03):他の回答の壊れたコードを元々使用していたメソッドを修正しました。このバージョンは、サロゲートペアの使用を必要とする文字を含むデータに対してテストされています。データは3つのファイルに入れられ、それぞれが異なるエンコーディングを使用していました。 1つのUTF-8、1つのUTF-16LE、および1つのUTF-16BE。

PDATE(2016-02):二等分された文字を処理する唯一の正しい方法は、基になるバイトを直接解釈することです。 UTF-8は適切に処理され、UTF-16/-32は機能します(byteBufferの長さを指定)。

22
Granger

MSDNから:

StreamReaderは特定のエンコーディングでの文字入力用に設計されていますが、Streamクラスはバイトの入力と出力用に設計されています。 StreamReaderを使用して、標準のテキストファイルから情報の行を読み取ります。

StreamReaderを含むほとんどの例では、ReadLine()を使用して行ごとに読み取っています。 Seekメソッドは、基本的にバイト単位のデータの読み取りまたは処理に使用されるStreamクラスから取得されます。

1
Abdel Raoof

テキストストリーム内の開始位置を検索するだけの場合は、この拡張機能をStreamReaderに追加して、ストリームの編集を行う場所を決定できるようにしました。確かに、これはロジックの増分の側面としての文字に基づいていますが、私の目的では、文字列パターンに基づいてテキスト/ ASCIIベースのファイル内の位置を取得するのに最適です。次に、その場所を読み取りの開始点として使用して、開始点の前のデータを非表示にする新しいファイルを書き込むことができます。

ストリーム内で返された位置をSeekに提供して、テキストベースのストリーム読み取り内のその位置から開始することができます。できます。私はそれをテストしました。ただし、マッチングアルゴリズム中に非ASCIIUnicode文字とマッチングする場合に問題が発生する可能性があります。これはアメリカ英語と関連するキャラクターページに基づいていました。

基本:テキストストリームを文字ごとにスキャンし、ストリームを介してのみ前方に連続する文字列パターン(文字列パラメーターに一致する)を探します。パターンが文字列パラメータと一致しない場合(つまり、文字ごとに)、一致を文字ごとに取得しようとして(現在の位置から)最初からやり直します。ストリームで一致するものが見つからない場合、最終的には終了します。一致するものが見つかった場合、StreamReaderが行うバッファリングに基づいて、StreamReader.BaseStream.Positionではなく、ストリーム内の現在の「文字」位置を返します。

コメントに示されているように、このメソッドはStreamReaderの位置に影響を与え、メソッドの最後で最初(0)に戻されます。 StreamReader.BaseStream.Seekを使用して、この拡張機能によって返される位置まで実行する必要があります。

注:この拡張機能によって返される位置は、テキストファイルを操作するときの開始位置としてBinaryReader.Seekでも機能します。 PJLヘッダー情報を破棄して、GhostScriptで使用できる「適切な」PostScript読み取り可能ファイルにした後、実際にこのロジックを使用してPostScriptファイルをディスクに書き戻しました。 :)

PostScript内で検索する文字列(PJLヘッダーの後)は「%!PS-」で、その後に「Adobe」とバージョンが続きます。

public static class StreamReaderExtension
{
    /// <summary>
    /// Searches from the beginning of the stream for the indicated
    /// <paramref name="pattern"/>. Once found, returns the position within the stream
    /// that the pattern begins at.
    /// </summary>
    /// <param name="pattern">The <c>string</c> pattern to search for in the stream.</param>
    /// <returns>If <paramref name="pattern"/> is found in the stream, then the start position
    /// within the stream of the pattern; otherwise, -1.</returns>
    /// <remarks>Please note: this method will change the current stream position of this instance of
    /// <see cref="System.IO.StreamReader"/>. When it completes, the position of the reader will
    /// be set to 0.</remarks>
    public static long FindSeekPosition(this StreamReader reader, string pattern)
    {
        if (!string.IsNullOrEmpty(pattern) && reader.BaseStream.CanSeek)
        {
            try
            {
                reader.BaseStream.Position = 0;
                reader.DiscardBufferedData();
                StringBuilder buff = new StringBuilder();
                long start = 0;
                long charCount = 0;
                List<char> matches = new List<char>(pattern.ToCharArray());
                bool startFound = false;

                while (!reader.EndOfStream)
                {
                    char chr = (char)reader.Read();

                    if (chr == matches[0] && !startFound)
                    {
                        startFound = true;
                        start = charCount;
                    }

                    if (startFound && matches.Contains(chr))
                    {
                        buff.Append(chr);

                        if (buff.Length == pattern.Length
                            && buff.ToString() == pattern)
                        {
                            return start;
                        }

                        bool reset = false;

                        if (buff.Length > pattern.Length)
                        {
                            reset = true;
                        }
                        else
                        {
                            string subStr = pattern.Substring(0, buff.Length);

                            if (buff.ToString() != subStr)
                            {
                                reset = true;
                            }
                        }

                        if (reset)
                        {
                            buff.Length = 0;
                            startFound = false;
                            start = 0;
                        }
                    }

                    charCount++;
                }
            }
            finally
            {
                reader.BaseStream.Position = 0;
                reader.DiscardBufferedData();
            }
        }

        return -1;
    }
}
1
Brien Halstead

FileStream.Position(または同等にStreamReader.BaseStream.Position)は、基になるバッファリングが行われているため、通常、TextReaderの位置よりもはるかに進んでいます。

テキストファイルで改行がどのように処理されるかを判断できる場合は、行の長さと行末文字に基づいて読み取られたバイト数を合計できます。

File.WriteAllText("test.txt", "1234" + System.Environment.NewLine + "56789");

long position = -1;
long bytesRead = 0;
int newLineBytes = System.Environment.NewLine.Length;

using (var sr = new StreamReader("test.txt"))
{
    string line = sr.ReadLine();
    bytesRead += line.Length + newLineBytes;

    Console.WriteLine(line);

    position = bytesRead;
}

Console.WriteLine("Wait");

using (var sr = new StreamReader("test.txt"))
{
    sr.BaseStream.Seek(position, SeekOrigin.Begin);
    Console.WriteLine(sr.ReadToEnd());
}

より複雑なテキストファイルエンコーディングの場合は、これよりも複雑にする必要があるかもしれませんが、私にとってはうまくいきました。

0
yoyo