Bagaimana cara memeriksa apakah suatu String berisi String lain dalam kasus yang tidak sensitif di Jawa?


386

Katakanlah saya punya dua string,

String s1 = "AbBaCca";
String s2 = "bac";

Saya ingin melakukan cek kembali yang s2ada di dalamnya s1. Saya bisa melakukan ini dengan:

return s1.contains(s2);

Saya cukup yakin itu contains()peka terhadap huruf besar-kecil, tetapi saya tidak bisa memastikan ini dengan membaca dokumentasi. Jika demikian maka saya kira metode terbaik saya akan menjadi sesuatu seperti:

return s1.toLowerCase().contains(s2.toLowerCase());

Selain itu, apakah ada cara lain (mungkin lebih baik) untuk mencapai hal ini tanpa memperhatikan sensitivitas huruf besar-kecil?


DrJava akan menjadi cara yang sangat mudah untuk menguji ini ketika dokumentasi gagal. Cukup ketikkan beberapa test case ke dalam jendela Interaksi, dan Anda harus mengetahuinya.
EfForEffort

17
Saya pikir Anda telah menjawab pertanyaan Anda sendiri. Saya tidak berpikir ada solusi di bawah ini yang lebih baik dari ini. Tapi mereka pasti lebih lambat.
Nikolay Dimitrov

7
Solusi Anda lebih sederhana daripada yang ada di jawaban
LobsterMan

2
Jawaban yang saya dan Banyak Orang cari di sini ada di pertanyaan Anda.
Lalit Fauzdar

1
Contoh Anda adalah yang paling sederhana, paling mudah dibaca, dan mungkin cara terbaik untuk melakukan ini - lebih baik daripada jawaban yang saya lihat.
user1258361

Jawaban:


320

Ya, berisi sensitif huruf. Anda dapat menggunakan java.util.regex.Pattern dengan flag CASE_INSENSITIVE untuk pencocokan case-sensitive:

Pattern.compile(Pattern.quote(wantedStr), Pattern.CASE_INSENSITIVE).matcher(source).find();

EDIT: Jika s2 berisi karakter khusus regex (yang ada banyak) penting untuk mengutip terlebih dahulu. Saya sudah mengoreksi jawaban saya karena ini adalah yang pertama kali dilihat orang, tetapi pilih Matt Quail sejak dia menunjukkan ini.


23
Seperti yang dinyatakan oleh dokumentasi untuk Pattern.CASE_INSENSITIVE, ini hanya berfungsi untuk karakter ASCII (yaitu, "Ä" tidak akan cocok dengan "ä"). Orang juga perlu menentukan UNICODE_CASEbendera untuk mencapai itu.
Philipp Wendler

72
Apakah pendekatan ini menggunakan Patternlebih banyak pemain daripada s1.toLowerCase().contains(s2.toLowerCase())?
Rajat Gupta

6
@ user01 Saya melakukan analisis kecepatan. Lihat jawaban saya untuk hasilnya (saya juga menunjukkan solusi yang lebih cepat): stackoverflow.com/a/25379180/1705598
icza

10
Akan saya lebih jelas apa yang sedang terjadi jika kita memiliki nama variabel yang lebih baik:Pattern.compile(Pattern.quote(needle), Pattern.CASE_INSENSITIVE).matcher(haystack).find()
John Bowers

5
@ user01 kebenarannya muncul sebelum kinerja, dan menggunakan toLowerCase akan memberikan hasil yang berpotensi salah (misalnya, ketika membandingkan teks Yunani tertentu yang mengandung huruf Sigma, yang memiliki dua bentuk huruf kecil untuk bentuk huruf besar yang sama).
Klitos Kyriacou

266

Satu masalah dengan jawaban oleh Dave L. adalah ketika s2 berisi markup regex seperti \d, dll.

Anda ingin memanggil Pattern.quote () di s2:

Pattern.compile(Pattern.quote(s2), Pattern.CASE_INSENSITIVE).matcher(s1).find();

1
Tangkapan yang bagus, Matt. Saya ingin tahu metode apa yang lebih efisien - huruf kecil berisi, atau solusi pola Anda. Tidak menggunakan pola yang kurang efisien untuk satu perbandingan, tetapi lebih efisien untuk beberapa perbandingan?
Aaron

41
Metode .toLowerCase (). Berisi () mungkin akan lebih cepat dalam kebanyakan kasus. Saya mungkin lebih suka gaya itu untuk kompleksitas yang lebih rendah juga.
Matt Quail

3
@AaronFerguson Ya, memang, toLowerCase().contains()lebih cepat. Saya melakukan beberapa analisis kecepatan, lihat jawaban saya untuk hasil: stackoverflow.com/a/25379180/1705598
icza

2
@MattQuail tidak ada gunanya menjadi lebih cepat jika mungkin salah. Misalnya, sigma modal Yunani memiliki dua bentuk huruf kecil (tergantung pada apakah ia muncul di akhir kata atau tidak) dan ketika mencoba melakukan pencocokan substring yang tidak peka huruf besar kecil, di mana substring berakhir dengan sigma, Anda dapat dengan mudah mendapatkan kesalahan hasil.
Klitos Kyriacou

Saya pikir kita harus menambahkan Pattern.UNICODE_CASEbendera juga. Bisakah Anda mengkonfirmasi ini?
Thariq Nugrohotomo

160

Kamu bisa menggunakan

org.apache.commons.lang3.StringUtils.containsIgnoreCase("AbBaCca", "bac");

The Apache Commons perpustakaan sangat berguna untuk hal semacam ini. Dan yang satu ini mungkin lebih baik daripada ekspresi reguler karena regex selalu mahal dalam hal kinerja.


1
Adakah yang tahu kalau ini menghormati lokal?
Charles Wood

12
@CharlesWood Ini didelegasikan ke String.regionMatches, yang menggunakan konversi karakter-bijaksana, jadi tidak. Selain itu, containsIgnoreCase("ß", "ss")mengembalikan -1, yang salah di setiap lokal ("s tajam" Jerman dikapitalisasi menjadi "ss".
maaartinus

Yang mana cara yang tepat untuk membandingkan kata-kata Jerman? Tampaknya itu adalah satu bahasa yang menyulitkan segala cara untuk membandingkan string: P
chomp

1
BTW: bahasa Jerman secara resmi diperpanjang dengan huruf kapital ß pada tahun 2017: de.wikipedia.org/wiki/Gro%C3%9Fes_%C3%9F . Pada keyboard Jerman, ketik Shift + Alt Gr + ß -> test: ẞ 😁
Kawu

119

Implementasi Lebih Cepat: Memanfaatkan String.regionMatches()

Menggunakan regexp bisa relatif lambat. Itu (lambat) tidak masalah jika Anda hanya ingin memeriksa dalam satu kasing. Tetapi jika Anda memiliki array atau koleksi ribuan atau ratusan ribu string, semuanya bisa menjadi sangat lambat.

Solusi yang disajikan di bawah ini tidak menggunakan ekspresi reguler atau toLowerCase()(yang juga lambat karena itu menciptakan string lain dan hanya membuangnya setelah cek).

Solusinya dibangun di atas metode String.regionMatches () yang tampaknya tidak diketahui. Ia memeriksa apakah 2 Stringwilayah cocok, tetapi yang penting adalah bahwa ia juga memiliki kelebihan dengan ignoreCaseparameter praktis .

public static boolean containsIgnoreCase(String src, String what) {
    final int length = what.length();
    if (length == 0)
        return true; // Empty string is contained

    final char firstLo = Character.toLowerCase(what.charAt(0));
    final char firstUp = Character.toUpperCase(what.charAt(0));

    for (int i = src.length() - length; i >= 0; i--) {
        // Quick check before calling the more expensive regionMatches() method:
        final char ch = src.charAt(i);
        if (ch != firstLo && ch != firstUp)
            continue;

        if (src.regionMatches(true, i, what, 0, length))
            return true;
    }

    return false;
}

Analisis Kecepatan

Analisis kecepatan ini tidak berarti menjadi ilmu roket, hanya gambaran kasar tentang seberapa cepat berbagai metode tersebut.

Saya membandingkan 5 metode.

  1. Kami containsIgnoreCase () metode.
  2. Dengan mengkonversi kedua string ke huruf kecil dan panggilan String.contains().
  3. Dengan mengonversi string sumber ke huruf kecil dan memanggil String.contains()dengan substring yang lebih dulu di-cache dan lebih rendah-huruf. Solusi ini sudah tidak sefleksibel karena menguji substring yang telah ditentukan sebelumnya.
  4. Menggunakan ekspresi reguler (jawaban yang diterima Pattern.compile().matcher().find()...)
  5. Menggunakan ekspresi reguler tetapi dengan pra-dibuat dan di-cache Pattern. Solusi ini sudah tidak sefleksibel karena menguji substring yang telah ditentukan.

Hasil (dengan memanggil metode 10 juta kali):

  1. Metode kami: 670 ms
  2. 2x toLowerCase () dan berisi (): 2829 ms
  3. 1x toLowerCase () dan berisi () dengan substring di-cache: 2446 ms
  4. Regexp: 7180 ms
  5. Regexp dengan cache Pattern: 1845 ms

Hasil dalam tabel:

                                            RELATIVE SPEED   1/RELATIVE SPEED
 METHOD                          EXEC TIME    TO SLOWEST      TO FASTEST (#1)
------------------------------------------------------------------------------
 1. Using regionMatches()          670 ms       10.7x            1.0x
 2. 2x lowercase+contains         2829 ms        2.5x            4.2x
 3. 1x lowercase+contains cache   2446 ms        2.9x            3.7x
 4. Regexp                        7180 ms        1.0x           10.7x
 5. Regexp+cached pattern         1845 ms        3.9x            2.8x

Metode kami 4x lebih cepat dibandingkan dengan menggunakan dan menggunakan lebih rendah contains(), 10x lebih cepat dibandingkan dengan menggunakan ekspresi reguler dan juga 3x lebih cepat bahkan jika Patternpra-cache (dan kehilangan fleksibilitas memeriksa untuk substring sewenang-wenang).


Kode Uji Analisis

Jika Anda tertarik bagaimana analisis dilakukan, berikut ini adalah aplikasi runnable yang lengkap:

import java.util.regex.Pattern;

public class ContainsAnalysis {

    // Case 1 utilizing String.regionMatches()
    public static boolean containsIgnoreCase(String src, String what) {
        final int length = what.length();
        if (length == 0)
            return true; // Empty string is contained

        final char firstLo = Character.toLowerCase(what.charAt(0));
        final char firstUp = Character.toUpperCase(what.charAt(0));

        for (int i = src.length() - length; i >= 0; i--) {
            // Quick check before calling the more expensive regionMatches()
            // method:
            final char ch = src.charAt(i);
            if (ch != firstLo && ch != firstUp)
                continue;

            if (src.regionMatches(true, i, what, 0, length))
                return true;
        }

        return false;
    }

    // Case 2 with 2x toLowerCase() and contains()
    public static boolean containsConverting(String src, String what) {
        return src.toLowerCase().contains(what.toLowerCase());
    }

    // The cached substring for case 3
    private static final String S = "i am".toLowerCase();

    // Case 3 with pre-cached substring and 1x toLowerCase() and contains()
    public static boolean containsConverting(String src) {
        return src.toLowerCase().contains(S);
    }

    // Case 4 with regexp
    public static boolean containsIgnoreCaseRegexp(String src, String what) {
        return Pattern.compile(Pattern.quote(what), Pattern.CASE_INSENSITIVE)
                    .matcher(src).find();
    }

    // The cached pattern for case 5
    private static final Pattern P = Pattern.compile(
            Pattern.quote("i am"), Pattern.CASE_INSENSITIVE);

    // Case 5 with pre-cached Pattern
    public static boolean containsIgnoreCaseRegexp(String src) {
        return P.matcher(src).find();
    }

    // Main method: perfroms speed analysis on different contains methods
    // (case ignored)
    public static void main(String[] args) throws Exception {
        final String src = "Hi, I am Adam";
        final String what = "i am";

        long start, end;
        final int N = 10_000_000;

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCase(src, what);
        end = System.nanoTime();
        System.out.println("Case 1 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsConverting(src, what);
        end = System.nanoTime();
        System.out.println("Case 2 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsConverting(src);
        end = System.nanoTime();
        System.out.println("Case 3 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCaseRegexp(src, what);
        end = System.nanoTime();
        System.out.println("Case 4 took " + ((end - start) / 1000000) + "ms");

        start = System.nanoTime();
        for (int i = 0; i < N; i++)
            containsIgnoreCaseRegexp(src);
        end = System.nanoTime();
        System.out.println("Case 5 took " + ((end - start) / 1000000) + "ms");
    }

}

6
+1 tetapi perhatikan bahwa gagal untuk ß(S tajam Jerman; SShuruf kapital ke ) dan juga untuk beberapa karakter lain (lihat sumber String.regionMatches, yang mencoba kedua konversi).
maaartinus

2
Anda selalu menguji string yang sama, yang sebenarnya bukan perbandingan yang adil. 'Aku ada' selalu di tengah, yang mungkin atau mungkin tidak membuat perbedaan untuk metode pencarian yang berbeda. Lebih baik menghasilkan string acak dan juga melaporkan kecepatan ketika substring tidak ada.

2
Itu tampaknya sangat dekat dengan metode Apache StringUtils: grepcode.com/file/repo1.maven.org/maven2/org.apache.commons/…
alain.janinm

1
@ alain.janinm Saya gagal melihat kesamaan. Satu-satunya hal yang tampaknya "dekat" dengan StringUtils.containsIgnoreCase()adalah bahwa solusi saya dan Apache menggunakan regionMatches()metode (dalam satu siklus), tetapi bahkan itu tidak sama dengan yang saya sebut String.regionMatches()dan panggilan Apache CharSequenceUtils.regionMatches().
icza

2
@icza CharSequenceUtils.regionMatcheshanya menelepon String.regionMatchessebenarnya. Ngomong-ngomong, maksud saya adalah untuk memberikan info, bahwa jika seseorang sudah menggunakan StringUtils lib dia bisa memanggilnya karena tampaknya cara yang efisien seperti Anda membuktikannya dengan tolok ukur Anda. Jika saya tidak menggunakan Apache lib, saya pasti akan menggunakan metode Anda;)
alain.janinm

22

Cara yang lebih sederhana untuk melakukan ini (tanpa khawatir tentang pencocokan pola) akan mengubah keduanya Stringmenjadi huruf kecil:

String foobar = "fooBar";
String bar = "FOO";
if (foobar.toLowerCase().contains(bar.toLowerCase()) {
    System.out.println("It's a match!");
}

4
Huruf karakter tergantung pada bahasa, yang berarti ini akan berfungsi pada komputer Anda tetapi akan gagal bagi pelanggan :). lihat komentar @Adriaan Koster.
kroiz

1
@kroiz, itu tergantung dari mana asalnya. Membandingkan "foobar" dan "FOO" akan selalu cocok, namun jika Anda membandingkan informasi input pengguna, atau konten khusus bahasa, maka Anda benar - pengembang harus berhati-hati.
Phil

16

Ya, ini bisa dicapai:

String s1 = "abBaCca";
String s2 = "bac";

String s1Lower = s1;

//s1Lower is exact same string, now convert it to lowercase, I left the s1 intact for print purposes if needed

s1Lower = s1Lower.toLowerCase();

String trueStatement = "FALSE!";
if (s1Lower.contains(s2)) {

    //THIS statement will be TRUE
    trueStatement = "TRUE!"
}

return trueStatement;

Kode ini akan mengembalikan String "BENAR!" karena ditemukan bahwa karakter Anda mengandung.


12
Kelemahan besar menggunakan toLowerCase () adalah hasilnya tergantung pada Lokal saat ini. Lihat: javapapers.com/core-java/…
Adriaan Koster

4
Pertanyaannya sebenarnya berisi solusi yang lebih baik karena ini gagal untuk huruf kecil s2. Tidak berbicara tentang detail seperti itu yang ini tidak dikompilasi dan jika itu, itu akan mengembalikan string.
maaartinus


3

Berikut ini beberapa yang ramah Unicode yang dapat Anda buat jika menarik ICU4j. Saya kira "abaikan kasus" dipertanyakan untuk nama metode karena meskipun perbandingan kekuatan utama mengabaikan kasus, itu digambarkan sebagai spesifik yang bergantung pada lokal. Tapi mudah-mudahan ini tergantung pada cara yang diharapkan pengguna.

public static boolean containsIgnoreCase(String haystack, String needle) {
    return indexOfIgnoreCase(haystack, needle) >= 0;
}

public static int indexOfIgnoreCase(String haystack, String needle) {
    StringSearch stringSearch = new StringSearch(needle, haystack);
    stringSearch.getCollator().setStrength(Collator.PRIMARY);
    return stringSearch.first();
}

3

Saya melakukan tes menemukan kecocokan case-string yang tidak sensitif. Saya memiliki 150.000 objek Vector semua dengan String sebagai satu bidang dan ingin menemukan bagian yang cocok dengan string. Saya mencoba tiga metode:

  1. Konversi semua menjadi huruf kecil

    for (SongInformation song: songs) {
        if (song.artist.toLowerCase().indexOf(pattern.toLowercase() > -1) {
                ...
        }
    }
  2. Gunakan metode String cocok ()

    for (SongInformation song: songs) {
        if (song.artist.matches("(?i).*" + pattern + ".*")) {
        ...
        }
    }
  3. Gunakan ekspresi reguler

    Pattern p = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
    Matcher m = p.matcher("");
    for (SongInformation song: songs) {
        m.reset(song.artist);
        if (m.find()) {
        ...
        }
    }

Hasil pengaturan waktu adalah:

  • Tidak ada kecocokan yang dicoba: 20 msecs

  • Untuk menurunkan kecocokan: 182 msecs

  • Pencocokan string: 278 msecs

  • Ekspresi reguler: 65 msecs

Ekspresi reguler terlihat menjadi yang tercepat untuk use case ini.


Bagus bahwa Anda memasukkan hasil pengaturan waktu. Semua orang mengatakan betapa lambatnya regex, tetapi pada kenyataannya itu sangat cepat jika Anda hanya perlu mengkompilasi regex sekali.
Mulai

1

Ada cara ringkas yang sederhana, menggunakan flag regex (case case {i}):

 String s1 = "hello abc efg";
 String s2 = "ABC";
 s1.matches(".*(?i)"+s2+".*");

/*
 * .*  denotes every character except line break
 * (?i) denotes case insensitivity flag enabled for s2 (String)
 * */

0

Saya tidak yakin apa pertanyaan utama Anda di sini, tapi ya, .contains peka huruf besar-kecil.


0
String container = " Case SeNsitive ";
String sub = "sen";
if (rcontains(container, sub)) {
    System.out.println("no case");
}

public static Boolean rcontains(String container, String sub) {

    Boolean b = false;
    for (int a = 0; a < container.length() - sub.length() + 1; a++) {
        //System.out.println(sub + " to " + container.substring(a, a+sub.length()));
        if (sub.equalsIgnoreCase(container.substring(a, a + sub.length()))) {
            b = true;
        }
    }
    return b;
}

Pada dasarnya, ini adalah metode yang membutuhkan dua string. Seharusnya versi yang tidak sensitif case berisi (). Saat menggunakan metode berisi, Anda ingin melihat apakah satu string terkandung dalam yang lain.

Metode ini mengambil string yang "sub" dan memeriksa apakah itu sama dengan substring dari string kontainer yang panjangnya sama dengan "sub". Jika Anda melihatfor loop, Anda akan melihat bahwa itu berulang di substring (yang merupakan panjang "sub") di atas string wadah.

Setiap iterasi memeriksa untuk melihat apakah substring dari string kontainer equalsIgnoreCaseke sub.


pada dasarnya itu adalah metode yang membutuhkan dua string. itu seharusnya menjadi versi case () yang tidak case sensitive. saat menggunakan metode berisi, Anda ingin melihat apakah satu string terkandung dalam yang lain. metode ini mengambil string yang "sub" dan memeriksa apakah itu sama dengan sub string dari string kontainer, yang panjangnya sama dengan "sub". jika Anda melihat for loop, Anda akan melihat bahwa iterates di dalam sub string (yang merupakan panjang dari "sub") di atas string kontainer. setiap iterasi memeriksa untuk melihat apakah sub string dari string wadah sama dengan atau untuk sub.
seth

@ Anda mungkin harus menambahkan itu ke jawaban Anda.
The Guy with The Hat

2
Ini adalah metode paling lambat yang pernah ... dan juga gagal untuk Jerman.
maaartinus

0

Jika Anda harus mencari string ASCII di string ASCII lain, seperti URL , Anda akan menemukan solusi saya menjadi lebih baik. Saya telah menguji metode icza dan menambang untuk kecepatan dan berikut hasilnya:

  • Kasus 1 mengambil 2788 ms - regionMatches
  • Kasus 2 mengambil 1520 ms - my

Kode:

public static String lowerCaseAscii(String s) {
    if (s == null)
        return null;

    int len = s.length();
    char[] buf = new char[len];
    s.getChars(0, len, buf, 0);
    for (int i=0; i<len; i++) {
        if (buf[i] >= 'A' && buf[i] <= 'Z')
            buf[i] += 0x20;
    }

    return new String(buf);
}

public static boolean containsIgnoreCaseAscii(String str, String searchStr) {
    return StringUtils.contains(lowerCaseAscii(str), lowerCaseAscii(searchStr));
}

0
import java.text.Normalizer;

import org.apache.commons.lang3.StringUtils;

public class ContainsIgnoreCase {

    public static void main(String[] args) {

        String in = "   Annulée ";
        String key = "annulee";

        // 100% java
        if (Normalizer.normalize(in, Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").toLowerCase().contains(key)) {
            System.out.println("OK");
        } else {
            System.out.println("KO");
        }

        // use commons.lang lib
        if (StringUtils.containsIgnoreCase(Normalizer.normalize(in, Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", ""), key)) {
            System.out.println("OK");
        } else {
            System.out.println("KO");
        }

    }

}

Terima kasih atas cuplikan kode ini, yang mungkin memberikan bantuan jangka pendek terbatas. Penjelasan yang tepat akan sangat meningkatkan nilai jangka panjangnya dengan menunjukkan mengapa ini adalah solusi yang baik untuk masalah ini, dan akan membuatnya lebih bermanfaat bagi pembaca masa depan dengan pertanyaan lain yang serupa. Harap edit jawaban Anda untuk menambahkan beberapa penjelasan, termasuk asumsi yang Anda buat.
Toby Speight

0
"AbCd".toLowerCase().contains("abcD".toLowerCase())

2
Bisakah Anda meningkatkan jawaban Anda dengan menjelaskan bagaimana kode Anda menyelesaikan masalah?
Isuka

1
Jawaban ini telah disarankan di banyak jawaban lain yang lebih terperinci untuk pertanyaan ini yang diberikan orang lain. Saya kira jawaban ini tidak ada gunanya di sini.
DaveyDaveDave

0

Kita dapat menggunakan streaming dengan anyMatch dan berisi Java 8

public class Test2 {
    public static void main(String[] args) {

        String a = "Gina Gini Protijayi Soudipta";
        String b = "Gini";

        System.out.println(WordPresentOrNot(a, b));
    }// main

    private static boolean WordPresentOrNot(String a, String b) {
    //contains is case sensitive. That's why change it to upper or lower case. Then check
        // Here we are using stream with anyMatch
        boolean match = Arrays.stream(a.toLowerCase().split(" ")).anyMatch(b.toLowerCase()::contains);
        return match;
    }

}

0

atau Anda dapat menggunakan pendekatan sederhana dan hanya mengonversi kasus string ke kasus substring dan kemudian menggunakan metode berisi.


-1
String x="abCd";
System.out.println(Pattern.compile("c",Pattern.CASE_INSENSITIVE).matcher(x).find());

-1

Anda bisa melakukan sesuatu seperti ini:

String s1 = "AbBaCca";
String s2 = "bac";
String toLower = s1.toLowerCase();
return toLower.contains(s2);
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.