Java에서 "대리 쌍"이란 무엇입니까?


149

에 대한 설명서 StringBuffer, 특히 reverse () 메서드 를 읽고있었습니다 . 이 문서에는 서로 게이트 쌍 에 대해 언급되어 있습니다. 이 맥락에서 대리 쌍은 무엇입니까? 그리고 무엇 낮은높은 대리는?


3
UTF-16 용어이며 여기에 설명되어 있습니다. download.oracle.com/javase/6/docs/api/java/lang/…
wkl

1
이 방법은 버그가 있습니다. 전체 코드 코드 코드 포인트를 분리해야합니다. 코드 코드 단위는 분리 하지 않아야합니다. 버그는 특정 레거시 메소드가 코드 포인트가 아닌 개별 문자 단위에서만 작동한다는 것입니다. 이는 문자 단위뿐만 아니라 구성 하려는 String 것입니다. 너무 나쁜 Java에서는 OO를 사용하여 문제를 해결할 수 없지만 String클래스와 StringBuffer클래스가 모두 크기 조정되었습니다 final. 말해봐, 그건 살인 사건이 아닌가? :)
tchrist

2
@tchrist이 문서 (및 소스)는 코드 포인트 문자열로 반전한다고 말합니다. (아마 1.0.2은 그렇게하지 않았고, 요즘 행동의 이러한 변화를 얻을 수 없었어요.)
톰 Hawtin의 - tackline

답변:


127

"서로 게이트 쌍"이라는 용어는 UTF-16 인코딩 체계에서 높은 코드 포인트로 유니 코드 문자를 인코딩하는 수단을 의미합니다.

유니 코드 문자 인코딩에서 문자는 0x0과 0x10FFFF 사이의 값에 매핑됩니다.

내부적으로 Java는 UTF-16 인코딩 체계를 사용하여 유니 코드 텍스트 문자열을 저장합니다. UTF-16에서는 16 비트 (2 바이트) 코드 단위가 사용됩니다. 16 비트는 0x0에서 0xFFFF까지의 문자 범위 만 포함 할 수 있으므로이 범위 (0x10000에서 0x10FFFF) 이상의 값을 저장하는 데 약간의 추가 복잡성이 사용됩니다. 이것은 서로 게이트로 알려진 코드 단위 쌍을 사용하여 수행됩니다.

대리 코드 단위는 두 코드 단위 시퀀스의 시작 또는 끝에 허용되는지에 따라 "고 대리자"및 "저 대리자"로 알려진 두 범위에 있습니다.


4
이것은 가장 많은 표를 얻었지만 단일 코드 예제는 제공하지 않습니다. 실제로 어떻게 사용하는지에 대한 대답은 없습니다. 이것이 이것이 다운 보트되는 이유입니다.
조지 사비에르

57

초기 Java 버전은 16 비트 char 데이터 유형을 사용하여 유니 코드 문자를 나타냅니다. 모든 유니 코드 문자의 값은 65,535 (0xFFFF)보다 작고 16 비트로 표현 될 수 있기 때문에이 디자인은 당시에 의미가있었습니다. 그러나 나중에 유니 코드는 최대 값을 1,114,111 (0x10FFFF)로 늘 렸습니다. 16 비트 값이 너무 작아서 유니 코드 버전 3.1에서 모든 유니 코드 문자를 표시 할 수 없으므로 UTF-32 인코딩 체계에 코드 포인트라고하는 32 비트 값이 채택되었습니다. 그러나 효율적인 메모리 사용을 위해서는 16 비트 값이 32 비트 값보다 선호되므로 유니 코드는 16 비트 값을 계속 사용할 수 있도록 새로운 디자인을 도입했습니다. UTF-16 인코딩 체계에서 채택 된이 디자인은 1,024 개의 값을 16 비트 높은 대리자 (U + D800-U + DBFF 범위)에 할당하고 다른 1,024 개의 값은 16 비트의 낮은 대리자 (U + DC00 범위)에 할당합니다. U + DFFF로).


7
허용 된 답변보다 이것을 좋아합니다. 유니 코드 3.1이 원래 65535에서 1024 + 1024 (높음 + 낮음) 값을 1024 * 1024 새 값으로 얻는 방법을 설명하기 때문에 파서가 시작될 때 추가 요구 사항이 없습니다. 끈.
Eric Hirst

1
UTF-16이 가장 메모리 효율적인 유니 코드 인코딩임을 암시하는 것에 대한이 답변이 마음에 들지 않습니다. UTF-8이 존재하며 대부분의 텍스트를 2 바이트로 렌더링 하지 않습니다 . UTF-16은 오늘날 마이크로 소프트가 메모리 효율성이 아닌 UTF-32보다 먼저 선택했기 때문에 주로 사용됩니다. 유일한 시간에 대해 실제로 것입니다 원하는 Windows에서 파일 처리의 많은 일을 할 때 UTF-16, 따라서 읽기 모두 그것을 많이 쓰고. 그렇지 않으면, 고속 (b / c 상수 오프셋)의 경우 UTF-32 또는 낮은 메모리 (b / c 최소 1 바이트)의 UTF-8
Fund Monica의 소송

23

그 문서에서 말하는 것은 유효하지 않은 UTF-16 문자열은 reverse유효한 문자열의 반대 일 수 있기 때문에 메소드를 호출 한 후에 유효해질 수 있다는 것입니다. 서로 게이트 쌍 ( 여기서 설명 )은 단일 유니 코드 코드 포인트를 인코딩하는 UTF-16의 16 비트 값 쌍입니다. 낮고 높은 대리모는 그 인코딩의 두 반쪽입니다.


6
설명. 문자열은 "true"문자 ( "graphemes"또는 "text elements")로 바꿔야합니다. 단일 "문자"코드 포인트는 하나 또는 두 개의 "char"청크 (대리 쌍) 일 수 있으며, grapheme은 하나 이상의 해당 코드 포인트 (예 : 기본 문자 코드 + 하나 이상의 결합 문자 코드) 일 수 있습니다. 하나 또는 두 개의 16 비트 청크 또는 "문자"길이 일 수 있습니다. 따라서 하나의 grapheme은 두 개의 "chars"길이의 총 3 개의 "chars"문자를 결합한 3 개의 문자 일 수 있습니다. 전체 문자열을 반전시킬 때 6 개의 "문자"를 순서대로 (즉, 역순으로) 유지해야합니다.
Triynko

4
따라서 "char"데이터 형식은 다소 오해의 소지가 있습니다. "문자"는 느슨한 용어입니다. "char"유형은 실제로 UTF16 청크 크기 일 뿐이며 대리 쌍의 상대적 희귀 성 때문에 문자라고합니다 (즉, 일반적으로 전체 문자 코드 포인트를 나타냄). "문자"는 실제로 단일 유니 코드 코드 포인트를 나타냅니다. 그러나 결합 문자를 사용하면 단일 "문자 / 그래프 / 텍스트 요소"로 표시되는 일련의 문자를 가질 수 있습니다. 이것은 로켓 과학이 아닙니다. 개념은 간단하지만 언어가 혼동됩니다.
Triynko

Java가 개발 될 당시 유니 코드는 초기 단계였습니다. Java는 약 5 년 동안 유니 코드가 대리 쌍을 갖기 전까지 있었으므로 당시에는 16 비트 문자가 잘 맞았습니다. 이제 UTF-16보다 UTF-8 및 UTF-32를 사용하는 것이 훨씬 좋습니다.
조나단 볼드윈

23

게시물 의 위 답변에 더 많은 정보를 추가하십시오 .

Java-12에서 테스트되었으며 5 이상의 모든 Java 버전에서 작동합니다.

여기에 언급 된 바와 같이 : https://stackoverflow.com/a/47505451/2987755 ,
어느 문자 (유니 코드가 U + FFFF보다 높음)는 대리 쌍으로 표시되며 Java는 문자 값 쌍, 즉 단일 유니 코드로 저장됩니다. 문자는 두 개의 인접한 Java 문자로 표시됩니다.
다음 예제에서 볼 수 있듯이
1. 길이 :

"🌉".length()  //2, Expectations was it should return 1

"🌉".codePointCount(0,"🌉".length())  //1, To get the number of Unicode characters in a Java String  

2. 평등 : 아래와 같이
유니 코드 \ud83c\udf09를 사용하여 "🌉"를 문자열로 나타내고 평등을 확인하십시오.

"🌉".equals("\ud83c\udf09") // true

Java는 UTF-32를 지원하지 않습니다

"🌉".equals("\u1F309") // false  

3. 유니 코드 문자를 Java 문자열로 변환 할 수 있습니다

"🌉".equals(new String(Character.toChars(0x0001F309))) //true

4. String.substring ()은 보충 문자를 고려하지 않습니다

"🌉🌐".substring(0,1) //"?"
"🌉🌐".substring(0,2) //"🌉"
"🌉🌐".substring(0,4) //"🌉🌐"

이를 해결하기 위해 우리는 사용할 수 있습니다 String.offsetByCodePoints(int index, int codePointOffset)

"🌉🌐".substring(0,"🌉🌐".offsetByCodePoints(0,1) // "🌉"
"🌉🌐".substring(2,"🌉🌐".offsetByCodePoints(1,2)) // "🌐"

5. 순회 유니 코드 문자열 의 BreakIterator
유니 코드 6. 정렬 문자열 java.text.Collator
7. 문자의 toUpperCase(), toLowerCase(), 방법은 사용하지 말아야 대신 사용하는 문자열 대문자와 특정 로케일의 소문자.
8. Character 클래스의 각 메소드에 대해 Character.isLetter(char ch)더 나은 사용법을 지원하지 않으므로 보충 문자를 처리 할 수 있는 유형 이 있습니다. 의 캐릭터 세트를 지정 9. 문자열을 바이트로 변환,, ,Character.isLetter(int codePoint)methodName(char ch)methodName(int codePoint)
String.getBytes()InputStreamReaderOutputStreamWriter

참조 :
https://coolsymbol.com/emojis/emoji-for-copy-and-paste.html#objects
https://www.online-toolz.com/tools/text-unicode-entities-convertor.php
https : //www.ibm.com/developerworks/library/j-unicode/index.html
https://www.oracle.com/technetwork/articles/javaee/supplementary-142654.html

예제 image1 image2 에 대한 추가 정보
탐색해야 할 다른 용어 : Normalization , BiDi


2
이 답변에 투표하기 위해 특별히 로그인했습니다 (창을 시크릿에서 일반 : P로 변경했음을 의미합니다). 멍청한 놈에 대한 최고의 설명
N-JOY

1
감사합니다!, 도움이되었지만 원래 게시물 작성자에게 감사의 말을 전합니다.
dkb

좋은 예! 나는 그것을 공표하기 위해 로그인했다 :) 그리고 다시, 그것은 Java가 왜 KNOWN 버그를 그들의 코드에 계속 유지시키는지를 이해하지 못한다고 생각하게했다. 나는 그들이 기존 코드를 깨고 싶지 않지만 계속해서 ...이 버그를 해결하는 데 몇 시간을 잃었습니까? 파손 된 경우 수리하십시오.
프란츠 D.


6

작은 서문

  • 유니 코드는 코드 포인트를 나타냅니다. 각 코드 포인트는 유니 코드 표준에 따라 8 비트, 16 비트 또는 32 비트 블록으로 인코딩 될 수 있습니다.
  • 버전 3.1 이전에는 주로 UTF-8로 알려진 8 비트 인코딩과 UCS-2 또는 "2 옥텟으로 코딩 된 범용 문자 세트"로 알려진 16 비트 인코딩이 사용되었습니다. UTF-8은 유니 코드 포인트를 1 바이트 블록 시퀀스로 인코딩하는 반면 UCS-2는 항상 2 바이트를 사용합니다.

    A = 41 -UTF-8을 사용하는 8 비트 블록 1 개
    의 A = 0041 -UCS-2
    Ω 의 16 비트 블록 1 개 = CE A9 -UTF-8
    Ω 의 8 비트 블록 2 개 = 03A9-1 개의 블록 UCS-2를 사용한 16 비트

문제

컨소시엄은 16 비트가 사람이 읽을 수있는 언어를 포함하기에 충분하다고 생각하여 2 ^ 16 = 65536의 코드 값을 제공합니다. BPM 또는 Basic Multilingual Plane으로도 알려진 Plane 0에는 오늘날 65536 개의 코드 포인트 중 55,445가 포함되어 있습니다. BPM은 중국어-일본어-한국어 기호 (CJK)를 포함하여 세계의 거의 모든 인간 언어를 다룹니다.

시간이 지났고 새로운 아시아 문자 집합이 추가되었고 중국어 기호는 7 만 점 이상을 차지했습니다. 이제 표준 😺의 일부로 이모티콘 포인트 도 있습니다. 새로운 16 개의 "추가" 비행기 가 추가되었습니다. UCS-2 공간은 Plane-0보다 큰 것을 덮기에 충분하지 않았습니다.

유니 코드 결정

  1. 유니 코드를 비행기 당 17 개의 비행기 × 65 536 자로 제한 = 1114112 최대 포인트.
  2. 각 코드 포인트에 대해 32 비트를 유지하고 모든 평면을 커버하기 위해 이전 UCS-4로 알려진 UTF-32를 제공합니다.
  3. UTF-8을 동적 인코딩으로 계속 사용하고 각 코드 포인트에 대해 UTF-8을 최대 4 바이트로 제한하십시오 (예 : 포인트 당 1-4 바이트).
  4. UCS-2 지원 중단
  5. UCS-2를 기반으로 UTF-16을 작성하십시오. UTF-16을 동적으로 설정하면 포인트 당 2 바이트 또는 4 바이트가 필요합니다. High Surrogates라는 1,024 개의 U + D800–U + DBFF를 UTF-16에 할당합니다. Low Surrogates라는 1024 개의 심볼 U + DC00–U + DFFF를 UTF-16에 할당합니다.

    이러한 변경 사항으로 인해 BPM은 UTF-16에서 16 비트의 1 블록으로 처리되는 반면 모든 "보충 문자"는 2 개의 블록을 각각 16 비트 씩 표시하는 대리 쌍 ( 전체 1024x1024 = 1,048 576 포인트)으로 처리됩니다.

    높은 대리가 낮은 대리보다 우선합니다 . 이 규칙에서 벗어난 것은 잘못된 인코딩으로 간주됩니다. 예를 들어, 쌍이없는 서로 게이트는 정확하지 않고 높은 서로 게이트 앞에서는 낮은 서로 게이트가 올바르지 않습니다.

    M 'MUSICAL SYMBOL G CLEF'는 UTF-32에서
    0xD0 0x9D 0x84 0x9E (4 x 1 바이트)와 같은 한 쌍의 서로 게이트 0xD834 0xDD1E (2 x 2 바이트)로 UTF-16으로 인코딩
    됩니다. 0x0001D11E (1 x 4 바이트).

현재 상황

  • 표준에 따르면 서로 게이트는 UTF-16에만 구체적으로 할당되지만 역사적으로 일부 Windows 및 Java 응용 프로그램은 이제 서로 게이트 범위로 예약 된 UTF-8 및 UCS-2 포인트를 사용했습니다.
    잘못된 UTF-8 / UTF-16 인코딩으로 레거시 응용 프로그램을 지원하기 위해 새로운 표준 WTF-8 인 Wobbly Transformation Format이 작성되었습니다. 쌍을 이루지 않은 서로 게이트 또는 잘못된 시퀀스와 같은 임의의 서로 게이트 지점을 지원합니다. 오늘날 일부 제품은 표준을 준수하지 않고 UTF-8을 WTF-8로 취급합니다.
  • 대리 솔루션 은 서로 다른 인코딩 간 변환시 많은 보안 문제를 발생 시켰으며 대부분은 잘 처리되었습니다.

⚖ 주제를 따르기 위해 많은 역사적 세부 사항이 억제되었습니다.
최신 유니 코드 표준은 http://www.unicode.org/versions/latest 에서 찾을 수 있습니다 .


3

서로 게이트 쌍은 UTF-16에서 하나의 '코드 포인트'를 구성하는 두 개의 '코드 단위'입니다. Java 문서는 이러한 '코드 포인트'가 여전히 유효하고 반대로 '코드 단위'가 올바르게 주문되었다고 명시하고 있습니다. 또한 두 개의 짝을 이루지 않은 서로 게이트 코드 단위가 반대로되어 유효한 서로 게이트 쌍을 형성 할 수 있다고 명시되어 있습니다. 즉, 짝을 이루지 않은 코드 단위가 있으면 반대의 반대가 동일하지 않을 가능성이 있습니다!

그러나이 문서는 Graphemes에 대해 아무 것도 말하지 않습니다. 여러 코드 포인트가 결합되어 있습니다. 이는 e와 그에 따른 악센트가 여전히 전환 될 수 있음을 의미하므로 e 앞에 악센트를 배치합니다. 즉, e 앞에 다른 모음이 있으면 e에 있던 악센트가 생길 수 있습니다.

이케!

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