Как удалить повторяющиеся слова с помощью Java, когда слов больше 200 миллионов?

У меня есть файл (размер = ~ 1,9 ГБ), который содержит ~ 220 000 000 (~ 220 миллионов) слов / строк. У них есть дублирование, почти 1 повторяющееся слово на 100 слов.

Во второй программе я хочу прочитать файл. Мне удалось прочитать файл по строкам с помощью BufferedReader.

Теперь, чтобы удалить дубликаты, мы можем использовать Set (и его реализации), но Set имеет проблемы, как описано ниже в 3 различных сценариях:

  1. При размере JVM по умолчанию Set может содержать до 0,7-0,8 миллиона слов, а затем OutOfMemoryError.
  2. При размере JVM 512M Set может содержать до 5-6 миллионов слов, а затем ошибку OOM.
  3. При размере JVM 1024M Set может содержать до 12-13 миллионов слов, а затем ошибку OOM. Здесь после добавления 10 миллионов записей в Set операции становятся крайне медленными. Например, добавление следующих ~ 4000 записей заняло 60 секунд.

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

Пожалуйста, дайте мне знать, есть ли у вас какие-либо идеи о каких-либо других способах / подходах для удаления повторяющихся слов с помощью Java из такого гигантского файла. Огромное спасибо :)

Добавление информации к вопросу: Мои слова в основном являются буквенно-цифровыми и являются идентификаторами, уникальными в нашей системе. Следовательно, это не простые английские слова.


person Ketan    schedule 19.09.2012    source источник
comment
в качестве решения, можете ли вы использовать базу данных или даже второй файл для хранения результатов?   -  person Francisco Spaeth    schedule 19.09.2012
comment
Думаю, вы собираетесь перебирать еще долго.   -  person Hot Licks    schedule 19.09.2012
comment
Я бы удостоверился, что у меня достаточно памяти для этой задачи. Вы можете купить 16 ГБ памяти для ПК примерно за 100 долларов. В наши дни это не стоит так дорого.   -  person Peter Lawrey    schedule 19.09.2012
comment
Что вы имеете в виду с одним повторяющимся словом на каждые 100 слов? Каждый подсписок длиной 100 содержит в среднем один элемент дважды? Или 99% слов в файле уникальны?   -  person meriton    schedule 20.09.2012
comment
Ключевым моментом здесь является внешняя сортировка. После сортировки слов все дубликаты будут последовательными. Затем вы делаете еще один быстрый проход по данным. фильтрация дубликатов.   -  person NovaDenizen    schedule 20.09.2012
comment
Определите слово здесь. Это действительно стандартное слово для разговорной речи или что-то еще?   -  person Hot Licks    schedule 20.09.2012
comment
@ Питер Лоури: Теоретически я определенно с вами согласен. Однако это может зависеть от вашей организации. Я был в контакте с местами, где аппаратный хостинг передается на аутсорсинг, где ПК за 100 долларов нельзя было размещать рядом с центром обработки данных. Вам нужно будет купить дорогой сервер, который также стоит небольшое состояние каждый месяц, чтобы просто сидеть и работать, время от времени сортируя несколько большой файл строк.   -  person Buhb    schedule 20.09.2012
comment
Имейте в виду, что размер самого файла превышает 1024 МБ, поэтому любая попытка сохранить все строки в памяти без какой-либо сложной схемы сжатия гарантированно потерпит неудачу.   -  person Buhb    schedule 20.09.2012
comment
@Buhb Хотя это правда, организации с центром обработки данных, которые, скажем, тратят 3 тысячи долларов на ПК, не будут платить вам минимальную заработную плату. AFAIK, стоимость вашего времени для компании по сравнению со стоимостью оборудования + поддержки, как правило, одинакова, независимо от того, является ли организация экономичной или нет.   -  person Peter Lawrey    schedule 20.09.2012
comment
Важно ли сохранять порядок слов? Вы должны использовать Java или что-то вроде sort -u возможно?   -  person Axel    schedule 20.09.2012
comment
Что касается ваших утверждений о том, сколько слов может храниться в памяти, удостоверились ли вы, что ваши слова не занимают больше памяти, чем необходимо? Я имею в виду такие вещи, как хранение подстрок без явного создания новых экземпляров String (поскольку это будет разделять буфер исходных строк, что увеличивает использование памяти). Однако с ~ 220 миллионами слов это не решит вашу проблему ...   -  person Axel    schedule 20.09.2012
comment
Сколько символов в одном из ваших слов. Вы подсчитали, сколько разных слов можно составить?   -  person Hot Licks    schedule 21.09.2012


Ответы (13)


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

person Tobias Ritzau    schedule 19.09.2012
comment
+1. Это должно быть довольно просто с хорошо зарекомендовавшими себя инструментами для решения проблемы. - person Louis Wasserman; 19.09.2012
comment
И еще может привести к OutOfMemory - person Lukasz Madon; 20.09.2012
comment
@lukas, как ты это видишь? Сортировка слиянием может занимать очень мало оперативной памяти. - person Tobias Ritzau; 20.09.2012

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

Обработайте каждый из файлов писем отдельно, используя Set для удаления дубликатов.

person Gilbert Le Blanc    schedule 19.09.2012
comment
Это предполагает, что Q встречается так же часто, как A, или вы можете перебрать 10 миллионов слов, которые подходят для некоторых букв. - person Joachim Isaksson; 19.09.2012
comment
@ Йоахим Исакссон: Хорошо. Разделите самые большие файлы по первым двум буквам. - person Gilbert Le Blanc; 19.09.2012
comment
Я считаю это решение более сложным для объяснения и более сложным для реализации, чем простые решения на основе сортировки, предлагаемые другими. Сортировка больших файлов на диске - обычная задача с готовыми реализациями. Целое разделите большие файлы, если они все еще слишком велики, требует либо дополнительного кода, либо ручного вмешательства. На самом деле намного проще пойти дальше, отсортировать все и покончить с этим. - person John Y; 20.09.2012
comment
Не считая значения, упомянутого Джоном Y, вы можете разделить свой файл на основе hashcode ()% n на разумное число n. - person Buhb; 20.09.2012
comment
@JohnY & Buhb: Мой метод гарантирует, что в нескольких файлах не будет повторяющихся слов. Все отдельные повторяющиеся слова будут в одном файле. - person Gilbert Le Blanc; 20.09.2012
comment
@GilbertLeBlanc: Но OP начинается с одного гигантского файла. Если мы отсортируем этот один файл, мы даже не введем понятие дубликатов в нескольких файлах. - person John Y; 20.09.2012
comment
@GilbertLeBlanc Я просто обращаюсь к проблеме, которая возникает, если 99% слов начинаются с одной и той же буквы. Поскольку ваше редактирование предлагает рекурсивное подразделение файлов, это больше не проблема. - person Buhb; 21.09.2012

Возможно, вы сможете использовать структуру данных trie, чтобы выполнить задание за один проход. У него есть преимущества, которые рекомендуют его для этого типа проблем. Поиск и вставка выполняются быстро. И его представление относительно компактно. Возможно, вы сможете представить все свои слова в ОЗУ.

person gregg    schedule 19.09.2012
comment
На данный момент это одно из самых интересных предложений. У вас может закончиться оперативная память, и тогда вам нужно будет найти совершенно новое решение, но это, по крайней мере, дает некоторую надежду на сохранение всех уникальных строк в памяти, что удобно. - person Buhb; 21.09.2012
comment
вам по-прежнему нужно более одного узла для отдельного слова - также не менее 8 байтов, даже если вы не храните сами строки, и массив ссылок для узла - person Konstantin Pribluda; 09.10.2012

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

Здесь есть код, который можно использовать для сортировки большого файла: http://www.codeodor.com/index.cfm/2007/5/10/Sorting-really-BIG-files/1194

person ᴇʟᴇvᴀтᴇ    schedule 19.09.2012

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

Ознакомьтесь с этой статьей:

http://javarevisited.blogspot.com/2012/01/memorymapped-file-and-io-in-java.html

person Dave Rager    schedule 19.09.2012

Вопрос: Это действительно СЛОВА или что-то еще - фразы, номера деталей и т. Д.?

Для СЛОВ на обычном разговорном языке можно было бы ожидать, что после первой пары тысяч вы найдете большинство уникальных слов, поэтому все, что вам действительно нужно сделать, это прочитать слово, проверить его по словарю, если найдено, пропустить если не найден, добавьте его в словарь и выпишите.

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

person Hot Licks    schedule 19.09.2012

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

person Carlos Cambón    schedule 19.09.2012

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

Именно так работали ранние средства проверки правописания. Они знали, есть ли слово в словаре, но не могли сказать вам, как правильно написано, потому что он говорит вам только о том, видно ли текущее слово.

Существует ряд реализаций с открытым исходным кодом, включая java-bloomfilter.

person Paul Rubel    schedule 19.09.2012
comment
Как бы вы удостоверились, что это действительно дубликат (а не ложное срабатывание)? - person Tobias Ritzau; 19.09.2012
comment
Вы можете установить произвольно низкую вероятность за счет памяти. К сожалению, это цена, которую вы платите за вероятностный алгоритм. С учетом ваших ограничений, размера данных и того факта, что вам не нужно проверять дополнительные элементы после факта, решения для сортировки, вероятно, будут более подходящими. - person Paul Rubel; 20.09.2012
comment
Фильтр Блума был бы излишне неточным. - person NovaDenizen; 20.09.2012
comment
@TobiasRitzau Если фильтр Блума указывает, что слово, вероятно, является дубликатом, вы можете (за большие деньги) вернуться к началу выходного файла и просканировать его в поисках слова, чтобы убедиться, что оно действительно существует. - person Samuel Edwin Ward; 20.09.2012
comment
Я на самом деле не использовал фильтры цветения, но я думал о решении, в котором вы делаете один проход, чтобы создать фильтр для набора, и выполняете второй проход, где вы сохраняете промахи в выходном файле. Хиты хранятся в наборе и записываются только в том случае, если он еще не существует в наборе. Поскольку там, где дублируется только около 1%, это должно сработать. Или? - person Tobias Ritzau; 20.09.2012

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

Вот что я имею в виду (в псевдокоде):

  • Входные параметры: Offset, Size
  • Выделить доступную для поиска структуру размера Size (= Set, но не обязательно)
  • Прочтите элементы Offset (или обнаружен EOF) из stdin и просто скопируйте их в stdout
  • Прочтите Size элементов из стандартного ввода (или EOF), сохраните их в Set. Если дублируются, отбросьте, иначе напишите в stdout.
  • Считывать элементы со стандартного ввода до EOF, если они находятся в Set, затем отбрасывать, иначе записывать в стандартный вывод

Теперь направьте столько экземпляров, сколько вам нужно (если с хранилищем проблем нет, может быть, столько, сколько у вас ядер) с увеличением Offsets и разумным Size. Это позволяет использовать больше ядер, поскольку я подозреваю, что процесс связан с процессором. Вы даже можете использовать netcat и распределить обработку между несколькими машинами, если вы спешите.

person Eugen Rieck    schedule 19.09.2012

Даже в английском языке, который имеет огромное количество слов для естественного языка, верхние оценки составляют всего около 80000 слов. Исходя из этого, вы можете просто использовать HashSet и добавить все свои слова (возможно, в нижнем регистре, чтобы избежать проблем с регистром):

Set<String> words = new HashSet<String>();
while (read-next-word) {
    words.add(word.toLowerCase());
}

Если это настоящие слова, это не вызовет проблем с памятью, тоже будет довольно быстро!

person Bohemian♦    schedule 20.09.2012
comment
Это тоже первое, о чем я подумал, но в теме он говорит, что уже пробовали с Сетами и потерпели неудачу. Это не должны быть настоящие слова - person enTropy; 22.09.2012

Чтобы не беспокоиться о реализации, вам следует использовать систему баз данных, будь то простой старый реляционный SQL или решение без SQL. Я почти уверен, что вы могли бы использовать, например, Berkeley DB java edition, а затем делать (псевдокод)

for(word : stream) {
  if(!DB.exists(word)) {
     DB.put(word)
     outstream.add(word)
  }
}

Проблема, по сути, проста: вам нужно хранить вещи на диске, потому что недостаточно памяти, затем либо используйте сортировку O (N log N) (необязательно), либо хеширование O (N), чтобы найти уникальные слова.

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

Последующий вопрос к какому-то умному парню, что, если у меня есть 64-битная машина и размер кучи установлен на 12 ГБ, виртуальная память не должна решать проблему (хотя и не оптимальным образом) или java не спроектирован Сюда?

person user1443778    schedule 20.09.2012

В этом случае быстрая сортировка была бы хорошим вариантом по сравнению с сортировкой слиянием, потому что для этого требуется меньше памяти. В этой ветке есть хорошее объяснение того, почему.

person Evo510    schedule 19.09.2012
comment
Но быстрая сортировка - это сортировка в памяти, а для сортировки слиянием действительно требуется достаточно оперативной памяти, чтобы удерживать 2 буфера чтения и буфер записи. - person NovaDenizen; 20.09.2012

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

Итак, ваша проблема сводится к действительно большому разреженному заполненному растровому изображению - размер которого зависит от ширины хэша. Если ваш хэш до 32 бит, вы можете использовать растровое изображение riak.

... забыл о действительно большом растровом изображении для 128+ битных хэшей%) (я вернусь)

person Konstantin Pribluda    schedule 09.10.2012