Java에서 문자열의 문자를 반복하는 가장 쉽고 가장 좋은 방법은 무엇입니까?


341

StringTokenizer? 변환 StringA와 char[]그 이상과 반복 처리? 다른 것?




1
또한 참조 stackoverflow.com/questions/8894258/... 벤치 마크하면 String.charAt ()가 작은 문자열을 가장 빠르게, 그리고 문자 배열을 읽어 반사를 사용하여 직접적으로 큰 문자열을 가장 빠르게 보여줍니다.
Jonathan


답변:


363

for 루프를 사용하여 문자열을 반복하고 charAt()각 문자를 검사하도록합니다. 문자열은 배열로 구현되므로이 charAt()메서드는 일정한 시간 작업입니다.

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

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

그게 내가 할 것입니다. 가장 쉬운 것 같습니다.

정확성에 관한 한, 나는 그것이 여기에 있다고 믿지 않습니다. 그것은 모두 당신의 개인적인 스타일에 근거합니다.


3
컴파일러가 length () 메소드를 인라인합니까?
Uri

7
길이 ()를 인라인 할 수 있습니다. 즉, 몇 개의 프레임을 호출하는 메소드를 호이스트 할 수 있지만이를 수행하는 것이 더 효율적입니다. (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

209

두 가지 옵션

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

또는

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

첫 번째는 아마도 더 빠르며, 두 번째는 더 읽기 쉽습니다.


26
초기화 표현식에 s.length ()를 배치하기위한 것. 누군가 이유를 모르는 경우 종료 명령문에 i <s.length ()로 배치 된 경우 한 번만 평가되기 때문에 루프 될 때마다 s.length ()가 호출됩니다.
Dennis

57
컴파일러 최적화가 당신을 위해 그것을 처리했다고 생각했습니다.
Rhyous

4
@Matthias Javap 클래스 디스어셈블러를 사용하여 루프 종료 표현식에 대해 s.length ()에 대한 반복 호출을 피할 수 있습니다. 코드 OP에서 게시 된 s.length ()에 대한 호출은 초기화 표현식에 있으므로 언어 ​​시맨틱은 이미 한 번만 호출되도록 보장합니다.
prasopes

3
@prasopes 대부분의 Java 최적화는 클래스 파일이 아닌 런타임에서 발생합니다. length ()에 대한 반복 된 호출을 보더라도 런타임 페널티를 나타내지는 않습니다.
Isaac

2
@Lasse, 추정 이유는 효율성입니다. 버전은 모든 반복에서 length () 메서드를 호출하지만 Dave는 초기화 프로그램에서 한 번 호출합니다. 즉, JIT ( "just in time") 옵티마이 저가 추가 콜을 최적화 할 가능성이 높으므로 실제 게인이 없어도 가독성 차이 만있을 수 있습니다.
스티브

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
나는 당신이 기본 다국어 비행기 이외의 것을 사용하는 방법을 얻지 못했습니다. curChar는 여전히 16 비트입니다.
Falken 교수 계약은

2
int를 사용하여 전체 코드 포인트를 저장하거나 각 문자는 코드 포인트를 정의하는 두 대리 쌍 중 하나만 저장합니다.
sk.

1
코드 포인트와 대리 쌍을 읽어야한다고 생각합니다. 감사!
Falken 교수 계약은

6
+1이 BMP 외부의 유니 코드 문자에 대한 유일한 해답 인 것 같기 때문에 +1
Jason S

(문자 반대) 코드 포인트 반복의 개념을 설명하기 위해 몇 가지 코드를 썼다 : gist.github.com/EmmanuelOga/...
엠마뉴엘 오가

26

StringTokenizer가 여기서 과도하게 사용된다는 데 동의합니다. 실제로 위의 제안을 시도하고 시간이 걸렸습니다.

내 테스트는 매우 간단했습니다. 약 백만 개의 문자로 StringBuilder를 만들고 문자열로 변환 한 다음 charAt () / char 배열로 변환 한 후 / CharacterIterator를 사용하여 천 번 (각각 컴파일러가 전체 루프를 최적화하지 못하도록 문자열에서 무언가를 수행하십시오 :-)).

내 2.6 GHz Powerbook (맥 :-)) 및 JDK 1.5의 결과 :

  • 테스트 1 : charAt + String-> 3138msec
  • 테스트 2 : 문자열을 배열로 변환-> 9568msec
  • 테스트 3 : StringBuilder charAt-> 3536msec
  • 테스트 4 : CharacterIterator 및 문자열-> 12151msec

결과가 크게 다르기 때문에 가장 간단한 방법도 가장 빠른 것 같습니다. 흥미롭게도 StringBuilder의 charAt ()는 String보다 약간 느립니다.

BTW 나는 '\ uFFFF'문자의 남용을 "반복의 끝"으로 간주하여 CharacterIterator를 사용하지 않는 것이 좋습니다. 큰 프로젝트에는 항상 두 종류의 서로 다른 목적으로 같은 종류의 핵을 사용하는 두 사람이 있으며 코드는 실제로 신비하게 충돌합니다.

테스트 중 하나는 다음과 같습니다.

    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/questions/196830/…
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 값으로 0 확장되어 스트림으로 전달됩니다.

문자와 코드 포인트는 어떻게 다릅니 까? 기사 에서 언급했듯이 :

유니 코드 3.1은 보충 문자를 추가하여 단일 문자 16 비트로 구별 할 수있는 216 자 이상의 총 문자 수를 가져옵니다 char. 따라서 char값이 더 이상 유니 코드의 기본 의미 단위에 일대일로 맵핑되지 않습니다. JDK 5는 더 큰 문자 값 세트를 지원하도록 업데이트되었습니다. char유형 의 정의를 변경하는 대신 새로운 보충 문자 중 일부는 두 char값 의 서로 게이트 쌍으로 표시됩니다 . 이름 혼동을 줄이기 위해 코드 포인트는 보충 문자를 포함하여 특정 유니 코드 문자를 나타내는 숫자를 나타내는 데 사용됩니다.

마지막으로 왜 forEachOrdered그렇지 forEach않습니까?

의 동작이 forEach명확하게 결정적되는 위치로서 forEachOrdered수행이 스트림의 각 요소에 대한 액션, 스트림의 발생 순서 스트림 정의 발생 순서를 갖는 경우. 따라서 forEach주문이 유지된다는 보장은 없습니다. 또한이 질문 을 확인하십시오 .

문자, 코드 포인트, 글리프 (glyph) 및 그래 핀 (grapheme)의 차이점에 대해서는 이 질문을 확인하십시오 .


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

1
왜 이것이 과잉인지 모르겠습니다. 반복자는 무엇이든 할 수있는 가장 자바적인 방법입니다 ... 반복. StringCharacterIterator는 불변성을 최대한 활용합니다.
슬림

2
@ddimitrov에 동의하십시오-이것은 과잉입니다. 반복자를 사용하는 유일한 이유는 foreach를 활용하는 것인데, for 루프보다 "보기"가 조금 더 쉽습니다. 어쨌든 기존의 for 루프를 작성하려면 charAt ()을 사용하십시오.
Rob Gilliam

3
유니 코드는 Java가 char제공 하는 것보다 많은 공간을 필요로하기 때문에 문자 반복자를 사용하는 것이 문자를 반복하는 유일한 올바른 방법 일 것입니다 . Java char는 16 비트를 포함하며 U + FFFF의 유니 코드 문자를 보유 할 수 있지만 유니 코드는 U + 10FFFF의 문자를 지정합니다. 16 비트를 사용하여 유니 코드를 인코딩하면 가변 길이 문자 인코딩이 발생합니다. 이 페이지의 대부분의 답변은 Java 인코딩이 일정한 길이의 인코딩이라고 가정합니다.
9

3
@ceving 문자 이터레이터가 BMP 이외의 문자에 도움이되지 않는 것 같습니다 : oracle.com/us/technologies/java/supplementary-142654.html
Bruno De Fraine

18

당신이있는 경우 구아바을 클래스 패스에 다음 꽤 읽을 수있는 대안이다. 구아바는이 경우 상당히 합리적인 사용자 정의 목록 구현을 가지고 있기 때문에 비효율적이지 않아야합니다.

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

복잡한 작업을 수행해야하는 경우 for 루프 + 구아바를 사용하십시오. forEach 내부의 forEach 범위 외부에 정의 된 변수 (예 : 정수 및 문자열)를 변경할 수 없기 때문입니다. forEach 내부에있는 것은 검사 예외를 던질 수 없으므로 때로는 성가시기도합니다.
sabujp

13

코드 포인트를 반복 해야하는 경우 String(이 답변 참조 ) 더 짧고 읽기 쉬운 방법은 CharSequence#codePointsJava 8에 추가 된 메소드 를 사용하는 것입니다.

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

또는 for 루프 대신 스트림을 직접 사용하십시오.

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

도 있습니다 CharSequence#chars(그것이 비록 당신이 문자의 스트림을 원하지 않는 경우 IntStream에는이 있으므로, CharStream).


3

StringTokenizerJDK의 클래스 중 하나이기 때문에 사용하지 않을 것 입니다.

javadoc은 말합니다 :

StringTokenizer새 코드에서는 사용을 권장하지 않지만 호환성 이유로 유지되는 레거시 클래스입니다. 이 기능을 원하는 사람은 split 방법 String이나 java.util.regex패키지를 대신 사용하는 것이 좋습니다 .


문자열 토크 나이 저는 토큰 (즉, 문장의 단어)을 반복하는 데 완벽하게 유효하고 효율적인 방법입니다. 이것은 문자를 반복하는 데있어 과잉입니다. 귀하의 의견을 오도하는 것으로 하향 조정하고 있습니다.
ddimitrov

3
ddimitrov : StringTokenizer가 권장되지 않음을 지적하는 방법을 따르지 않습니다 .JavaDoc ( java.sun.com/javase/6/docs/api/java/util/StringTokenizer.html )을 인용하여 오해의 소지가 있습니다. 오프셋으로 상향 조정되었습니다.
Powerlord

1
감사합니다. Bemrose ... 인용 된 블록 인용문은 명백해야하는데, 여기서 활성 버그 수정이 StringTokenizer에 적용되지 않을 것이라고 추측해야합니다.
Alan

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

자바 온라인 내가 얻을 :

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/…
Emmanuel Oga

0

StringTokenizer는 문자열을 개별 문자로 나누는 작업에 전혀 적합하지 않습니다. 다음과 String#split()같이 아무것도 일치하지 않는 정규식을 사용하여 쉽게 할 수 있습니다.

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

그러나 StringTokenizer는 정규 표현식을 사용하지 않으며 문자 사이에 아무것도 일치하지 않도록 지정할 수있는 구분자 문자열이 없습니다. 가 이다 는 구분 기호를 반환 구분 기호 문자열로 문자열 자체를 사용합니다 (모든 문자 구분을)하고있다 : 하나의 귀여운 당신이 같은 일을 수행하는 데 사용할 수있는 해킹 :

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

그러나 나는 이러한 옵션을 무시할 목적으로 만 언급했습니다. 두 기술 모두 원래 문자열을 문자 기본 요소 대신 하나의 문자 문자열로 나누고 두 가지 모두 객체 생성 및 문자열 조작 형태로 많은 오버 헤드를 발생시킵니다. for 루프에서 charAt ()를 호출하는 것과 비교하면 거의 오버 헤드가 발생하지 않습니다.


0

에 정성 들여 이 대답 하고 이 답변 .

위의 답변은 코드 포인트 값으로 반복하지 않는 많은 솔루션의 문제를 지적합니다 . 대리 문자 에는 문제가 있습니다 . Java 문서도 여기 에 문제를 설명합니다 ( "유니 코드 문자 표현"참조). 어쨌든 보충 유니 코드 세트의 실제 대리 문자를 사용하고 다시 문자열 로 변환하는 코드가 있습니다. .toChars ()는 문자 배열을 반환합니다. 대리자를 처리하는 경우 반드시 두 문자가 있어야합니다. 이 코드는 모든 유니 코드 문자에서 작동 합니다.

    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의 문자열을 반복하는 두 가지 방법이 있습니다.

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

성능이 위험에 처한 경우 첫 번째 일정을 일정한 시간에 사용하는 것이 좋습니다. 그렇지 않은 경우 두 번째 작업을 수행하면 Java의 문자열 클래스에 대한 불변성을 고려하여 작업을 쉽게 수행 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.