2

テキスト行を一時ファイルに書き込むときに、将来のランダム アクセスのために各行のファイル位置を保存したいと考えています。

これは動作しません

 StreamWriter outFile;

 { ...
          fPos[i++] = outFile.BaseStream.Position;
 }

上記のループの fPos 値は、0、0、0、...、1024、1024、...2028、2048 などです。つまり、物理的な位置です。

次は機能しますが、バッファリングを廃止します。

 { ...
          outFile.Flush();
          fPos[i++] = outFile.BaseStream.Position;
 }

バッファリングをあきらめずに (論理) ファイル位置を取得するより良い方法はありますか?

4

3 に答える 3

1

これがどこから来たのか正確には思い出せませんが、過去にはかなりうまくいきました。

リーダーをライターに置き換え:

/// <summary>
/// A stream writer that allows its current position to be recorded and 
/// changed. This is not generally possible for stream writers, because 
/// of the use buffered reads. 
/// This writer can be used only for text content. Theoretically it can 
/// process any encoding, but was checked only for ANSI and UTF8 If you 
/// want to use this class with any other encoding, please check it.
/// NOT SAFE FOR ASYNC WRITES.
/// </summary>   
public class PositionableStreamWriter : StreamWriter
{
    /// <summary>
    /// Out best guess counted position.
    /// </summary>
    private long _position;

    /// <summary>
    /// Guards against e.g. calling "Write(char[] buffer, int index, int count)" 
    /// as part of the implementation of "Write(string value)", which would cause
    /// double counting.
    /// </summary>
    private bool _guardRecursion; 

    public PositionableStreamWriter(string fileName, bool append, Encoding enc)
        : base(fileName, append, enc)
    {
        CommonConstruct();
    }
    public PositionableStreamWriter(Stream stream, Encoding enc)
        : base(stream, enc)
    {
        CommonConstruct();
    }

    /// <summary>
    /// Encoding can really haven't preamble
    /// </summary>        
    private bool CheckPreamble(long lengthBeforeFlush)
    {
        byte[] preamble = this.Encoding.GetPreamble();
        if (this.BaseStream.Length >= preamble.Length)
        {
            if (lengthBeforeFlush == 0)
                return true;
            else // we would love to read, but base stream is write-only.
                return true; // just assume a preamble is there.
        }
        return false;
    }

    /// <summary>
    /// Use this property to get and set real position in file.
    /// Position in BaseStream can be not right.
    /// </summary>
    public long Position
    {
        get { return _position; }
        set
        {
            this.Flush();
            ((Stream)base.BaseStream).Seek(value, SeekOrigin.Begin);
            _position = value;
        }
    }

    public override void Write(char[] buffer)
    {
        if (buffer != null && !_guardRecursion)
            _position += Encoding.GetByteCount(buffer);
        CallBase(() => base.Write(buffer));
    }
    public override void Write(char[] buffer, int index, int count)
    {
        if (buffer != null && !_guardRecursion)
            _position += Encoding.GetByteCount(buffer, index, count);
        CallBase(() => base.Write(buffer, index, count));
    }
    public override void Write(string value)
    {
        if (value != null && !_guardRecursion)
            _position += Encoding.GetByteCount(value);
        CallBase(() => base.Write(value));
    }

    public override void WriteLine()
    {
        if (!_guardRecursion)
            _position += Encoding.GetByteCount(NewLine);
        CallBase(() => base.WriteLine());
    }
    public override void WriteLine(char[] buffer)
    {
        if (buffer != null && !_guardRecursion)
            _position += Encoding.GetByteCount(buffer) + Encoding.GetByteCount(NewLine);
        CallBase(() => base.WriteLine(buffer));
    }
    public override void WriteLine(char[] buffer, int index, int count)
    {
        if (buffer != null && !_guardRecursion)
            _position += Encoding.GetByteCount(buffer, index, count) + Encoding.GetByteCount(NewLine);
        CallBase(() => base.WriteLine(buffer, index, count));
    }
    public override void WriteLine(string value)
    {
        if (value != null && !_guardRecursion)
            _position += Encoding.GetByteCount(value) + Encoding.GetByteCount(NewLine);
        CallBase(() => base.WriteLine(value));
    }

    private void CallBase(Action callBase)
    {
        if (_guardRecursion == true)
            callBase();
        else
        {
            try
            {
                _guardRecursion = true;
                callBase();
            }
            finally
            {
                _guardRecursion = false;
            }
        }
    }

    private void CommonConstruct()
    {
        var lenghtAtConstruction = BaseStream.Length;
        if (lenghtAtConstruction == 0)
            Flush(); // this should force writing the preamble if a preamble is being written.
        //we need to add length of symbol which is in begin of file and describes encoding of file                                    
        if (CheckPreamble(lenghtAtConstruction))
        {
            _position = this.Encoding.GetPreamble().Length;
        }
    }
}

使用法:

class Program
{
    static void Main(string[] args)
    {
        var pos = new List<long>();
        using (var writer = new PositionableStreamWriter("tst.txt", false, Encoding.Unicode))
        {
            pos.Add(writer.Position);
            writer.Write("abcde");
            pos.Add(writer.Position);
            writer.WriteLine("Nope");
            pos.Add(writer.Position);
            writer.WriteLine("Something");
            pos.Add(writer.Position);
            writer.WriteLine("Another thing");
            pos.Add(writer.Position);
        }

        using (var stream = File.Open("tst.txt", FileMode.Open))
        using (var reader = new BinaryReader(stream))
        {
            for (int i = 0; i < pos.Count - 1; i++)
            {
                stream.Position = pos[i];
                var len = (int)(pos[i + 1] - pos[i]);
                var buf = reader.ReadBytes(len);
                Console.Write(Encoding.Unicode.GetString(buf));
            }
        }
    }
}
于 2013-08-19T17:55:55.587 に答える