繁体   English   中英

使用BinaryWriter查找和插入数据

[英]Using BinaryWriter to seek and insert data

背景:

我是C#的新手,目前正致力于一个主要用于学习的项目。 我正在创建一个库来处理Quake和Quake II使用的.PAK文件。 我可以阅读并提取它们,以及编写空白存档,然后我将数据写入存档。

基本规范可以在这里找到参考: http//debian.fmi.uni-sofia.bg/~sergei/cgsr/docs/pak.txt

问题:

我想寻找二进制数据部分的末尾并插入给定文件的数据。 我在创建空文件时在方法中使用BinaryWriter,所以这就是我想要用于插入的内容。 根据我的发现,使用BinaryWriter进行搜索和写入将在给定位置写入并覆盖现有数据。

我的目标是简单地在给定点插入数据而不会丢失任何现有数据。 如果这是不可能的,我想我将需要提取包含单个文件信息(名称,偏移量,长度)的整个索引,添加二进制数据,然后附加索引并附加我正在编写的文件的信息。

有什么想法或建议吗?

您无法使用C#IO类直接将数据插入文件。 最简单的方法是将其读入缓冲区并操作缓冲区,或将第一部分写入临时文件,然后插入新数据,然后写入文件的其余部分,然后重命名。 或者,您可以在文件末尾附加一些数据并向上移动所有内容,以便在您要插入数据的位置创建适当数量的可用空间。

看看http://www.codeproject.com/Articles/17716/Insert-Text-into-Existing-Files-in-C-Without-Temp

直接使用基于流的对象(如FileStream)是文件级操作的更好选择。

无法将数据插入到任何文件中,您必须读取整个文件并使用新插入的部分将其保存回来,或者复制到新文件直到插入点,添加新数据然后复制其余文件(您可以此时删除原始/重命名新)。

很难有效地进行插入。 特别是如果您想在同一个流中进行多次插入。 您可以编写自己的Stream类来跟踪您执行的任何插入操作,并假装在实际上没有插入更改时插入更改。 例如,如果您在位置10处有A并且在该位置插入X 你的类可以,而不是将插入写入流并移动所有后续字节,当你请求位置11时,只要你从底层流请求位置10和A ,就返回X (而在底层流中,它仍然位于第10位) )。 然后,当您在流上调用FlushClose时,整个更改的流将立即刷新到基础流。 这需要一些非常仔细的工程才能正确,因为您可能插入插入,插入任何顺序等等......

下面是我为您编写的一些代码,它将任意字节插入到流中并移动所有后续字节而不进行任何聪明的缓存。 不幸的是,它不使用BinaryWriter而是直接在Stream (必须是可搜索的)上工作,因为它更容易编码。 它插入流的当前位置,然后位置在插入的字节之后。 要插入字符串,请使用Encoding类将它们转换为字节。

public static class StreamExtensions
{
    /// <summary>
    /// Inserts the specified binary data at the current position in the stream.
    /// </summary>
    /// <param name="stream">The stream.</param>
    /// <param name="array">The array.</param>
    /// <remarks>
    /// <para>Note that this is a very expensive operation, as all the data following the insertion point has to
    /// be moved.</para>
    /// <para>When this method returns, the writer will be positioned at the end of the inserted data.</para>
    /// </remarks>
    public static void Insert(this Stream stream, byte[] array)
    {
        #region Contract
        if (stream == null)
            throw new ArgumentNullException("writer");
        if (array == null)
            throw new ArgumentNullException("array");
        if (!stream.CanRead || !stream.CanWrite || !stream.CanSeek)
            throw new ArgumentException("writer");
        #endregion

        long originalPosition = stream.Position;
        long readingPosition = originalPosition;
        long writingPosition = originalPosition;

        int length = array.Length;
        int bufferSize = 4096;
        while (bufferSize < length)
            bufferSize *= 2;

        int currentBuffer = 1;
        int[] bufferContents = new int[2];
        byte[][] buffers = new [] { new byte[bufferSize], new byte[bufferSize] };
        Array.Copy(array, buffers[1 - currentBuffer], array.Length);
        bufferContents[1 - currentBuffer] = array.Length;

        bool done;

        do
        {
            bufferContents[currentBuffer] = ReadBlock(stream, ref readingPosition, buffers[currentBuffer], out done);

            // Switch buffers.
            currentBuffer = 1 - currentBuffer;

            WriteBlock(stream, ref writingPosition, buffers[currentBuffer], bufferContents[currentBuffer]);

        } while (!done);

        // Switch buffers.
        currentBuffer = 1 - currentBuffer;

        // Write the remaining data.
        WriteBlock(stream, ref writingPosition, buffers[currentBuffer], bufferContents[currentBuffer]);

        stream.Position = originalPosition + length;
    }

    private static void WriteBlock(Stream stream, ref long writingPosition, byte[] buffer, int length)
    {
        stream.Position = writingPosition;
        stream.Write(buffer, 0, length);
        writingPosition += length;
    }

    private static int ReadBlock(Stream stream, ref long readingPosition, byte[] buffer, out bool done)
    {
        stream.Position = readingPosition;
        int bufferContent = 0;
        int read;
        do
        {
            read = stream.Read(buffer, bufferContent, buffer.Length - bufferContent);
            bufferContent += read;
            readingPosition += read;
        } while (read > 0 && read < buffer.Length);
        done = read == 0;
        return bufferContent;
    }
}

要使用此方法,请将代码粘贴到与代码相同的命名空间中,然后:

Stream someStream = ...;
byte[] dataToInsert = new byte[]{ 0xDE, 0xAD, 0xBE, 0xEF };
someStream.Position = 5;
someStream.Insert(dataToInsert);

既然你说你有一个BinaryWriter因为你用它来创建一个空文件,为了能够使用这个类,你需要实际的Stream 您可以使用静态File.Create()File.Open()方法,它们返回FileStream对象。 如果您以后决定仍然需要BinaryWriter ,则可以使用提供BinaryWriter构造函数的流。 要从编写器获取流,请使用BinaryWriter.BaseStream 请注意,关闭BinaryWriter也会关闭基础流,除非您通过使用MiscUtil库中的NonClosingStreamWrapper来阻止它。

是的,正如您所说,您无法在文件中“插入”数据。 您应该使用一些读写器组合直到插入点并写入您的新数据。

我不知道.pak格式,但我认为这个例子可以说明你。

    var sourceStream = System.IO.File.OpenRead("myFile.pak");
    var destStream = System.IO.File.Create("modified.pak");

    using (var reader = new System.IO.BinaryReader(sourceStream))
    {
        using (var writer = new System.IO.BinaryWriter(destStream))
        {
            while (reader.BaseStream.CanRead)
            {
                char data = reader.ReadChar();
                if (data == '?')
                {
                    writer.Write("<Found!>");
                }
                else
                {
                    writer.Write(data);
                }
            }
        }
    }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM