Заменить последовательность строк в двоичном файле


Я искал способ, чтобы найти и заменить последовательность строк в двоичных файлов без каких-либо удачи. Основным требованием было то, что метод не должен загружать все файлы в памяти, а использовать блоки. Я новичок в C# и код может выглядеть не "полированные", но она работает нормально. Может, у кого-то будут идеи, как этот код может быть улучшен или есть ли у нее недостатки? С. С. Спасибо Джон Скит за идею.

public static void ReplaceTextInFile(string inFile, string find, string replace)
{

    if (find.Length!=replace.Length) throw new ArgumentException("The lenght of find and replace strings must match!");

    const int chunkPrefix = 1024*10;
    var findBytes = GetBytes(find);
    var replaceBytes = GetBytes(replace);
    long chunkSize = findBytes.Length * chunkPrefix;
    var f = new FileInfo(inFile);
    if (f.Length < chunkSize)
        chunkSize = f.Length;

    var readBuffer = new byte[chunkSize];

    using (Stream stream = File.Open(inFile, FileMode.Open))
    {
        int bytesRead;
        while ((bytesRead=stream.Read(readBuffer, 0, readBuffer.Length)) != 0)
        {
            var replacePositions = new List<int>();
            var matches = SearchBytePattern(findBytes, readBuffer, ref replacePositions);
            if (matches != 0)
                foreach (var replacePosition in replacePositions)
                {
                    var originalPosition = stream.Position;
                    stream.Position = originalPosition - bytesRead + replacePosition;
                    stream.Write(replaceBytes, 0, replaceBytes.Length);                            
                    stream.Position = originalPosition;
                }


            if (stream.Length == stream.Position) break;
            var moveBackByHalf = stream.Position - (bytesRead / 2);
            stream.Position = moveBackByHalf;
        }

    }

}


static public int SearchBytePattern(byte[] pattern, byte[] bytes, ref List<int> position)
{
    int matches = 0;
    for (int i = 0; i < bytes.Length; i++)
    {
        if (pattern[0] == bytes[i] && bytes.Length - i >= pattern.Length)
        {
            bool ismatch = true;
            for (int j = 1; j < pattern.Length && ismatch == true; j++)
            {
                if (bytes[i + j] != pattern[j])
                    ismatch = false;
            }
            if (ismatch)
            {
                position.Add(i);
                matches++;
                i += pattern.Length - 1;
            }
        }
    }
    return matches;
}

public static byte[] GetBytes(string text)
{
    return Encoding.UTF8.GetBytes(text);
}

использование

 ReplaceTextInFile(@"MyFile.bin", "Text to replace", "New Text! Test!");


6628
3
c#
задан 30 июня 2011 в 02:06 Источник Поделиться
Комментарии
5 ответов

Несколько проблем с кодом:


  • Вы сравниваете строку длины для обоих, но тогда замена байтов. В кодировке UTF-8, как вы используете, вполне возможно, что два будут разные: если найти = "aeiou" и заменить = "áéíóú" вы будете иметь findBytes.Длина == 5, и replaceBytes.Длина == 10

  • Вам не нужно сдавать позиции параметра по ссылке на SearchBytePattern, поскольку вы не меняете ссылку, только вызывать методы на нем.

  • На SearchBytePattern, вам не нужен внешний цикл, чтобы пройти весь путь до байта.Длина, это только нужно идти в байт.Длина - выкройка.Длина + 1 (и что бы упростить внутреннее "если"

  • поток.Читать не обязательно возвращает число байтов, которые вы просили - он может вернуть меньше. Ваш код должен быть готов работать в этой ситуации.

4
ответ дан 3 июля 2011 в 04:07 Источник Поделиться

Производительность мудрый, вы, возможно, захотите, чтобы проверить Бойера-Мура алгоритм (погуглив нашел эту статью на CodeProject, и эту статью Куда-нибудь). Алгоритм очень эффективен, поскольку он выполняет предварительную обработку входной строки, чтобы знать , куда прыгать, когда есть несоответствие.

Например, если вы ищете строку "текст на замену" (15 символов), вы должны сначала проверить 15-го символа во входном потоке. Если это символ'Z', то, очевидно, вы можете сразу же перейти 15 символов вперед, потому что нет такого символа в строку поиска. С другой стороны, если это "р", то это может быть начальная буква слова заменить, и алгоритм будет прыгать 6 символов, чтобы попытаться выровнять стринги с входного потока. Это гораздо более эффективно, чем при перемещении указателя только по одной позиции в каждой итерации.

Далее, ваш буфер размером 10*1024*найти.Длина не имеет смысла. Какова цель этого умножения по сравнению с буфер фиксированного размера (больше, чем найти, конечно)? И, что более важно, почему ты постоянно возвращаешься на половину этой длины каждый раз, когда вы уже проверили эти данные и нужно только для резервного копирования найти.Длина-1 (в случае, если матч находится в самом конце твоего буфера)? Что вы делаете сейчас, будет иметь смысл только, если буфер стоит ровно в два раза длину найти (который является тем, что Джон, вероятно, имел в виду в своем ответе).

Кроме того, carlosfigueiraответом охватывает ошибки в коде (SearchBytePattern должен иметь параметр, указывающий на число байтов фактически чтение из потока), а также проблем, связанных с Unicode (если вы еще не читали это уже, проверить Джон тарелкам ОМГ пони, про Юникод).

2
ответ дан 3 июля 2011 в 06:07 Источник Поделиться

Можно убрать первый if в SearchBytePattern путем изменения состояния во внешнем цикле и начало внутреннего цикла for, например:

for (int i = 0; i < bytes.Length - pattern.Length; i++)
{
bool ismatch = true;
for (int j = 0; j < pattern.Length && ismatch == true; j++)

кроме того, вы можете нарушить внутренний цикл, когда была найдена разница:

if (bytes[i + j] != pattern[j])
{
ismatch = false;
break;
}

1
ответ дан 30 июля 2011 в 04:07 Источник Поделиться

Так несколько неотложных вещей, которые я вижу, жестко кодировку, если вы не собираетесь, чтобы определить кодировку файла для автоматического поиска, а затем сделать его параметров, задаваемых пользователем. Вы действительно не можете сделать это точно без уверенности в правильной кодировке. Для получения более подробной информации об этом и почему проверить:
http://www.joelonsoftware.com/articles/Unicode.html

Кроме того, кусок префикс лучше либо руками пользователем, или на основе: памяти трудности вам известны по активности этот процесс, или B: размер файла, а размер фрагментов будет вызывать очень разные чтения по производительности в зависимости от размера файла, т. е.

Файл 2Гб на 20Мб кусок будет обработана гораздо быстрее, чем на 256Б кусок. Файл 400к будет вполне приемлемым по 256Б кусок.

Знаю, что ограничения памяти, ожидания параллелизмом (так что вы не создаете слишком много ИО ждет), и время ожидания для пользователя, чтобы решить, размер блока, в противном случае оставьте его до пользователей в качестве параметра.

Далее, имя SearchBytePattern дает абсолютно никаких иллюстрация к пользователю, что он собирается сделать, (я до сих пор не уверен, что это начитавшись его..) может быть, оно возвращается в положение начала индекс BytePattern? Может, это возвращается по параметру фактической строки в этом месте? Дать ему четкий однозначный имя (даже если это долго), то же и для параметров "положение" - это целое число, список переменных типа int могут быть позиции? Или что-то еще, и это uncelar, что они позиции..

Различать параметры и имена методов.

Далее избавляемся от SearchBytePattern вообще все равно, вместо downconverting вашим критериям searchpattern в байты, использовать правильную кодировку с streamreader и объект StreamWriter, который отправляет в отдельный файл. тогда вам просто необходимо (простите незначительные ошибки, экспромтом..):

char[] charsReadFromFile = new char[chunkSize];

do
{
int numberOfCharsReadFromFile = streamReaderOnGivenFile.Read(charsReadFromFile, 0, chunkSize);
string stringReadFromFile = new String(charsReadFromFile).Trim(charsReadFromFile.Skip(numberOfCharsReadFromFile).ToArray());
streamWriterOnNewFile.Write(stringReadFromFile.Replace(searchPattern, stringToReplacePatternWith));
} while(numberOfCharsReadFromFile > 0)

1
ответ дан 30 июля 2011 в 11:07 Источник Поделиться

Одна вещь, я заметил, что ваш SearchBytePattern функция возвращает int, который всегда равен количеству элементов в позиции списка. Вы можете либо сделать возвращаемого значения Void, или функция возвращает новый список, поскольку эти два лишние.

Кроме того, комментарии помогают.

0
ответ дан 30 июня 2011 в 02:06 Источник Поделиться