Bagaimana cara mengganti substring literal case-insensitive di Jawa

130

Menggunakan metode replace(CharSequence target, CharSequence replacement)dalam String, bagaimana saya bisa membuat target case-insensitive?

Misalnya, cara kerjanya sekarang:

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

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

Bagaimana saya bisa membuatnya jadi ganti (atau jika ada metode yang lebih cocok) adalah case-insensitive sehingga kedua contoh mengembalikan "Bar"?

J. Lin
sumber

Jawaban:

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

Keluaran:

Bar

Patut disebutkan bahwa replaceAllmemperlakukan argumen pertama sebagai pola regex, yang dapat menyebabkan hasil yang tidak terduga. Untuk mengatasi ini, gunakan juga Pattern.quoteseperti yang disarankan dalam komentar.

lukastymo
sumber
1
Bagaimana jika target berisi karakter $ atau diakritik seperti á?
stracktracer
3
Maksud saya dua hal: 1. "blÁÜ123" .replaceAll ("(? I) bláü") tidak menggantikan apa pun. 2. "Kalimat! Akhiri" .replaceAll ("(? I) Sentence.") Mungkin menggantikan lebih dari yang diantisipasi.
stracktracer
1
Anda tidak dapat mengubah string menjadi regex yang cocok dengan begitu sederhana. Itu tidak benar secara umum, itu hanya akan bekerja untuk kasus-kasus tertentu.
Danubian Sailor
19
Gunakan Pattern.quote () untuk melindungi string pencarian agar tidak ditafsirkan sebagai regex. Doe snot ini membahas quic unicode yang tercantum di atas, tetapi harus baik untuk set karakter dasar. misalnya target.replaceAll("(?i)"+Pattern.quote("foo"), "");
Jeff Adamson
1
Hanya memastikan. Pattern.quote ("foo") tidak diperlukan jika stringnya adalah "foo" bukan? Hanya jika itu sesuatu yang lebih mewah, bukan?
ed22
10

Jika Anda tidak peduli dengan kasus, maka Anda mungkin tidak masalah jika mengembalikan semua huruf besar:

target.toUpperCase().replace("FOO", "");
Hovercraft Penuh Belut
sumber
Anda juga dapat meneruskan Lokal ke toUpperCase (lokal) jika Anda berurusan dengan karakter seperti á.
merampok
10

Tidak seanggun pendekatan lain tetapi cukup solid dan mudah diikuti, esp. untuk orang yang lebih baru ke Jawa. Satu hal yang membuat saya tentang kelas String adalah ini: Ini sudah ada untuk waktu yang sangat lama dan sementara itu mendukung penggantian global dengan regexp dan ganti global dengan Strings (via CharSequences), yang terakhir tidak memiliki parameter boolean sederhana : 'isCaseInsensitive'. Sungguh, Anda akan berpikir bahwa hanya dengan menambahkan satu tombol kecil, semua masalah yang disebabkan ketidakhadirannya untuk pemula terutama bisa dihindari. Sekarang di JDK 7, String masih tidak mendukung penambahan kecil ini!

Baiklah, saya akan berhenti mencengkeram. Untuk semua orang khususnya yang lebih baru ke Jawa, inilah de- ex machina cut-and-paste Anda . Seperti yang saya katakan, tidak elegan dan tidak akan memenangkan hadiah kodekan yang licin, tetapi berhasil dan dapat diandalkan. Setiap komentar, silakan berkontribusi. (Ya, saya tahu, StringBuffer mungkin merupakan pilihan yang lebih baik untuk mengelola dua garis mutasi karakter string, tetapi cukup mudah untuk menukar teknik.)

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;
}
Matt Campbell
sumber
metode ini sangat lambat karena kompleksitasnya adalah O (size_str * size_findtext)
Mladen Adamovic
9

Ekspresi reguler cukup rumit untuk dikelola karena fakta bahwa beberapa karakter dicadangkan: misalnya, "foo.bar".replaceAll(".")menghasilkan string kosong, karena titik berarti "apa pun" Jika Anda ingin mengganti hanya titik yang harus ditunjukkan sebagai parameter "\\.".

Solusi yang lebih sederhana adalah dengan menggunakan objek StringBuilder untuk mencari dan mengganti teks. Dibutuhkan dua: satu yang berisi teks dalam versi huruf kecil sedangkan yang kedua berisi versi asli. Pencarian dilakukan pada konten huruf kecil dan indeks yang terdeteksi juga akan menggantikan teks asli.

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", "*"));
    }
}
ilmassa
sumber
1
Bagus sekali! Perhatikan bahwa "target" tidak boleh nol. Menghapus sbSourceLower tidak perlu (lagi).
msteiger
Terima kasih atas solusi ringkas dan terima kasih kepada @msteiger untuk koreksi. Saya heran mengapa tidak ada yang menambahkan solusi serupa ke lib terkenal seperti Jambu, Apache Commons dll?
yetanothercoder
4

Untuk karakter non-Unicode:

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

org.apache.commons.lang3.StringUtils:

public static String replaceIgnoreCase (Teks string, String searchString, Penggantian string)

Case tidak sensitif menggantikan semua kemunculan String dalam String lain.

Michael
sumber
3

Saya suka SMA 's jawaban yang menggunakan replaceAlldengan ekspresi reguler. Jika Anda akan melakukan penggantian yang sama berkali-kali, masuk akal untuk melakukan pra-kompilasi ekspresi reguler satu kali:

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"));
    }
}
Stephen Ostermiller
sumber
3

Sederhananya tanpa perpustakaan pihak ketiga:

    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));
gouessej
sumber