Javaで文字列の文字を反復処理する最も簡単/最良/最も正しい方法は何ですか?


340

StringTokenizer?をに変換Stringし、char[]それを繰り返しますか?他に何か?




1
stackoverflow.com/questions/8894258/も参照してください。ベンチマークは、小さな文字列の場合はString.charAt()が最も速く、大きな文字列の場合はリフレクションを使用してchar配列を直接読み取るのが最も高速です。
ジョナサン


回答:


362

forループを使用charAt()して文字列を反復し、各文字を調べてそれを調べます。Stringは配列で実装されているため、このcharAt()メソッドは一定時間の操作です。

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

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

それが私がすることです。それは私にとって最も簡単なようです。

正確さに関しては、それがここにあるとは思えません。それはすべてあなたの個人的なスタイルに基づいています。


3
コンパイラーはlength()メソッドをインライン化しますか?
ウリ

7
それはlength()をインライン化する可能性があります。つまり、その背後にあるメソッドを数フレーム呼び出しますが、これを行う方が効率的です(int i = 0、n = s.length(); i <n; i ++){char c = s.charAt(i); }
Dave Cheney

32
以下のためにあなたのコードを乱雑に小さなパフォーマンスの向上。このコード領域が速度が重要であると判断するまで、これを回避してください。
スリム

31
この手法では、コードポイントではなく文字が得られることに注意してください。
Gabe

2
@ikh charAtはO(1)ではありません:どうですか?のコードString.charAt(int)は単にやっているだけvalue[index]です。私はあなたにchatAt()コードポイントを与える何か他のものと混同していると思います。
antak

208

2つのオプション

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

または

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

最初のものはおそらくより速く、2番目のものはおそらくより読みやすくなります。


26
さらに、初期化式にs.length()を配置するための1つ。理由がわからない場合は、それが1回だけ評価されるためです。終了ステートメントにi <s.length()として配置されている場合、ループするたびにs.length()が呼び出されます。
Dennis

57
コンパイラの最適化があなたのためにそれを処理してくれると思いました。
Rhyous

4
@Matthias Javapクラスの逆アセンブラを使用して、forループ終了式でs.length()への繰り返し呼び出しが実際に回避されていることを確認できます。OPでポストされたコードでは、s.length()への呼び出しは初期化式にあるため、言語のセマンティクスは、それが1回だけ呼び出されることをすでに保証していることに注意してください。
プラソープ2014年

3
@prasopesただし、ほとんどのJava最適化はランタイムで行われ、クラスファイルでは行われません。必ずしも実行時のペナルティを示さないlength()の繰り返し呼び出しを見たとしても。
アイザック

2
@Lasse、推定の理由は効率のためです-バージョンはすべての反復でlength()メソッドを呼び出しますが、Daveは初期化子で一度呼び出します。つまり、JIT( "ジャストインタイム")オプティマイザが余分な呼び出しを最適化する可能性が非常に高いため、読みやすさの違いのみで実際の利益は得られません。
Steve

90

BMP(Unicode Basic Multilingual Plane)の外の文字、つまりu0000-uFFFFの範囲外のコードポイントを処理している場合、ここで説明する他のテクニックのほとんどが機能しないことに注意してください。これ以外のコードポイントはほとんど死んだ言語に割り当てられているため、これはまれにしか起こりません。しかし、これ以外にもいくつかの便利な文字があります。たとえば、数学的表記に使用されるコードポイントや、中国語で適切な名前をエンコードするために使用されるコードポイントなどがあります。

その場合、コードは次のようになります。

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

Character.charCount(int)この方法は、Java 5+を必要とします。

出典:http : //mindprod.com/jgloss/codepoint.html


1
ここでは、Basic Multilingual Plane以外の使い方はわかりません。curCharは16ビットのままですか?
ファルケン教授の契約が

2
intを使用してコードポイント全体を格納するか、各文字がコードポイントを定義する2つのサロゲートペアのうち1つだけを格納します。
sk。

1
コードポイントとサロゲートペアについて調べる必要があると思います。ありがとう!
ファルケン教授の契約が

6
+1これは、BMP外のUnicode文字に対して正しい唯一の回答のようです
Jason S

(charsではなく)コードポイントを反復する概念を説明するためにいくつかのコードを書きました:gist.github.com/EmmanuelOga/…–
Emmanuel Oga

26

StringTokenizerはここではやり過ぎだということに同意します。実際、私は上記の提案を試し、時間をかけました。

私のテストはかなり単純でした:約100万文字のStringBuilderを作成し、それをStringに変換し、charAt()でそれらをトラバースします/ char配列に変換した後/ CharacterIteratorで1000回(もちろん、コンパイラーがループ全体を最適化しないように文字列に何かを行います:-))。

私の2.6 GHz Powerbook(Macです:-))とJDK 1.5の結果:

  • テスト1:charAt +文字列-> 3138ミリ秒
  • テスト2:文字列が配列に変換された-> 9568ミリ秒
  • テスト3:StringBuilder charAt-> 3536ミリ秒
  • テスト4:CharacterIteratorおよびString-> 12151ミリ秒

結果は大きく異なるため、最も簡単な方法も最速の方法のようです。興味深いことに、StringBuilderのcharAt()はStringのものよりも少し遅いようです。

ところで、私はCharacterIteratorを使用しないことをお勧めします。「\ uFFFF」文字の​​乱用は「イテレーションの終わり」であり、本当にひどいハックであると考えるからです。大規模なプロジェクトでは、2つの異なる目的で同じ種類のハッキングを使用する2人の人間が常に存在し、コードが本当に不可解にクラッシュします。

テストの1つを次に示します。

    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");

1
これには、ここで概説されているのと同じ問題があります。stackoverflow.com
Emmanuel Oga

22

Javaの8我々はとしてそれを解決することができます。

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

chars()メソッドIntStreamdocで述べたようにを返します:

このシーケンスからchar値をゼロ拡張するintのストリームを返します。サロゲートコードポイントにマップする文字は、解釈されずに渡されます。ストリームの読み取り中にシーケンスが変更された場合、結果は未定義です。

このメソッドcodePoints()は、IntStreamドキュメントごとに次のものも返します。

このシーケンスからコードポイント値のストリームを返します。シーケンスで検出されたサロゲートペアは、Character.toCodePointのように結合され、結果がストリームに渡されます。通常のBMP文字、対になっていないサロゲート、未定義のコード単位を含む他のコード単位は、int値にゼロ拡張されてから、ストリームに渡されます。

charとcode pointはどう違うのですか?この記事で述べたように:

Unicode 3.1は補助文字を追加し、合計文字数を単一の16ビットで区別できる216文字を超えましたchar。したがって、char値には、Unicodeの基本的な意味単位への1対1のマッピングがありません。JDK 5は、より大きな文字値のセットをサポートするように更新されました。charタイプの定義を変更する代わりに、新しい補助文字の一部は2つのchar値のサロゲートペアで表されます。名前の混乱を減らすために、コードポイントを使用して、補助文字を含む特定のUnicode文字を表す番号を参照します。

最後に、なぜforEachOrderedありませんかforEach

の動作forEachは明示的に非決定的です。ストリームが定義済みの遭遇順序を持っている場合、はストリームの遭遇順序でforEachOrderedこのストリームの各要素に対してアクションを実行します。したがって、注文が保持されることは保証されません。この質問もチェックしてくださいforEachで詳細を。

文字、コードポイント、グリフ、および書記素の違いについては、この質問を確認してください。


21

これにはいくつかの専用クラスがあります:

import java.text.*;

final CharacterIterator it = new StringCharacterIterator(s);
for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {
   // process c
   ...
}

7
不変のchar配列を反復するだけの単純なものでは、やりすぎのように見えます。
ddimitrov 2008年

1
なぜこれが行き過ぎなのかわかりません。イテレータは何でもする最もJava風の方法です...反復。StringCharacterIteratorは、不変性を最大限に活用するようにバインドされています。
スリム

2
@ddimitrovに同意してください-これはやり過ぎです。イテレータを使用する唯一の理由は、foreachを利用することです。これは、forループよりも「見やすく」なります。とにかく従来のforループを作成する場合は、charAt()を使用することもできます
Rob Gilliam

3
UnicodeはJavaがchar提供するよりも多くのスペースを必要とするため、文字反復子を使用することがおそらく文字を反復処理するための唯一の正しい方法です。Java charは16ビットを含み、U + FFFFまでのUnicode文字を保持できますが、UnicodeはU + 10FFFFまでの文字を指定します。16ビットを使用してUnicodeをエンコードすると、可変長文字エンコードになります。このページのほとんどの回答は、Javaエンコーディングが一定長のエンコーディングであると想定していますが、これは誤りです。
2013年

3
@ceving文字イテレータがBMP以外の文字であなたを助けることはないようです:oracle.com/us/technologies/java/supplementary-142654.html
Bruno De Fraine

18

クラスパスにGuavaがある場合、次はかなり読みやすい代替です。グアバはこのケースのためにかなり賢明なカスタムリストの実装さえ持っているので、これは非効率であるべきではありません。

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

更新:@Alexが述べたように、Java 8ではCharSequence#chars使用するものもあります。タイプもIntStreamなので、次のような文字にマップできます。

yourString.chars()
        .mapToObj(c -> Character.valueOf((char) c))
        .forEach(c -> System.out.println(c)); // Or whatever you want

複雑な処理を行う必要がある場合は、forEach内のforEachのスコープ外で定義された変数(整数や文字列など)を変更できないため、forループ+グアバを使用します。forEachの内部にあるものは何でも、チェックされた例外をスローすることができないため、これも時々迷惑です。
sabujp

13

のコードポイントを反復処理する必要がある場合String(この回答を参照)、CharSequence#codePointsJava 8で追加されたメソッドを使用することで、より短く/より読みやすい方法になります。

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

または、forループの代わりにストリームを直接使用します。

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

CharSequence#chars文字のストリームが必要な場合もあります(ただしIntStream、がないため、ですCharStream)。


3

私は使用しません StringTokenizerレガシーなJDKのクラスの1つである、し。

javadocは言う:

StringTokenizer新しいコードではその使用は推奨されませんが、互換性の理由で保持されるレガシークラスです。この機能を求める人はStringjava.util.regex代わりにまたは パッケージのsplitメソッドを使用することをお勧めします。


文字列トークナイザは、トークン(つまり、文中の単語)を反復するための完全に有効な(そしてより効率的な)方法です。文字を反復することは間違いなく過剰です。私はあなたのコメントを誤解を招くものとして反対票を投じています。
ddimitrov 2008年

3
ddimitrov:私はどのようにStringTokenizerははJavaDocの(からの引用を含め推奨されていないことを指摘し、次のいないよjava.sun.com/javase/6/docs/api/java/util/StringTokenizer.htmlそれはようになっていると述べ用)誤解を招く。オフセットに賛成。
Powerlord、2008年

1
Bemrose氏に感謝します...引用されたブロックの引用は非常に明確であるはずだと思います。おそらく、アクティブなバグ修正はStringTokenizerにコミットされないと推測する必要があります。
アラン、

2

パフォーマンスが必要な場合は、テストする必要ありますは、環境でます。他に方法はありません。

ここにサンプルコード:

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

のJavaオンライン私が取得します:

1 10349420
2 526130
3 484200
0

Android x86 API 17では、次のようになります。

1 9122107
2 13486911
3 12700778
0

0

Javaチュートリアル:文字列」を参照してください。

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);
    }
}

長さを入れてループint lenを使用しforます。


1
私は少しスパム行為を感じ始めています...そのような単語がある場合:)。しかし、この解決策は、問題はここに概説している:これは、同じ問題がここに概説している:stackoverflow.com/questions/196830/...
エマニュエル・男鹿

0

StringTokenizerは、文字列を個々の文字に分解するタスクにはまったく適していません。ではString#split()、あなたは何も、例えば一致しない正規表現を使って、簡単にそれを行うことができます。

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

ただし、StringTokenizerは正規表現を使用せず、文字間の何にも一致しないように指定できる区切り文字列はありません。同じことを行うために使用できるかわいいハック 1つあります。文字列自体を区切り文字列として使用し(その中のすべての文字を区切り文字にします)、区切り文字を返します。

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

ただし、これらのオプションについては、却下する目的でのみ言及しています。どちらの手法も、元の文字列をcharプリミティブではなく1文字の文字列に分割します。どちらの方法でも、オブジェクトの作成と文字列操作という形でかなりのオーバーヘッドが発生します。これを、実質的にオーバーヘッドのないforループでcharAt()を呼び出すのと比較してください。


0

この答えこの答えについて詳しく説明します

上記の回答は、コードポイント値で繰り返されない、ここでの多くのソリューションの問題を指摘しています- サロゲート文字に問題があります。Javaドキュメントもこの問題の概要を示します(「Unicode文字表現」を参照)。とにかく、ここで補足ユニコードセットからいくつかの実際のサロゲート文字を使用するいくつかのコードだと、変換、それらをバック Stringに。.toChars()は文字の配列を返すことに注意してください。サロゲートを処理している場合、2つの文字が必ず必要になります。このコードは、すべての Unicode文字で機能します。

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

0

このサンプルコードはあなたを助けます!

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);
    }
}

0

したがって、通常、このスレッドですでに複数の人が回答しているjavaの文字列を反復処理するには、2つの方法があります。

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

パフォーマンスが問題になっている場合は、最初の1つを一定の時間で使用することをお勧めします。2つ目がそうでない場合は、Javaの文字列クラスの不変性を考慮して作業を容易にします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.