Как сравнить большие текстовые файлы?

у меня есть общий вопрос о вашем мнении о моей "технике".

есть 2 текстовых файлов (file_1 и file_2), которые нужно сравнивать друг с другом. Оба очень огромные (3-4 гигабайта, от 30,000,000 до 45,000,000 строк каждый). Моя идея-прочитать несколько строк (как можно больше) из file_1 в память, затем сравните их с все строки file_2. Если есть совпадение, строки из обоих файлов, которые совпадают, должны быть записаны в новый файл. Затем перейдите к следующей 1000 строкам file_1 а также сравните их с все строки file_2 пока я не прошел file_1 полностью.

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

как вы думаете, может взять? Для моей программы Время не имеет большого значения. У меня нет опыта работы с такими огромными файлами, поэтому я понятия не имею, сколько времени это может занять. Но это не займет больше дня. ;- ) Но я боюсь, что моя техника может занять вечность...

Антуан вопрос, который только что пришел мне на ум: сколько строк вы бы прочитали в памяти? Как можно больше? Есть ли способ определить количество возможных строк, прежде чем фактически попробовать? Я хочу прочитать как можно больше (потому что я думаю, что это быстрее), но у меня часто заканчивалась память.

спасибо продвижение.

редактировать Думаю, я должен объяснить свою проблему немного подробнее.

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

mat1 1000 2000 TEXT      //this means the range is from 1000 - 2000
mat1 2040 2050 TEXT
mat3 10000 10010 TEXT
mat2 20 500 TEXT

file_2выглядит так:

mat3 10009 TEXT
mat3 200 TEXT
mat1 999 TEXT

TEXT относится к символам и цифрам, которые меня не интересуют,mat может идти от mat1 - mat50 и не в порядке; также может быть 1000x mat2 (но цифры в следующем столбце разные). Мне нужно найти подходящие линии таким образом, что: matX одинакова в обеих сравниваемых строках и число, указанное в file_2 вписывается в диапазон указанных в file_1. Поэтому в моем примере я бы нашел одно совпадение: строка 3 из file_1и в строке 1 file_2 (потому что оба mat3 и 10009 между 10000 и 10010). Я надеюсь, это прояснит для вас!

так мой вопрос: как бы вы искали соответствующие строки?

Да, я использую Java в качестве языка программирования.

редактировать Сначала я разделил огромные файлы, чтобы у меня не было проблем с нехваткой памяти. Я также думаю, что быстрее сравнивать (многие) меньшие файлы друг с другом, чем эти два огромных файла. После этого я могу сравнить их так, как я упоминал выше. Возможно, это не лучший способ, но я все еще учусь. ;-) Все Nonentheless ваш подходы были очень полезны для меня, спасибо за ваши ответы!

14 ответов


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

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

разделы: читать до конца file_1 и отправить все строки, начинающиеся с mat1 to file_1_mat1 и так далее. Сделайте то же самое для file_2. Это тривиально с небольшим grep, или вы хотите сделать это программно на Java, это упражнение для начинающих.

это один проход через два файла в общей сложности 80 миллионов строк чтения, что дает два набора из 50 файлов по 800 000 строк каждый в среднем.

сортировка: для каждого раздела сортируйте только по числовому значению во втором столбце (нижняя граница от file_1 и фактическим номер от file_2). Даже если 800 000 строк не могут поместиться в память, я полагаю, мы можем адаптировать двустороннюю внешнюю сортировку слияния и выполнять это быстрее (меньше общих чтений), чем своего рода весь неразмеченное пространство.

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

даже без этапа сортировки наивное сравнение, которое вы уже делаете, должно работать быстрее через 50 пар файлов с 800 000 строк каждый, а не с двумя файлами с 40 миллионами строк каждый.


Я думаю, ваш способ вполне разумные.

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

но это довольно сложный способ-вам нужно запустить внешнюю программу (сортировку) или написать сопоставимую эффективную реализацию filesort в java самостоятельно - что само по себе непростая задача. Итак, для простоты, я думаю, что Вы способ chunked чтения очень многообещающий;

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

Далее -- вы можете прочитать линий буфер такой:

final List<String> lines = new ArrayList<>();
try{
    final List<String> block = new ArrayList<>(BLOCK_SIZE);
    for(int i=0;i<BLOCK_SIZE;i++){
       final String line = ...;//read line from file
       block.add(line);
    }
    lines.addAll(block); 
}catch(OutOfMemory ooe){
    //break
}

таким образом, Вы читаете столько строк, сколько можете-оставляя последний BLOCK_SIZE свободной памяти. BLOCK_SIZE должен быть большим enouth для остальных из вас программы для запуска без OOM


в идеальном мире вы сможете читать в каждой строке file_2 в память (возможно, используя объект быстрого поиска, такой как HashSet, в зависимости от ваших потребностей), затем прочитайте каждую строку из file_1 по одной и сравните ее со своей структурой данных, содержащей строки из file_2.

как вы сказали, у вас заканчивается память, однако, я думаю, что стратегия типа "разделяй и властвуй" была бы лучшей. Вы можете использовать тот же метод, что и я упоминал выше, но читать наполовину (или на треть, квартал... в зависимости от того, сколько памяти вы можете использовать) линий от file_2 и хранить их, а затем сравнить все строки в file_1. Затем прочтите следующую половину / третью / четверть / что угодно в память (заменив старые строки) и снова пройдите через file_1. Это означает, что вы должны пройти через file_1 больше, но вы должны работать с ограничениями памяти.


EDIT: в ответ на добавленную деталь в вашем вопросе, я бы изменил свой ответ частично. Вместо чтение во всех file_2 (или в кусках) и чтение в file_1 строки за раз, отмените это, поскольку file_1 содержит данные для проверки.

кроме того, что касается поиска совпадающих строк. Я думаю, что лучшим способом было бы сделать некоторую обработку на file_1. Создать HashMap<List<Range>> это сопоставляет строку ("mat1" - "mat50") со списком Ranges (просто обертка для startOfRange int и endOfRange int) и заполнить его данными из file_1. Затем напишите функцию like (ignoring проверка ошибок)

boolean isInRange(String material, int value)
{
    List<Range> ranges = hashMapName.get(material);
    for (Range range : ranges)
    {
        if (value >= range.getStart() && value <= range.getEnd())
        {
            return true;
        }
    }
    return false;
}

и вызовите его для каждой (проанализированной) строки file_2.


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

вы, вероятно, должны запустить некоторые эксперименты [бенчмарки] с различным размером куска, чтобы узнать, что является оптимальным куском для чтения в среднем случае.


Не уверен, насколько хороший ответ это будет - но посмотрите на эту страницу:http://c2.com/cgi/wiki?DiffAlgorithm - он суммирует несколько алгоритмов diff. Алгоритм Ханта-Макилроя, вероятно, является лучшей реализацией. На этой странице также есть ссылка на Java-реализацию GNU diff. Однако, я думаю, что реализация на C/C++ и скомпилированы в машинный код будет гораздо быстрее. Если вы застряли с java, вы можете рассмотреть JNI.


действительно, это может занять некоторое время. Вы должны сделать 1,200.000,000 сравнение линии. Есть несколько возможностей, чтобы ускорить это на порядок magnitute:

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

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


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

и все эти ответы о хэшах, контрольных суммах и т. д.: Они не быстрее. В обоих случаях вы должны прочитать весь файл. С хэшами / контрольными суммами вам даже нужно вычислить что-то...


что вы можете сделать, это сортировать каждый отдельный файл. например, UNIX sort или аналогичный в Java. Вы можете прочитать отсортированные файлы по одной строке за раз, чтобы выполнить сортировку слиянием.


Я никогда не работал с такими огромными файлами, но это моя идея и должна работать.

Вы можете посмотреть в хэш. Использование хэширования SHA-1.

импортировать следующие

import java.io.FileInputStream;
import java.security.MessageDigest;

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

StringBuffer myBuffer = new StringBuffer("");
//For each line loop through
    for (int i = 0; i < mdbytes.length; i++) {
        myBuffer.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
    }
System.out.println("Computed Hash = " + sb.toString());

пример кода SHA, ориентированный на текстовый файл

поэтому вопрос о вычислении SHA в JAVA (возможно, полезно)

еще один пример кода хэширования.

простое чтение каждого файла seperatley, если хэш-значение для каждого файла одинаковое в конце процесса, то два файла идентичны. Если нет, тогда что-то не так.

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

в целом, кажется, что чтение строка за строкой по строкам и т. д это займет вечность. Я бы сделал это, если вы пытаетесь найти каждую индивидуальную разницу. Но я думаю, что хэширование было бы быстрее, чтобы увидеть, являются ли они одинаковыми.

контрольная сумма ша


Если вы хотите точно знать, отличаются ли файлы или нет, то нет лучшего решения, чем ваше-сравнение последовательно.

однако вы можете сделать некоторые эвристики, которые могут сказать вам с некоторой вероятностью, если файлы идентичны. 1) проверьте размер файла; это самый простой. 2) Возьмите случайную позицию файла и сравните блок байтов, начиная с этой позиции в двух файлах. 3) повторите шаг 2) для достижения необходимой вероятности.

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


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

вы упомянули, что количество строк составляет около 45 миллионов. Это означает, что вы можете (потенциально) хранить индекс, который использует 16 байтов на запись (128 бит), и он будет использовать около 45,000,000*16 = ~685 МБ ОЗУ, что не является необоснованным в современной системе. Существуют накладные расходы при использовании решения I опишите ниже, чтобы можно было использовать другие методы, такие как сопоставленные с памятью файлы или таблицы на диске для создания индекса. См.Hypertable или HBase для примера того, как хранить индекс в быстрой дисковой хэш-таблице.

таким образом, в полном объеме алгоритм будет чем-то вроде:

  1. создайте хэш-карту, которая сопоставляет Long со списком Longs (HashMap>)
  2. получить хэш каждой строки в первый файл (объект.хэш-кода должно быть достаточно)
  3. получите смещение в файле строки, чтобы вы могли найти его позже
  4. добавьте смещение в список строк с соответствующими хэш-кодами в хэш-карте
  5. сравните каждую строку второго файла с набором смещений строк в индексе
  6. сохранить все строки, которые имеют соответствующие записи

EDIT: В ответ на ваш отредактированный вопрос, это не действительно помогает сама по себе. Вы можете просто хэшировать первую часть строки, но это создаст только 50 различных записей. Затем вы можете создать другой уровень в структуре данных, который сопоставит начало каждого диапазона со смещением линии, из которой он пришел.

что-то вроде index.get("mat32") вернет TreeMap диапазонов. Вы можете искать диапазон, предшествующий значению, которое вы ищете lowerEntry(). Вместе это даст вам довольно быстро проверьте если данная комбинация matX/number была в одном из диапазонов, которые вы проверяете.


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


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

hg init some_repo
cd some_repo
cp ~/huge_file1.txt .
hg ci -Am "Committing first huge file."
cp ~/huge_file2.txt huge_file1.txt
hg ci -m "Committing second huge file."

отсюда вы можете получить разницу, рассказывая вам, какие строки отличаются. Если бы вы могли каким-то образом использовать эту разницу, чтобы определить, какие линии были одинаковыми, вы бы все набор.

Это просто идея, кто-то поправит меня, если я ошибаюсь.


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