Як замінити нечутливі до регістру буквальні підрядки в Java


130

replace(CharSequence target, CharSequence replacement)Як я можу зробити цільовий регістр нечутливим за допомогою методу в String?

Наприклад, як це працює зараз:

String target = "FooBar";
target.replace("Foo", "") // would return "Bar"

String target = "fooBar";
target.replace("Foo", "") // would return "fooBar"

Як я можу зробити так, щоб це замінило (або якщо є більш підходящий метод) нечутливим до регістру, щоб обидва приклади повернули "Бар"?

Відповіді:


284
String target = "FOOBar";
target = target.replaceAll("(?i)foo", "");
System.out.println(target);

Вихід:

Bar

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


1
Що робити, якщо ціль містить $ або діакритичні символи, такі як á?
stracktracer

3
Я маю на увазі дві речі: 1. "blÁÜ123" .replaceAll ("(? I) bláü") нічого не замінює. 2. "Речення! Кінець". Замінити Все ("(? I) Речення.") Може замінити більше, ніж передбачалося.
stracktracer

1
Ви не можете перетворити рядок у регулярний вираз, який відповідає йому настільки просто. Загалом це неправильно, воно працюватиме лише для конкретних випадків.
Дунайський матрос

19
Використовуйте Pattern.quote (), щоб захистити рядок пошуку від інтерпретації як регулярний вираз. Цей соплі-соплі вирішують перелічені вище прикмети unicode, але для основних наборів символів має бути добре. напр. target.replaceAll("(?i)"+Pattern.quote("foo"), "");
Джефф Адамсон

1
Просто переконуюсь. Pattern.quote ("foo") не потрібен, якщо рядок "foo" правильно? Тільки якщо це щось більш вигадливе, правда?
ed22


10

Не такий елегантний, як інші підходи, але досить міцний і простий у дотриманні, особливо. для людей, які новіші для Java. Одне, що мене цікавить про клас String, це: це існує вже дуже давно, і він підтримує глобальну заміну на регулярний вираз і глобальну заміну на Strings (через CharSequences), але останній не має простого булевого параметра : 'isCaseInsensitive'. Дійсно, ви могли подумати, що, додавши цей маленький перемикач, усіх проблем, які його відсутність викликає, особливо для початківців, можна було б уникнути. Тепер на JDK 7, String досі не підтримує це одне маленьке доповнення!

Ну все одно я перестану хапатись. Для всіх, особливо новіших для Java, ось ваш виріз і вставка deus ex machina . Як я вже говорив, не настільки елегантно і не виграє вам жодного призового кодування, але це працює і є надійним. Будь-які коментарі, не соромтесь робити внесок. (Так, я знаю, StringBuffer - це, мабуть, кращий вибір керування двома символьними рядками мутації рядків, але досить просто поміняти методи.)

public String replaceAll(String findtxt, String replacetxt, String str, 
        boolean isCaseInsensitive) {
    if (str == null) {
        return null;
    }
    if (findtxt == null || findtxt.length() == 0) {
        return str;
    }
    if (findtxt.length() > str.length()) {
        return str;
    }
    int counter = 0;
    String thesubstr = "";
    while ((counter < str.length()) 
            && (str.substring(counter).length() >= findtxt.length())) {
        thesubstr = str.substring(counter, counter + findtxt.length());
        if (isCaseInsensitive) {
            if (thesubstr.equalsIgnoreCase(findtxt)) {
                str = str.substring(0, counter) + replacetxt 
                    + str.substring(counter + findtxt.length());
                // Failing to increment counter by replacetxt.length() leaves you open
                // to an infinite-replacement loop scenario: Go to replace "a" with "aa" but
                // increment counter by only 1 and you'll be replacing 'a's forever.
                counter += replacetxt.length();
            } else {
                counter++; // No match so move on to the next character from
                           // which to check for a findtxt string match.
            }
        } else {
            if (thesubstr.equals(findtxt)) {
                str = str.substring(0, counter) + replacetxt 
                    + str.substring(counter + findtxt.length());
                counter += replacetxt.length();
            } else {
                counter++;
            }
        }
    }
    return str;
}

цей метод надзвичайно повільний, оскільки його складність становить O (size_str * size_findtext)
Младен Адамович

9

Регулярні вирази досить складні для управління через те, що деякі символи зарезервовані: наприклад, "foo.bar".replaceAll(".")видає порожній рядок, оскільки крапка означає "що завгодно". Якщо ви хочете замінити лише крапку, слід вказати як параметр "\\.".

Більш просте рішення - використовувати об'єкти StringBuilder для пошуку та заміни тексту. Потрібно два: один, який містить текст у малій версії, а другий - у вихідній. Пошук здійснюється за малим вмістом, і виявлений індекс також замінить початковий текст.

public class LowerCaseReplace 
{
    public static String replace(String source, String target, String replacement)
    {
        StringBuilder sbSource = new StringBuilder(source);
        StringBuilder sbSourceLower = new StringBuilder(source.toLowerCase());
        String searchString = target.toLowerCase();

        int idx = 0;
        while((idx = sbSourceLower.indexOf(searchString, idx)) != -1) {
            sbSource.replace(idx, idx + searchString.length(), replacement);
            sbSourceLower.replace(idx, idx + searchString.length(), replacement);
            idx+= replacement.length();
        }
        sbSourceLower.setLength(0);
        sbSourceLower.trimToSize();
        sbSourceLower = null;

        return sbSource.toString();
    }


    public static void main(String[] args)
    {
        System.out.println(replace("xXXxyyyXxxuuuuoooo", "xx", "**"));
        System.out.println(replace("FOoBaR", "bar", "*"));
    }
}

1
Чудово працює! Зауважте, що "ціль" не повинна бути нульовою. Очищення sbSourceLower не повинно бути необхідним (більше).
msteiger

Дякуємо за стисле рішення та дякуємо @msteiger за виправлення. Цікаво, чому ніхто не додав подібного рішення жодному відомому лібюру, як Guava, Apache Commons тощо?
іншийкодер

4

Для символів, які не використовуються Unicode:

String result = Pattern.compile("(?i)препарат", 
Pattern.UNICODE_CASE).matcher(source).replaceAll("БАД");

4

org.apache.commons.lang3.StringUtils:

загальнодоступний статичний рядок замінаIgnoreCase (текст рядка, пошук рядківString, заміна рядка)

Випадок нечутливо замінює всі виникнення рядка в іншій рядку.


3

Мені подобається SMAS «и відповідь , який використовує replaceAllз регулярним виразом. Якщо ви будете робити одну і ту ж заміну багато разів, має сенс попередньо скласти регулярний вираз один раз:

import java.util.regex.Pattern;

public class Test { 

    private static final Pattern fooPattern = Pattern.compile("(?i)foo");

    private static removeFoo(s){
        if (s != null) s = fooPattern.matcher(s).replaceAll("");
        return s;
    }

    public static void main(String[] args) {
        System.out.println(removeFoo("FOOBar"));
    }
}

3

Просто зробити це просто без сторонніх бібліотек:

    final String source = "FooBar";
    final String target = "Foo";
    final String replacement = "";
    final String result = Pattern.compile(target, Pattern.LITERAL | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE).matcher(source)
.replaceAll(Matcher.quoteReplacement(replacement));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.