Как заменить строку только один раз без регулярных выражений в Java? - PullRequest
22 голосов
/ 24 октября 2009

Мне нужно заменить динамическую подстроку большей строкой, но только один раз (т. Е. Первое совпадение). Класс String предоставляет только replace(), который заменяет ВСЕ экземпляры подстроки; есть метод replaceFirst(), но он принимает только регулярное выражение вместо обычной строки. У меня есть две проблемы с использованием регулярных выражений:

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

2) эта замена происходит очень часто, и я не уверен, повлияет ли использование регулярных выражений на производительность. Я не могу скомпилировать регулярное выражение заранее, так как само регулярное выражение является динамическим!

Я, должно быть, что-то здесь упускаю, так как это кажется мне очень простой вещью ... Есть ли метод replaceFirst, принимающий обычную строку где-нибудь еще в java franework?

Ответы [ 7 ]

26 голосов
/ 02 июня 2012

Вы должны использовать уже протестированные и хорошо документированные библиотеки в пользу написания собственного кода!

StringUtils.replaceOnce("aba", "a", "")    = "ba"

Класс StringUtils относится к пакету Apache Commons Lang3 и может быть импортирован в Maven следующим образом:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>
16 голосов
/ 02 декабря 2009

Как предложил Лоуренс, вы можете использовать Pattern.quote так:

newString = string.replaceFirst(Pattern.quote(substring), replacement);

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

newString = Pattern.compile(substring, Pattern.LITERAL).
        matcher(string).replaceFirst(replacement);

Это может быть немного более эффективно, но и немного менее понятно.

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

13 голосов
/ 24 октября 2009

Используйте bigString.indexof(smallString), чтобы получить индекс первого вхождения малой строки в большую (или -1, если нет, в этом случае все готово). Затем используйте bigString.substring, чтобы получить фрагменты большой строки до и после матча, и, наконец, concat, чтобы соединить их до и после фрагментов вместе с предполагаемой заменой в середине.

5 голосов
/ 28 сентября 2012

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

newStr = str.replaceFirst(Pattern.quote(target), Matcher.quoteReplacement(replacement));
2 голосов
/ 22 марта 2017

Решение с Pattern

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

/**
 * Replaces the first subsequence of the <tt>source</tt> character sequence
 * that matches the literal target sequence with the specified literal
 * replacement sequence.
 * 
 * @param source source sequence on which the replacement is made
 * @param target the sequence of char values to be replaced
 * @param replacement the replacement sequence of char values
 * @return the resulting string
 */
private static String replaceFirst1(CharSequence source, CharSequence target, CharSequence replacement) {
    return Pattern.compile(target.toString(), Pattern.LITERAL).matcher(
        source).replaceFirst(Matcher.quoteReplacement(replacement.toString()));
}


Из документации Pattern.LITERAL :

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


Раствор без Pattern

Другой, более эффективный способ, конечно, заключается в использовании подсказки Алекса Мартелли для получения следующей функциональности:

/**
 * Replaces the first subsequence of the <tt>source</tt> string that matches
 * the literal target string with the specified literal replacement string.
 * 
 * @param source source string on which the replacement is made
 * @param target the string to be replaced
 * @param replacement the replacement string
 * @return the resulting string
 */
private static String replaceFirst2(String source, String target, String replacement) {
    int index = source.indexOf(target);
    if (index == -1) {
        return source;
    }

    return source.substring(0, index)
        .concat(replacement)
        .concat(source.substring(index+target.length()));
}


Измерение времени

На основе 10 запусков метод replaceFirst2 выполняется примерно в 5 раз быстрее , чем метод replaceFirst1. Я поместил оба этих метода в цикл с 100 000 повторений и получил следующие результаты в миллисекундах:

    Method                 Results (in ms)             Average
replaceFirst1: 220 187 249 186 199 211 172 199 281 199 | 210
replaceFirst2:  40  39  58  45  48  40  42  42  43  59 |  45
2 голосов
/ 08 апреля 2014

Класс StringBuilder в Java можно очень легко использовать для замены одной строки на другую без регулярных выражений.

private static String replace(String in, String ths, String that) {

    StringBuilder sb = new StringBuilder(in);

    int idx = sb.indexOf(ths); 

    while (idx > -1)
        sb.replace(idx, idx + ths.length(), that);
        idx = sb.indexOf(ths);
    }

    return sb.toString(); 

}
1 голос
/ 10 июля 2013

Pattern.quote, похоже, не работает во всех случаях. Попробуйте `Pattern.quote (" :-) ");

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...