it-swarm.com.ru

Java разделить строковые представления

Вот текущий код в моем приложении:

String[] ids = str.split("/");

При профилировании приложения я заметил, что на разделение строки тратится немалое время.

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

Итак, мой вопрос, какую альтернативу я могу использовать для оптимизации разделения строк? Я видел StringUtils.split, но быстрее ли?

(Я бы попробовал и проверил себя, но профилирование моего приложения занимает много времени, поэтому, если кто-то уже знает ответ, который сэкономит время)

36
Matthieu Napoli

String.split(String) не создаст регулярное выражение, если длина вашего шаблона составляет всего один символ. При разбиении по одному символу он будет использовать специализированный код, который довольно эффективен. StringTokenizer не намного быстрее в этом конкретном случае.

Это было введено в OpenJDK7/OracleJDK7. Вот отчет об ошибке и коммит . Я сделал простой тест здесь .


$ Java -version
Java version "1.8.0_20"
Java(TM) SE Runtime Environment (build 1.8.0_20-b26)
Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode)

$ Java Split
split_banthar: 1231
split_tskuzzy: 1464
split_tskuzzy2: 1742
string.split: 1291
StringTokenizer: 1517
36
Piotr Praszmo

Если вы можете использовать сторонние библиотеки, Guava'sSplitter не несет накладных расходов на регулярные выражения, когда вы их не запрашиваете, и, как правило, работает очень быстро. (Раскрытие: я помогаю гуаве.)

Iterable<String> split = Splitter.on('/').split(string);

(Кроме того, Splitter как правило гораздо более предсказуемо , чем String.split.)

17
Louis Wasserman

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

StringTokenizer st = new StringTokenizer("1/2/3","/");
String[] arr = st.countTokens();
arr[0] = st.nextToken();

Если вы хотите повысить производительность, вы также можете сделать это вручную:

String s = "1/2/3"
char[] c = s.toCharArray();
LinkedList<String> ll = new LinkedList<String>();
int index = 0;

for(int i=0;i<c.length;i++) {
    if(c[i] == '/') {
        ll.add(s.substring(index,i));
        index = i+1;
    }
}

String[] arr = ll.size();
Iterator<String> iter = ll.iterator();
index = 0;

for(index = 0; iter.hasNext(); index++)
    arr[index++] = iter.next();
8
tskuzzy

Java.util.StringTokenizer(String str, String delim) примерно в два раза быстрее согласно этот пост .

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

3
purtip31

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

Я работаю с файлом 426 МБ, с 2622761 строк. Единственным пробелом являются нормальные пробелы ("") и строки ("\ n").

Сначала я заменяю все строки пробелами и анализирую одну огромную строку:

.split(" ")
Cumulative time: 31.431366952 seconds

.split("\s")
Cumulative time: 52.948729489 seconds

splitStringChArray()
Cumulative time: 38.721338004 seconds

splitStringChList()
Cumulative time: 12.716065893 seconds

splitStringCodes()
Cumulative time: 1 minutes, 21.349029036000005 seconds

splitStringCharCodes()
Cumulative time: 23.459840685 seconds

StringTokenizer
Cumulative time: 1 minutes, 11.501686094999997 seconds

Затем я тестирую разбиение строки за строкой (это означает, что функции и циклы выполняются много раз, а не все сразу):

.split(" ")
Cumulative time: 3.809014174 seconds

.split("\s")
Cumulative time: 7.906730124 seconds

splitStringChArray()
Cumulative time: 4.06576739 seconds

splitStringChList()
Cumulative time: 2.857809996 seconds

Bonus: splitStringChList(), but creating a new StringBuilder every time (the average difference is actually more like .42 seconds):
Cumulative time: 3.82026621 seconds

splitStringCodes()
Cumulative time: 11.730249921 seconds

splitStringCharCodes()
Cumulative time: 6.995555826 seconds

StringTokenizer
Cumulative time: 4.500008172 seconds

Вот код:

// Use a char array, and count the number of instances first.
public static String[] splitStringChArray(String str, StringBuilder sb) {
    char[] strArray = str.toCharArray();
    int count = 0;
    for (char c : strArray) {
        if (c == ' ') {
            count++;
        }
    }
    String[] splitArray = new String[count+1];
    int i=0;
    for (char c : strArray) {
        if (c == ' ') {
            splitArray[i] = sb.toString();
            sb.delete(0, sb.length());
        } else {
            sb.append(c);
        }
    }
    return splitArray;
}

// Use a char array but create an ArrayList, and don't count beforehand.
public static ArrayList<String> splitStringChList(String str, StringBuilder sb) {
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    char[] strArray = str.toCharArray();
    int i=0;
    for (char c : strArray) {
        if (c == ' ') {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(c);
        }
    }
    return words;
}

// Using an iterator through code points and returning an ArrayList.
public static ArrayList<String> splitStringCodes(String str) {
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    IntStream is = str.codePoints();
    OfInt it = is.iterator();
    int cp;
    StringBuilder sb = new StringBuilder();
    while (it.hasNext()) {
        cp = it.next();
        if (cp == 32) {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(cp);
        }
    }

    return words;
}

// This one is for compatibility with supplementary or surrogate characters (by using Character.codePointAt())
public static ArrayList<String> splitStringCharCodes(String str, StringBuilder sb) {
    char[] strArray = str.toCharArray();
    ArrayList<String> words = new ArrayList<String>();
    words.ensureCapacity(str.length()/5);
    int cp;
    int len = strArray.length;
    for (int i=0; i<len; i++) {
        cp = Character.codePointAt(strArray, i);
        if (cp == ' ') {
            words.add(sb.toString());
            sb.delete(0, sb.length());
        } else {
            sb.append(cp);
        }
    }

    return words;
}

Вот как я использовал StringTokenizer:

    StringTokenizer tokenizer = new StringTokenizer(file.getCurrentString());
    words = new String[tokenizer.countTokens()];
    int i = 0;
    while (tokenizer.hasMoreTokens()) {
        words[i] = tokenizer.nextToken();
        i++;
    }
2
Joshua Mathias

StringTokenizer работает быстрее, чем любой другой метод разбиения, но получение токенайзером для возврата разделителей вместе со строкой токенов повышает производительность примерно на 50%. Это достигается с помощью конструктора Java.util.StringTokenizer.StringTokenizer(String str, String delim, boolean returnDelims). Вот некоторые другие идеи по этому вопросу: Производительность класса StringTokenizer против метода split в Java

2
cristianoms

В Guava есть Splitter , который более гибок, чем метод String.split(), и не использует (обязательно) регулярное выражение. OTOH, String.split() был оптимизирован в Java 7, чтобы избежать механизма регулярных выражений, если разделитель представляет собой один символ. Таким образом, производительность должна быть аналогичной в Java 7.

1
JB Nizet

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

StringTokenizer - чтение строк с целыми числами

Разделение: 366 мс IndexOf: 50 мс StringTokenizer: 89 мс GuavaSplit: 109 мс IndexOf2 (какое-то супероптимизированное решение, приведенное в предыдущем вопросе): 14 мс CsvMapperSplit (отображение строки за строкой): 326 мс CsvMapperSplit_DOC (создание одного документа и отображение всех строк за один раз): 177 мс

0
sanketshah

Использовать Apache Commons Lang »3.0 

StringUtils.splitByWholeSeparator("ab-!-cd-!-ef", "-!-") = ["ab", "cd", "ef"]

Если вам нужно разделение без регулярных выражений и вы хотите получить результаты в массиве String, тогда используйте StringUtils, я сравнил StringUtils.splitByWholeSeparator с разделителем Guava и разделением Java String, и обнаружил, что StringUtils работает быстрее.

  1. StringUtils - 8 мс
  2. Строка - 11мс 
  3. Splitter - 1 мс (но возвращает Iterable/Iterator и преобразование их в строковый массив занимает всего 54 мс)
0
Bruce

Метод разделения String, вероятно, является более безопасным выбором. По крайней мере, в Java 6 (хотя api-ссылка для 7) они в основном говорят, что использование StringTokenizer не рекомендуется. Их формулировка приводится ниже.

«StringTokenizer - это устаревший класс, который сохраняется по соображениям совместимости, хотя его использование не рекомендуется в новом коде. Рекомендуется всем, кто ищет эту функциональность, использовать вместо этого метод split String или пакет Java.util.regex.»

0
John Kane