Apa cara termudah / terbaik / paling benar untuk beralih melalui karakter string di Jawa?

341

StringTokenizer? Konversikan Stringke a char[]dan ulangi itu? Sesuatu yang lain

Paul Wicks
sumber
3
Lihat juga stackoverflow.com/questions/1527856/…
rogerdpack
1
Lihat juga stackoverflow.com/questions/8894258/... Pertunjukan benchmark String.charAt () tercepat untuk string kecil, dan menggunakan refleksi untuk membaca array char secara langsung lebih cepat untuk string besar.
Jonathan

Jawaban:

363

Saya menggunakan for for untuk mengulangi string dan gunakan charAt()untuk mendapatkan setiap karakter untuk memeriksanya. Karena String diimplementasikan dengan array, charAt()metode ini adalah operasi waktu yang konstan.

String s = "...stuff...";

for (int i = 0; i < s.length(); i++){
    char c = s.charAt(i);        
    //Process char
}

Itu yang akan saya lakukan. Sepertinya yang paling mudah bagi saya.

Sejauh kebenarannya, saya tidak percaya yang ada di sini. Itu semua didasarkan pada gaya pribadi Anda.

jjnguy
sumber
3
Apakah kompiler sebaris dengan metode length ()?
Uri
7
mungkin inline length (), yaitu hoist metode di belakang yang memanggil beberapa frame, tetapi lebih efisien untuk melakukan ini untuk (int i = 0, n = s.length (); i <n; i ++) {char c = s.charAt (i); }
Dave Cheney
32
Mengacaukan kode Anda untuk keuntungan kinerja kecil . Harap hindari ini sampai Anda memutuskan area kode ini sangat penting.
langsing
31
Perhatikan bahwa teknik ini memberi Anda karakter , bukan poin kode , yang berarti Anda bisa mendapatkan pengganti.
Gabe
2
@ikh charAt bukan O (1) : Bagaimana bisa begitu? Kode untuk String.charAt(int)hanya melakukan value[index]. Saya pikir Anda bingung chatAt()dengan hal lain yang memberi Anda poin kode.
antak
209

Dua pilihan

for(int i = 0, n = s.length() ; i < n ; i++) { 
    char c = s.charAt(i); 
}

atau

for(char c : s.toCharArray()) {
    // process c
}

Yang pertama mungkin lebih cepat, kemudian yang kedua mungkin lebih mudah dibaca.

Dave Cheney
sumber
26
ditambah satu untuk menempatkan s.length () dalam ekspresi inisialisasi. Jika ada yang tidak tahu mengapa, itu karena itu hanya dievaluasi sekali di mana jika itu ditempatkan dalam pernyataan penghentian sebagai i <s.length (), maka s.length () akan dipanggil setiap kali dilingkarkan.
Dennis
57
Saya pikir optimisasi kompiler menangani hal itu untuk Anda.
Rhyous
4
@Matthias Anda dapat menggunakan disassembler kelas Javap untuk melihat bahwa panggilan berulang ke s.length () di untuk ekspresi terminasi loop memang dihindari. Perhatikan bahwa dalam kode OP diposting panggilan ke s.length () adalah dalam ekspresi inisialisasi, jadi semantik bahasa sudah menjamin bahwa itu akan dipanggil hanya sekali.
prasopes
3
@prasopes Perhatikan bahwa sebagian besar optimasi java terjadi di runtime, BUKAN di file kelas. Bahkan jika Anda melihat panggilan berulang ke length () yang tidak mengindikasikan penalti runtime, tentu saja.
Isaac
2
@Lasse, alasan diduga adalah untuk efisiensi - versi Anda memanggil metode length () pada setiap iterasi, sedangkan Dave menyebutnya sekali di initializer. Yang mengatakan, sangat mungkin pengoptimal JIT ("tepat waktu") akan mengoptimalkan panggilan ekstra, sehingga kemungkinan hanya perbedaan keterbacaan tanpa keuntungan nyata.
Steve
90

Perhatikan sebagian besar teknik lain yang dijelaskan di sini memecah jika Anda berurusan dengan karakter di luar BMP (Unicode Basic Multilingual Plane ), yaitu titik kode yang berada di luar kisaran u0000-uFFFF. Ini hanya akan jarang terjadi, karena titik kode di luar ini sebagian besar ditugaskan ke bahasa mati. Tetapi ada beberapa karakter yang berguna di luar ini, misalnya beberapa titik kode yang digunakan untuk notasi matematika, dan beberapa digunakan untuk menyandikan nama yang tepat dalam bahasa Cina.

Jika demikian, kode Anda adalah:

String str = "....";
int offset = 0, strLen = str.length();
while (offset < strLen) {
  int curChar = str.codePointAt(offset);
  offset += Character.charCount(curChar);
  // do something with curChar
}

The Character.charCount(int)Metode membutuhkan Java 5 +.

Sumber: http://mindprod.com/jgloss/codepoint.html

sk.
sumber
1
Saya tidak mengerti bagaimana Anda menggunakan apa pun kecuali Basic Multilingual Plane di sini. CurChar masih 16 bit baik-baik saja?
Kontrak Prof. Falken dilanggar
2
Anda bisa menggunakan int untuk menyimpan seluruh titik kode, atau masing-masing karakter hanya akan menyimpan satu dari dua pasangan pengganti yang menentukan titik kode.
sk.
1
Saya pikir saya perlu membaca tentang poin kode dan pasangan pengganti. Terima kasih!
Kontrak Prof. Falken dilanggar
6
Memberi +1 karena ini sepertinya satu-satunya jawaban yang benar untuk karakter Unicode di luar BMP
Jason S
Menulis beberapa kode untuk mengilustrasikan konsep iterating over codepoints (sebagai lawan chars): gist.github.com/EmmanuelOga/…
Emmanuel Oga
26

Saya setuju bahwa StringTokenizer berlebihan di sini. Sebenarnya saya mencoba saran di atas dan mengambil waktu.

Pengujian saya cukup sederhana: buat StringBuilder dengan sekitar satu juta karakter, ubah menjadi String, dan lintasi masing-masingnya dengan charAt () / setelah mengonversi ke char array / dengan CharacterIterator seribu kali (tentu saja memastikan untuk lakukan sesuatu pada string sehingga kompiler tidak dapat mengoptimalkan seluruh loop :-)).

Hasilnya pada Powerbook 2,6 GHz saya (itu mac :-)) dan JDK 1.5:

  • Tes 1: karakter + String -> 3138msec
  • Tes 2: String dikonversi ke array -> 9568msec
  • Uji 3: Karakter StringBuilder -> 3536msec
  • Uji 4: CharacterIterator dan String -> 12151msec

Karena hasilnya sangat berbeda, cara yang paling mudah juga tampaknya menjadi yang tercepat. Menariknya, karakter () dari StringBuilder tampaknya sedikit lebih lambat daripada yang dimiliki String.

BTW Saya menyarankan untuk tidak menggunakan CharacterIterator karena saya menganggap penyalahgunaan karakter '\ uFFFF' sebagai "akhir iterasi" adalah hack yang sangat mengerikan. Dalam proyek-proyek besar selalu ada dua orang yang menggunakan jenis peretasan yang sama untuk dua tujuan yang berbeda dan kode crash secara misterius.

Inilah salah satu tesnya:

    int count = 1000;
    ...

    System.out.println("Test 1: charAt + String");
    long t = System.currentTimeMillis();
    int sum=0;
    for (int i=0; i<count; i++) {
        int len = str.length();
        for (int j=0; j<len; j++) {
            if (str.charAt(j) == 'b')
                sum = sum + 1;
        }
    }
    t = System.currentTimeMillis()-t;
    System.out.println("result: "+ sum + " after " + t + "msec");

sumber
1
Ini memiliki masalah yang sama yang diuraikan di sini: stackoverflow.com/questions/196830/…
Emmanuel Oga
22

Di Java 8 kita bisa menyelesaikannya sebagai:

String str = "xyz";
str.chars().forEachOrdered(i -> System.out.print((char)i));
str.codePoints().forEachOrdered(i -> System.out.print((char)i));

Method chars () mengembalikan sebuah IntStreamseperti yang disebutkan dalam doc :

Mengembalikan aliran int nol-memperluas nilai char dari urutan ini. Setiap karakter yang memetakan ke titik kode pengganti dilewatkan tanpa ditafsirkan. Jika urutan dimutasi saat aliran sedang dibaca, hasilnya tidak ditentukan.

Metode ini codePoints()juga mengembalikan IntStreamsesuai dokumen:

Mengembalikan aliran nilai-nilai titik kode dari urutan ini. Setiap pasangan pengganti yang ditemui dalam urutan digabungkan seolah-olah oleh Character.toCodePoint dan hasilnya diteruskan ke aliran. Unit kode lainnya, termasuk karakter BMP biasa, pengganti yang tidak berpasangan, dan unit kode yang tidak ditentukan, adalah nol-diperluas ke nilai int yang kemudian diteruskan ke aliran.

Apa perbedaan antara char dan code point? Seperti disebutkan dalam artikel ini :

Unicode 3.1 menambahkan karakter tambahan, sehingga jumlah total karakter lebih dari 216 karakter yang dapat dibedakan dengan 16-bit tunggal char. Oleh karena itu, charnilai tidak lagi memiliki pemetaan satu-ke-satu ke unit semantik mendasar di Unicode. JDK 5 diperbarui untuk mendukung serangkaian nilai karakter yang lebih besar. Alih-alih mengubah definisi chartipe, beberapa karakter tambahan baru diwakili oleh pasangan pengganti dari dua charnilai. Untuk mengurangi kebingungan penamaan, titik kode akan digunakan untuk merujuk ke nomor yang mewakili karakter Unicode tertentu, termasuk yang tambahan.

Akhirnya mengapa forEachOrdereddan tidak forEach?

Perilaku forEacheksplisit nondeterministik di mana sebagai forEachOrderedmelakukan tindakan untuk setiap elemen aliran ini, dalam urutan pertemuan aliran jika aliran memiliki urutan pertemuan yang ditentukan. Jadi forEachtidak menjamin bahwa pesanan akan disimpan. Periksa juga pertanyaan ini untuk lebih lanjut.

Untuk perbedaan antara karakter, titik kode, mesin terbang dan grapheme, periksa pertanyaan ini .

akhil_mittal
sumber
21

Ada beberapa kelas khusus untuk ini:

import java.text.*;

final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
   // process c
   ...
}
Bruno De Fraine
sumber
7
Sepertinya berlebihan untuk sesuatu yang sederhana seperti iterasi dari array char abadi.
ddimitrov
1
Saya tidak melihat mengapa ini berlebihan. Iterator adalah cara paling java-ish untuk melakukan apa saja ... berulang. The StringCharacterIterator terikat untuk mengambil keuntungan penuh dari keabadian.
langsing
2
Setuju dengan @ddimitrov - ini berlebihan. Satu-satunya alasan untuk menggunakan iterator adalah untuk memanfaatkan foreach, yang sedikit lebih mudah untuk "dilihat" daripada for for loop. Jika Anda akan tetap menulis konvensional untuk loop, maka sebaiknya gunakan charAt ()
Rob Gilliam
3
Menggunakan iterator karakter mungkin satu-satunya cara yang benar untuk beralih lebih dari karakter, karena Unicode membutuhkan lebih banyak ruang daripada yang chardisediakan Java . Java charberisi 16 bit dan dapat menampung karakter Unicode hingga U + FFFF tetapi Unicode menentukan karakter hingga U + 10FFFF. Menggunakan 16 bit untuk mengkodekan hasil Unicode dalam pengkodean karakter panjang variabel. Sebagian besar jawaban pada halaman ini menganggap bahwa penyandian Java adalah penyandian panjang konstan, yang salah.
ceving
3
@ceving Tampaknya bukan iterator karakter yang akan membantu Anda dengan karakter non-BMP: oracle.com/us/technologies/java/supplementary-142654.html
Bruno De Fraine
18

Jika Anda memiliki Guava di classpath Anda, berikut ini adalah alternatif yang cukup mudah dibaca. Guava bahkan memiliki implementasi Daftar kustom yang cukup masuk akal untuk kasus ini, jadi ini seharusnya tidak efisien.

for(char c : Lists.charactersOf(yourString)) {
    // Do whatever you want     
}

UPDATE: Seperti yang dicatat @Alex, dengan Java 8 ada juga yang CharSequence#charsakan digunakan. Bahkan jenisnya adalah IntStream, sehingga dapat dipetakan ke karakter seperti:

yourString.chars()
        .mapToObj(c -> Character.valueOf((char) c))
        .forEach(c -> System.out.println(c)); // Or whatever you want
Touko
sumber
Jika Anda perlu melakukan sesuatu yang kompleks maka lakukan dengan for + loop jambu karena Anda tidak dapat bermutasi variabel (misalnya Integer dan Strings) yang didefinisikan di luar lingkup forEach di dalam forEach. Apa pun yang ada di dalam forEach juga tidak bisa membuang pengecualian yang dicentang, jadi itu terkadang menjengkelkan.
sabujp
13

Jika Anda perlu mengulangi poin kode dari String(lihat jawaban ini ) cara yang lebih pendek / lebih mudah dibaca adalah dengan menggunakan CharSequence#codePointsmetode yang ditambahkan di Java 8:

for(int c : string.codePoints().toArray()){
    ...
}

atau menggunakan streaming secara langsung, bukan untuk loop:

string.codePoints().forEach(c -> ...);

Ada juga CharSequence#charsjika Anda ingin aliran karakter (meskipun itu adalah IntStream, karena tidak ada CharStream).

Alex
sumber
3

Saya tidak akan menggunakannya StringTokenizerkarena ini adalah salah satu kelas di JDK yang merupakan warisan.

Javadoc mengatakan:

StringTokenizeradalah kelas lawas yang dipertahankan karena alasan kompatibilitas meskipun penggunaannya tidak disarankan dalam kode baru. Disarankan bahwa siapa pun yang mencari fungsi ini menggunakan metode split Stringatau java.util.regexpaket sebagai gantinya.

Alan
sumber
Token tokenizer adalah cara yang benar-benar valid (dan lebih efisien) untuk iterasi atas token (yaitu kata-kata dalam sebuah kalimat.) Ini jelas merupakan pembunuhan berlebihan untuk iterasi pada karakter. Saya menolak komentar Anda sebagai menyesatkan.
ddimitrov
3
ddimitrov: Saya tidak mengikuti bagaimana menunjukkan bahwa StringTokenizer tidak dianjurkan. TERMASUK kutipan dari JavaDoc ( java.sun.com/javase/6/docs/api/java/util/StringTokenizer.html ) karena menyatakan seperti itu menyesatkan. Dipilih untuk mengimbangi.
Powerlord
1
Terima kasih Pak Bemrose ... Saya menganggap bahwa kutipan blok yang dikutip seharusnya sangat jelas, di mana orang mungkin harus menyimpulkan bahwa perbaikan bug aktif tidak akan dilakukan ke StringTokenizer.
Alan
2

Jika Anda memerlukan kinerja, maka Anda harus menguji lingkungan Anda. Tidak ada jalan lain.

Di sini contoh kode:

int tmp = 0;
String s = new String(new byte[64*1024]);
{
    long st = System.nanoTime();
    for(int i = 0, n = s.length(); i < n; i++) {
        tmp += s.charAt(i);
    }
    st = System.nanoTime() - st;
    System.out.println("1 " + st);
}

{
    long st = System.nanoTime();
    char[] ch = s.toCharArray();
    for(int i = 0, n = ch.length; i < n; i++) {
        tmp += ch[i];
    }
    st = System.nanoTime() - st;
    System.out.println("2 " + st);
}
{
    long st = System.nanoTime();
    for(char c : s.toCharArray()) {
        tmp += c;
    }
    st = System.nanoTime() - st;
    System.out.println("3 " + st);
}
System.out.println("" + tmp);

Di Java online saya mendapatkan:

1 10349420
2 526130
3 484200
0

Di Android x86 API 17 saya mendapatkan:

1 9122107
2 13486911
3 12700778
0
Enyby
sumber
0

Lihat Tutorial Java: Strings .

public class StringDemo {
    public static void main(String[] args) {
        String palindrome = "Dot saw I was Tod";
        int len = palindrome.length();
        char[] tempCharArray = new char[len];
        char[] charArray = new char[len];

        // put original string in an array of chars
        for (int i = 0; i < len; i++) {
            tempCharArray[i] = palindrome.charAt(i);
        } 

        // reverse array of chars
        for (int j = 0; j < len; j++) {
            charArray[j] = tempCharArray[len - 1 - j];
        }

        String reversePalindrome =  new String(charArray);
        System.out.println(reversePalindrome);
    }
}

Masukkan panjang int lendan gunakan forloop.

Eugene Yokota
sumber
1
Saya mulai merasa sedikit spammer ... jika ada kata seperti itu :). Tetapi solusi ini juga memiliki masalah yang diuraikan di sini: Ini memiliki masalah yang sama yang diuraikan di sini: stackoverflow.com/questions/196830/…
Emmanuel Oga
0

StringTokenizer benar-benar tidak cocok untuk tugas memecah string menjadi karakter individu. Dengan String#split()Anda dapat melakukannya dengan mudah dengan menggunakan regex yang tidak cocok dengan apa pun, misalnya:

String[] theChars = str.split("|");

Tapi StringTokenizer tidak menggunakan regex, dan tidak ada string pembatas yang dapat Anda tentukan yang tidak cocok dengan apa pun di antara karakter. Ada adalah satu kecil yang lucu hack dapat Anda gunakan untuk mencapai hal yang sama: menggunakan string dirinya sebagai pembatas tali (membuat setiap karakter di dalamnya pembatas) dan memilikinya mengembalikan pembatas:

StringTokenizer st = new StringTokenizer(str, str, true);

Namun, saya hanya menyebutkan opsi-opsi ini untuk tujuan pemberhentian mereka. Kedua teknik memecah string asli menjadi string satu karakter, bukan primitif char, dan keduanya melibatkan banyak overhead dalam bentuk pembuatan objek dan manipulasi string. Bandingkan dengan memanggil charAt () di dalam for loop, yang hampir tidak menimbulkan overhead.

Alan Moore
sumber
0

Menguraikan jawaban ini dan jawaban ini .

Di atas jawaban menunjukkan masalah banyak solusi di sini yang tidak mengulangi dengan nilai titik kode - mereka akan mengalami masalah dengan karakter pengganti . Dokumen java juga menguraikan masalah di sini (lihat "Representasi Karakter Unicode"). Bagaimanapun, inilah beberapa kode yang menggunakan beberapa karakter pengganti yang sebenarnya dari set Unicode tambahan, dan mengubahnya kembali menjadi String. Perhatikan bahwa .toChars () mengembalikan array karakter: jika Anda berurusan dengan pengganti, Anda harus memiliki dua karakter. Kode ini harus bekerja untuk setiap karakter Unicode.

    String supplementary = "Some Supplementary: 𠜎𠜱𠝹𠱓";
    supplementary.codePoints().forEach(cp -> 
            System.out.print(new String(Character.toChars(cp))));
Hawkeye Parker
sumber
0

Kode Contoh ini akan membantu Anda keluar!

import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class Solution {
    public static void main(String[] args) {
        HashMap<String, Integer> map = new HashMap<String, Integer>();
        map.put("a", 10);
        map.put("b", 30);
        map.put("c", 50);
        map.put("d", 40);
        map.put("e", 20);
        System.out.println(map);

        Map sortedMap = sortByValue(map);
        System.out.println(sortedMap);
    }

    public static Map sortByValue(Map unsortedMap) {
        Map sortedMap = new TreeMap(new ValueComparator(unsortedMap));
        sortedMap.putAll(unsortedMap);
        return sortedMap;
    }

}

class ValueComparator implements Comparator {
    Map map;

    public ValueComparator(Map map) {
        this.map = map;
    }

    public int compare(Object keyA, Object keyB) {
        Comparable valueA = (Comparable) map.get(keyA);
        Comparable valueB = (Comparable) map.get(keyB);
        return valueB.compareTo(valueA);
    }
}
devDeejay
sumber
0

Jadi biasanya ada dua cara untuk beralih melalui string di java yang sudah dijawab oleh banyak orang di sini di utas ini, hanya menambahkan versi saya dulu. Pertama menggunakan

String s = sc.next() // assuming scanner class is defined above
for(int i=0; i<s.length; i++){
     s.charAt(i)   // This being the first way and is a constant time operation will hardly add any overhead
  }

char[] str = new char[10];
str = s.toCharArray() // this is another way of doing so and it takes O(n) amount of time for copying contents from your string class to character array

Jika kinerja dipertaruhkan maka saya akan merekomendasikan untuk menggunakan yang pertama dalam waktu yang konstan, jika tidak maka dengan yang kedua membuat pekerjaan Anda lebih mudah mengingat ketidakmampuan dengan kelas string di java.

Sumit Kapoor
sumber