Найшвидший спосіб розділити обмежений рядок на Java


10

Я будую компаратор, який надає можливість сортування в декількох стовпцях для обмеженої рядки. В даний час я використовую метод розбиття з класу String як мій кращий вибір для розбиття необробленого рядка на лексеми.

Це найкращий спосіб перетворити необроблений рядок в масив String? Я буду сортувати мільйони рядків, тому думаю, що підхід має значення.

Здається, працює добре і дуже просто, але не впевнений, що в Java є швидший шлях.

Ось як працює сортування в моєму компараторі:

public int compare(String a, String b) {

    String[] aValues = a.split(_delimiter, _columnComparators.length);
    String[] bValues = b.split(_delimiter, _columnComparators.length);
    int result = 0;

    for( int index : _sortColumnIndices ) {
        result = _columnComparators[index].compare(aValues[index], bValues[index]);
        if(result != 0){
            break;
        }
    }
    return result;
}

Після порівняльного аналізу різних підходів, вірите чи ні, метод спліт був найшвидшим із використанням останньої версії Java. Ви можете завантажити мій завершений компаратор тут: https://sourceforge.net/projects/multicolumnrowcomparator/


5
Я зазначу, що характер відповіді на це питання залежить від реалізації jvm. Поведінка рядків (спільний загальний резервний масив у OpenJDK, але не в OracleJDK) відрізняється. Ця різниця може мати суттєвий вплив на розбиття рядків та створення підрядків, поряд із збиранням сміття та витоком пам’яті. Наскільки великі ці масиви? Як ти це робиш зараз? Чи розглядаєте ви відповідь, яка спричиняє новий тип строгінгу, а не фактичні рядки Java?

1
Зокрема, подивіться на StringTokenizer nextToken, який врешті-решт викликає пакетний приватний конструктор String . Порівняйте це зі змінами, задокументованими у внутрішніх представленнях "Зміни до рядків", внесеними на Java 1.7.0_06

Розмір масиву залежить від кількості стовпців, тому він є змінним. Цей багатоколонковий компаратор передається як такий параметр: ExternalSort.mergeSortedFiles (fileList, новий файл ("BigFile.csv"), _comparator, Charset.defaultCharset (), false); Звичайний режим сортування буде сортувати весь рядок рядків, насправді компаратор робить розбиття та сортування на основі стовпців сортування
Константин

Я б розглядав люкенові токенізатори. Люцен можна використовувати як просто потужну бібліотеку аналізу тексту, яка добре працює як для простих, так і складних завдань
Doug T.

Розглянемо Apache Commons Lang's StringUtils.split[PreserveAllTokens](text, delimiter).
Відновіть Моніку

Відповіді:


19

Я написав для цього швидкий і брудний тест на тест. У ньому порівнюються 7 різних методів, деякі з яких потребують конкретних знань про поділ даних.

Для базового розщеплення загального призначення, Guava Splitter на 3,5 рази швидше, ніж String # split (), і я рекомендую використовувати його. Стрингтокенізатор трохи швидший за це, і розділити себе за допомогою indexOf вдвічі швидше, ніж знову.

Для коду та додаткової інформації див. Http://demeranville.com/battle-of-the-tokenizers-delimited-text-parser-performance/


Мені просто цікаво, який JDK ви використовували ... і якщо він був 1.6, мені було б найбільше цікаво переглянути резюме ваших результатів у 1.7.

1
це було 1.6 я думаю. Код існує як тест JUnit, якщо ви хочете запустити його в 1.7. Примітка. String.split виконує відповідність регулярних виразів, яка завжди буде повільніше, ніж розділення на один визначений символ.
Том

1
Однак для 1.6, код StringTokenizer (і подібний) викликає String.substring (), який робить O (1) створення нової рядки, використовуючи той самий резервний масив. Це було змінено в 1.7, щоб зробити копію необхідної частини резервного масиву, а не для O (n). Це може мати неабиякий вплив на ваші результати, зменшуючи різницю між спліт та StringTokenizer (уповільнюючи все, що раніше використовувалося підрядкою).

1
Безумовно, правда. Річ у тім, що StringTokenizer пішов від "створити нову строку присвоїти 3 цілих числа" до "створити нову рядок, зробіть копію масиву даних", яка змінить, наскільки швидка ця частина. Зараз різниця між різними підходами може бути меншою, і було б цікаво (якщо не з іншої причини, крім її цікавої) зробити подальший підйом з Java 1.7.

1
Дякую за цю статтю! Дуже корисно і використовуватиме для порівняння різних підходів.
Костянтин

5

Як пише @Tom, підхід типу indexOf швидший String.split(), оскільки останній має справу з регулярними виразами і має для них багато зайвих накладних витрат.

Однак одна зміна алгоритму, яка може забезпечити вам надшвидке прискорення. Якщо припустити, що цей компаратор буде використовуватися для сортування ~ 100 000 рядків, не пишіть Comparator<String>. Тому що під час вашого сорту одна і та ж струна, ймовірно, буде порівнюватися декілька разів, тому ви розділите її кілька разів тощо.

Розділіть всі рядки один раз на String [] s і встановіть Comparator<String[]>сортування String []. Потім, наприкінці, ви можете їх об'єднати всі разом.

Крім того, ви також можете використовувати Map для кешування рядка -> String [] або навпаки. наприклад (схематичний) Також зверніть увагу, ви торгуєте пам’яттю на швидкість, сподіваюся, у вас є лота оперативна пам’ять

HashMap<String, String[]> cache = new HashMap();

int compare(String s1, String s2) {
   String[] cached1 = cache.get(s1);
   if (cached1  == null) {
      cached1 = mySuperSplitter(s1):
      cache.put(s1, cached1);
   }
   String[] cached2 = cache.get(s2);
   if (cached2  == null) {
      cached2 = mySuperSplitter(s2):
      cache.put(s2, cached2);
   }

   return compareAsArrays(cached1, cached2);  // real comparison done here
}

це хороший момент.
tom

Було б потрібно змінити код зовнішнього сортування, який можна знайти тут: code.google.com/p/externalsortinginjava
Константин

1
Мабуть, найпростіше тоді використовувати Карту. Див. Редагування.
user949300

З огляду на те, що це частина двигуна зовнішнього сортування (для обробки набагато більше даних, ніж можливо, вміщено у доступній пам'яті), я дійсно збирався після ефективного "спліттера" (так, марно багато разів поділяти ту саму струну, отже, мій Початкова потреба зробити це якомога швидше)
Константин

Коротко переглянувши код ExternalSort, схоже, якщо ви очистили кеш у кінці (або на початку) кожного sortAndSave()дзвінка, тоді вам не слід втрачати пам'ять через величезний кеш. IMO, у коді повинно бути кілька додаткових гачків, таких як події стрілянини або виклик методів, що не захищають нічого, що користувачі, як ви, могли перекрити. (Крім того, це не повинні бути всі статичні методи, щоб вони могли це зробити ) Ви можете звернутися до авторів і подати запит.
user949300

2

Відповідно до цих орієнтирів , StringTokenizer швидше розбиває рядки, але він не повертає масив, що робить його менш зручним.

Якщо вам потрібно сортувати мільйони рядків, я рекомендую використовувати RDBMS.


3
Це було в JDK 1.6 - речі в рядках принципово відрізняються в 1.7 - див. Java-performance.info/changes-to-string-java-1-7-0_06 (зокрема, створення підрядки вже не є O (1), але скоріше O (n)). Посилання зазначає, що в 1.6 Pattern.split використовується інше створення String, ніж String.substring ()) - див. Код, пов'язаний у коментарі вище, щоб слідкувати за StringTokenizer.nextToken () та приватним конструктором пакету, до якого він мав доступ.

1

Це метод, який я використовую для аналізу великих файлів з обмеженими вкладками (1 ГБ +). Він має набагато менші накладні витрати, ніж String.split()обмежувач, charяк обмежувач. Якщо хтось має більш швидкий метод, я хотів би його побачити. Це також можна зробити над CharSequenceі CharSequence.subSequence, але для цього потрібна реалізація CharSequence.indexOf(char)(див. Метод упаковки, String.indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)якщо ви зацікавлені).

public static String[] split(final String line, final char delimiter)
{
    CharSequence[] temp = new CharSequence[(line.length() / 2) + 1];
    int wordCount = 0;
    int i = 0;
    int j = line.indexOf(delimiter, 0); // first substring

    while (j >= 0)
    {
        temp[wordCount++] = line.substring(i, j);
        i = j + 1;
        j = line.indexOf(delimiter, i); // rest of substrings
    }

    temp[wordCount++] = line.substring(i); // last substring

    String[] result = new String[wordCount];
    System.arraycopy(temp, 0, result, 0, wordCount);

    return result;
}

Ви орієнтували це на String.split ()? Якщо так, то як вона порівнюється?
Джей Елстон

@JayElston У файлі 900 Мб скорочено час розбиття з 7,7 секунди до 6,2 секунди, тобто на 20% швидше. Це все ще найповільніша частина мого розбору матриць з плаваючою комою. Я здогадуюсь, що більша частина часу, що залишився - це розподіл масиву. Можливо, можливо вирізати розподіл матриці за допомогою підходу, заснованого на токенізаторі, із зміщенням у методі - який би почав більше нагадувати метод, який я цитував вище коду.
vallismortis
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.