Подтвердить что ты не робот

Разбиение строк на Java

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

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

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

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

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

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

4b9b3361

Ответ 1

String.split(String) не будет создавать regexp, если ваш шаблон имеет длину всего один символ. При разбиении на один символ он будет использовать специализированный код, который довольно эффективен. 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

Ответ 2

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

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

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

Ответ 3

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();

Ответ 4

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

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

Ответ 5

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

Ответ 6

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

Я работаю с файлом размером 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++;
    }

Ответ 7

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

Ответ 8

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

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

Ответ 9

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

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

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

Ответ 10

Использование Apache Commons Lang "3.0

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

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

  • StringUtils - 8ms
  • String - 11ms
  • Splitter - 1ms (но возвращает Iterable/Iterator и преобразование их в строковый массив занимает в общей сложности 54 мс)