Удалить управление и не-ASCII символы из больших файлов


Я получаю огромное входящих файлов (до 6ГБ) и они завалены контроля и не-ASCII символов. Мне нужно зачистить их и я сделал эту процедуру (ниже). Проблема в том, что это безумно медленно. Я бы с удовольствием какие-нибудь мысли или советы о том, как я могу его ускорить.

public void StripHighBitCharacters(string fn)
{
    string writeFile = fn + "B";
    using (var reader = new StreamReader(fn))
    using (var writer = new StreamWriter(writeFile))
    {
        while (!reader.EndOfStream)
        {
            string line = reader.ReadLine();
            if (line.Length > 0)
            {
                writer.WriteLine(BuildClearString(line));
            }
            else
            {
                writer.WriteLine(line);
            }
        }
    }
    File.Copy(writeFile, fn, true);
    File.Delete(writeFile);
}
public string BuildClearString(string line)
{

    StringBuilder sb = new StringBuilder();

    foreach (char c in line)
    {
        if (c >= 32 && c <= 175)
        {
            sb.Append(c);
        }
    }

    return (sb.ToString());
}


1518
5
задан 3 февраля 2018 в 05:02 Источник Поделиться
Комментарии
5 ответов

Общие


  • Вы используете using заявления, что всегда хорошо.

  • У вас маленькие, а также назвал методы, которые хорошо, как хорошо, но StripHighBitCharacters() способ не делать то, что подразумевает название. В BuildClearString() метод делает то, что StripHighBitCharacters() стоит делать, основываясь на ее имя.

  • Метод параметр StripHighBitCharacters плохо им. Почему бы вам не назвать его fileName ?

  • Вы должны быть совместимы с использованием var тип. Почему вы не используете его электронной.G для string writeFile ?


@1201ProgramAlarm , упомянутых в его ответ повторное использование StringBuilder который способ пойти на прирост производительности, но я бы это еще.


  • Я бы инициализировать StringBuilder с начальной мощностью не менее 4 КБ, потому что обычно ваша файловая система хранения данных в 4 КБ блоков. Но поскольку вы ожидаете получить реальные большие файлы, то следует увеличить емкость е.г 4Мб.

  • Вместо того чтобы создавать новый файл с именем fn + "B" вы должны использовать
    Path.GetTempFileName() и после того, как содержимое написано удалить и переместить временный файл в исходный пункт назначения.

Реализация указанных пунктов приведет к

private const int maxCapacity = 4096 * 1024;
private StringBuilder sb = new StringBuilder(maxCapacity);

public void CleanFile(string fileName)
{

var tempFileName = Path.GetTempFileName();
using (var reader = new StreamReader(fileName))
using (var writer = new StreamWriter(tempFileName))
{
sb.Length = 0;
while (!reader.EndOfStream)
{
var line = reader.ReadLine();
if (line.Length + sb.Length > maxCapacity)
{
writer.Write(sb.ToString());
sb.Length = 0;
}
StripHighBitCharacters(line);

}
}

File.Delete(fileName);
File.Move(tempFileName, fileName);
}

public void StripHighBitCharacters(string value)
{
foreach (var c in value.Where(c => c > 31 && c < 176))
{
sb.Append(c);
}

sb.AppendLine();
}

После использования бедняга профилирования (с помощью секундомера) я понял, что при условии StripHighBitCharacters() способ с помощью LINQ заняло около 39 секунд.

Используя только петля и if как так

public void StripHighBitCharacters(string value)
{
foreach (var c in value)
{
if (c > 31 && c < 176)
{
sb.Append(c);
}
}

sb.AppendLine();
}

замеры пошли лучше. Прошло всего 22 секунды.

Оба теста были сделаны с помощью файла с 1,3 ГБ.

2
ответ дан 5 февраля 2018 в 08:02 Источник Поделиться

Одним из источников снижения производительности частые выделения памяти. В вашем случае StringBuilder будет выделено место для каждой строки в ваш файл, и может выделить дополнительное пространство (вместе с "копирование данных") для более длинных линий.

Вы можете устранить все это за счет использования StringBuilder объект. В начале BuildClearStringвызовите метод Clear на нем (sb.Clear(); или sb.Length = 0;). Следует, что на проверку емкости.

if (sb.Capacity < line.Length)
sb.Capacity = line.Length;

Путем изменения емкости вы убедитесь, что буфер достаточно велик, чтобы вместить все символы, которые вы будете добавлять, поэтому вам не придется оплачивать какие-либо выделения памяти при обработке строки. Используя его, вы держите существующей выделенной памяти, что позволяет избежать каких-либо ассигнований для последующих строк, если строка больше, чем вы уже столкнулись. Вы также можете установить начальную емкость на StringBuilder объект.

4
ответ дан 4 февраля 2018 в 05:02 Источник Поделиться

Это должно повысить производительность, так как объектом StreamWriter.Писать(чар) реализация не особенно бедных накладных расходов.

НБ, это позволит устранить необходимость в каких-либо промежуточных StringBuilder и связанные временные массивы.

public void StripHighBitCharacters(string fn)
{
string writeFile = fn + "B";
using (var reader = new StreamReader(fn))
using (var writer = new StreamWriter(writeFile))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine();
if (line.Length > 0)
{
foreach (var c in line.Where(c => c >= 32 && c <= 175)) { writer.Write(c); }
}

writer.WriteLine();
}
}
// You may wish to consider moving `fn` to a temp location and then deleting it after the `File.Move(writeFile, fn)` line succeeds
File.Delete(fn);
File.Move(writeFile, fn);
}

1
ответ дан 4 февраля 2018 в 10:02 Источник Поделиться

Рассмотрим производителями и потребителями шаблон как методом blockingcollection. Читать линии и полосы в производителе. Писать чистые линии у потребителя. Это позволяет активным диском и газа в основном бесплатно. Используйте верхним, так что производитель не слишком далеко от потребителя.

Как уже было сказано только один строковом разработчике

private StringBuilder sb = new StringBuilder();
public string BuildClearString(string line)
{
sb.clear();

Если вам не нужны начальные и конечные пробельные символы, а затем использовать строку.Метод TRIM.

var line = reader.ReadLine().Trim();

Это может быть быстрее. Но я сомневаюсь в этом.

foreach (char c in line)
{
if (c < 32)
{
continue;
}
if (c > 175)
{
continue;
}
sb.Append(c);
}

Без производителя потребительской части я обрезать его вниз. Эти проверки требуют времени.

public void StripHighBitCharacters(string fn)
{
string writeFile = fn + "B";
using (var reader = new StreamReader(fn))
using (var writer = new StreamWriter(writeFile))
{
while (!reader.EndOfStream)
{
string line = reader.ReadLine().Trim();
writer.WriteLine(BuildClearString(line));
}
}
File.Delete(fn);
File.Move(writeFile, fn);
}

private StringBuilder sb = new StringBuilder();
public string BuildClearString(string line)
{
sb.Clear();
foreach (char c in line.Where(c => c >= 32 && c <= 175))
{
sb.Append(c);
}
return (sb.ToString());
}

1
ответ дан 5 февраля 2018 в 03:02 Источник Поделиться

Почему бы просто не использовать BinaryReader/BinaryWriter? Если у вас много строк, вы могли бы закончить с более итераций цикла с readline() и BinaryReader бы минимизировать накладные расходы и устраняют необходимость в то StringBuilder или оценка размера буфера.

private void StripUnwantedChars(string InFile, string OutFile, int readSize = 1048576)
{
using (var fsInFile = File.Open(InFile, FileMode.Open, FileAccess.Read))
using (var bReader = new BinaryReader(fsInFile))
using (var fsOutfile = File.Open(OutFile, FileMode.Create))
using (var bWriter = new BinaryWriter(fsOutfile))
{
while (fsInFile.Position != fsInFile.Length)
{
byte[] bytes = bReader.ReadBytes(readSize);
foreach (byte checkByte in bytes)
{
if (((checkByte >= 32) && (checkByte <= 175)) || (checkByte == 13) || (checkByte == 10))
{
bWriter.Write(checkByte);
}
}
}
}
}

Редактировать: добавлена проверка для разрыва строки и возврата каретки.

1
ответ дан 5 февраля 2018 в 04:02 Источник Поделиться