Java String의 유니 코드 코드 포인트를 어떻게 반복 할 수 있습니까?


105

그래서 나는에 대해 알고 String#codePointAt(int)있지만 char코드 포인트 오프셋이 아니라 오프셋에 의해 인덱싱됩니다 .

나는 다음과 같은 것을 시도하는 것에 대해 생각하고 있습니다.

  • 인덱스 String#charAt(int)를 얻기 위해 사용char
  • 대리자char높은 범위 에 있는지 테스트
    • 그렇다면 String#codePointAt(int)코드 포인트를 가져오고 인덱스를 2 씩 증가시킵니다.
    • 그렇지 않은 경우 주어진 char값을 코드 포인트로 사용 하고 인덱스를 1 씩 증가시킵니다.

하지만 내 걱정은

  • 당연히 높은 대리 범위에있는 코드 포인트가 두 개의 char값 으로 저장되는지 아니면 하나의 값 으로 저장되는지 잘 모르겠습니다.
  • 이것은 문자를 반복하는 끔찍한 비용이 드는 방법처럼 보입니다.
  • 누군가 더 나은 것을 생각해 냈을 것입니다.

답변:


143

예, Java는 문자열의 내부 표현을 위해 UTF-16-esque 인코딩을 사용하며, 예, 대리 체계를 사용하여 BMP (Basic Multilingual Plane) 외부의 문자를 인코딩합니다 .

BMP 외부의 문자를 다룰 것이라는 것을 알고 있다면 Java 문자열의 문자를 반복하는 표준 방법은 다음과 같습니다.

final int length = s.length();
for (int offset = 0; offset < length; ) {
   final int codepoint = s.codePointAt(offset);

   // do something with the codepoint

   offset += Character.charCount(codepoint);
}

2
"비싼"것인지 아닌지에 대해서는 자바에 내장 된 다른 방법이 없습니다. 그러나 라틴어 / 유럽어 / 키릴 어 / 그리스어 / 히브리어 / 아랍어 스크립트 만 다루는 경우에는 마음껏 s.charAt () 만 사용하면됩니다. :)
Jonathan Feinberg

24
하지만 그렇게해서는 안됩니다. 예를 들어 프로그램이 XML을 출력하고 누군가 모호한 수학 연산자를 제공하면 갑자기 XML이 유효하지 않을 수 있습니다.
기계 달팽이

2
나는 offset = s.offsetByCodePoints(offset, 1);. offset += Character.charCount(codepoint);대신 사용하면 이점이 있습니까?
Paul Groke 2015 년

3
@Mechanicalsnail 귀하의 의견을 이해하지 못합니다. XML을 출력하면이 답변이 오작동하는 이유는 무엇입니까?
Gili

3
@Gili 대답은 괜찮습니다. 그는 @Jonathan Feinberg가 사용 charAt()하는 것이 나쁜 생각 이라고 주장하는 코멘트를 언급하고있었습니다.
RecursiveExceptionException

72

코드 포인트를 포함하는 CharSequence#codePoints을 반환하는 Java 8이 추가되었습니다 IntStream. 스트림을 직접 사용하여 반복 할 수 있습니다.

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

또는 스트림을 배열로 수집하여 for 루프를 사용합니다.

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

이러한 방법은 Jonathan Feinbergs의 솔루션 보다 비용이 많이 들지만 읽기 / 쓰기 속도가 더 빠르며 성능 차이는 일반적으로 중요하지 않습니다.


3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())또한 작동합니다.
saka1029

2
@ saka1029의 약간 짧은 버전 : S 코드 :for (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
LII


7

foreach 루프 ( ref ) 와 함께 작동하는 해결 방법을 추가하고 Java 8 로 이동할 때 Java 8의 새로운 String # codePoints 메서드로 쉽게 변환 할 수 있다고 생각했습니다 .

다음과 같이 foreach와 함께 사용할 수 있습니다.

 for(int codePoint : codePoints(myString)) {
   ....
 }

다음은 도우미 mthod입니다.

public static Iterable<Integer> codePoints(final String string) {
  return new Iterable<Integer>() {
    public Iterator<Integer> iterator() {
      return new Iterator<Integer>() {
        int nextIndex = 0;
        public boolean hasNext() {
          return nextIndex < string.length();
        }
        public Integer next() {
          int result = string.codePointAt(nextIndex);
          nextIndex += Character.charCount(result);
          return result;
        }
        public void remove() {
          throw new UnsupportedOperationException();
        }
      };
    }
  };
}

또는 문자열을 int 배열로 변환하려는 경우 (위의 접근 방식보다 더 많은 RAM을 사용할 수 있음) :

 public static List<Integer> stringToCodePoints(String in) {
    if( in == null)
      throw new NullPointerException("got null");
    List<Integer> out = new ArrayList<Integer>();
    final int length = in.length();
    for (int offset = 0; offset < length; ) {
      final int codepoint = in.codePointAt(offset);
      out.add(codepoint);
      offset += Character.charCount(codepoint);
    }
    return out;
  }

고맙게도 "codePoints"를 사용하여 UTF-16 (자바의 내부 문자열 표현)의 대리 쌍을 안전하게 처리합니다.

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