оптимизация sed (большая модификация файла на основе меньшего набора данных)

StackOverflow https://stackoverflow.com/questions/848914

Вопрос

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

Моя последняя задача связана с редактированием строк на основе данных из другого файла.

Файл данных (который должен быть изменен) содержит 1500000 строк, каждая из которых, например.Длина 800 символов.Каждая строка уникальна и содержит только один идентификационный номер, каждый идентификационный номер уникален)

Файл модификатора, например.Длина 1800 строк, содержит идентификационный номер, сумму и дату, которые следует изменить в файле данных.

Я только что преобразовал (с помощью регулярного выражения Vim) файл модификатора в sed, но это очень неэффективно.

Допустим, у меня есть такая строка в файле данных:

(some 500 character)id_number(some 300 character)

И мне нужно изменить данные в части из 300 символов.

На основе файла модификатора я получаю такие строки sed:

/id_number/ s/^\(.\{650\}\).\{20\}/\1CHANGED_AMOUNT_AND_DATA/

Итак, у меня есть 1800 таких строк.

Но я знаю, что даже на очень быстром сервере, если я сделаю

sed -i.bak -f modifier.sed data.file

Это очень медленно, потому что ему приходится читать каждый шаблон x каждую строку.

Нет ли лучшего способа?

Примечание: Я не программист, никогда не изучал (в школе) алгоритмы.Я могу использовать awk, sed, устаревшую версию Perl на сервере.

Это было полезно?

Решение

Мои предлагаемые подходы (в порядке желательности) заключаются в обработке этих данных как:

  1. База данных (даже простая БД на базе SQLite с индексом будет работать намного лучше, чем sed/awk для файла размером 10 ГБ)
  2. Плоский файл, содержащий записи фиксированной длины.
  3. Плоский файл, содержащий записи переменной длины.

Использование базы данных позволяет решить все те мелкие детали, которые замедляют обработку текстовых файлов (поиск нужной записи, изменение данных, сохранение их обратно в БД).Взгляните на DBD::SQLite в случае Perl.

Если вы хотите придерживаться плоских файлов, вам нужно вручную поддерживать индекс вместе с большим файлом, чтобы вам было легче находить номера записей, которыми вам нужно манипулировать.Или, еще лучше, возможно, ваши идентификационные номера. являются ваши рекордные цифры?

Если у вас есть записи переменной длины, я бы предложил преобразовать их в записи фиксированной длины (поскольку кажется, что только ваш идентификатор имеет переменную длину).Если вы не можете этого сделать, возможно, какие-либо существующие данные никогда не будут перемещаться в файле?Затем вы можете сохранить этот ранее упомянутый индекс и при необходимости добавлять новые записи, с той разницей, что вместо индекса, указывающего на номер записи, вы теперь указываете на абсолютную позицию в файле.

Другие советы

Я предлагаю вам программу, написанную на Perl (поскольку я не гуру sed/awk и не знаю, на что именно они способны).

Ваш «алгоритм» прост:вам нужно, прежде всего, создать хэш-карту, которая могла бы предоставить вам новую строку данных, применимую для каждого идентификатора.Это достигается, конечно же, чтением файла модификатора.

Как только эта карта заполнена, вы можете просмотреть каждую строку вашего файла данных, прочитать идентификатор в середине строки и сгенерировать новую строку, как вы описали выше.

Я тоже не гуру Perl, но считаю, что программа довольно простая.Если вам нужна помощь в написании, попросите об этом :-)

В Perl вам следует использовать substr для получения id_number, особенно если id_number имеет постоянную ширину.

my $id_number=substr($str, 500, id_number_length);

После этого, если $id_number находится в диапазоне, вам следует использовать substr для замены оставшегося текста.

substr($str, -300,300, $new_text);

Регулярные выражения Perl работают очень быстро, но не в этом случае.

Мое предложение: не используйте базу данных.Хорошо написанный Perl-скрипт на порядок превосходит базу данных в задачах такого рода.Поверьте, у меня есть большой практический опыт.Когда Perl будет завершен, у вас не будет импортированных данных в базу данных.

Когда вы пишете 1500000 строк по 800 символов, мне кажется, что это 1,2 ГБ.Если у вас очень медленный диск (30 МБ/с), вы прочитаете его за 40 секунд.Лучше 50 -> 24с, 100 -> 12с и так далее.Но скорость поиска хеша Perl (например, соединения с БД) на процессоре с частотой 2 ГГц превышает 5 млн запросов в секунду.Это означает, что ваша работа с привязкой к процессору будет длиться секунды, а работа с привязкой к вводу-выводу будет занимать десятки секунд.Если это действительно 10 ГБ, цифры изменятся, но пропорция останется той же.

Вы не указали, меняет ли размер изменение данных или нет (если изменение можно выполнить на месте), поэтому мы не будем это предполагать и будем работать как фильтр.Вы не указали, какой у вас формат "файла-модификатора" и какая модификация.Предположим, что он разделен табуляцией примерно так:

<id><tab><position_after_id><tab><amount><tab><data>

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

my $modifier_filename = 'modifier_file.txt';

open my $mf, '<', $modifier_filename or die "Can't open '$modifier_filename': $!";
my %modifications;
while (<$mf>) {
   chomp;
   my ($id, $position, $amount, $data) = split /\t/;
   $modifications{$id} = [$position, $amount, $data];
}
close $mf;

# make matching regexp (use quotemeta to prevent regexp meaningful characters)
my $id_regexp = join '|', map quotemeta, keys %modifications;
$id_regexp = qr/($id_regexp)/;     # compile regexp

while (<>) {
  next unless m/$id_regexp/;
  next unless $modifications{$1};
  my ($position, $amount, $data) = @{$modifications{$1}};
  substr $_, $+[1] + $position, $amount, $data;
}
continue { print }

На моем ноутбуке это занимает около полминуты для 1,5 миллиона строк, 1800 идентификаторов поиска, 1,2 ГБ данных.Для 10 ГБ это не должно превышать 5 минут.Это разумно быстро для вас?

Если вы начинаете думать, что вы не привязаны к вводу-выводу (например, если используете какой-либо NAS), но привязаны к ЦП, вы можете пожертвовать некоторой читабельностью и изменить это:

my $mod;
while (<>) {
  next unless m/$id_regexp/;
  $mod = $modifications{$1};
  next unless $mod;
  substr $_, $+[1] + $mod->[0], $mod->[1], $mod->[2];
}
continue { print }

Вам почти наверняка следует использовать базу данных, поскольку МайкиБ предложил.

Если по какой-то причине вы не хотите использовать базу данных, то, если список модификаций поместится в памяти (как сейчас составляет 1800 строк), наиболее эффективным методом является хеш-таблица, заполненная модификациями, как это предложено Ив Бом.

Если вы дойдете до того, что даже список модификаций станет огромным, вам нужно отсортировать оба файла по их идентификаторам, а затем выполнить слияние списка -- по сути:

  1. Сравните идентификатор в «верхней части» входного файла с идентификатором в «верхней части» файла изменений.
  2. Откорректируйте запись соответствующим образом, если они совпадают.
  3. Напишите это
  4. Отбросьте «верхнюю» строку из файла, который имел (в алфавитном или числовом порядке) наименьший идентификатор, и прочитайте другую строку из этого файла.
  5. Перейти к 1.

За кулисами база данных почти наверняка будет использовать слияние списков, если вы выполняете это изменение с помощью одного SQL-кода. UPDATE команда.

Хорошая сделка по выбору sqlloader или datadump.Вот так и надо идти.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top