이 문자열의 길이가 문자 수보다 긴 이유는 무엇입니까?


145

이 코드는 :

string a = "abc";
string b = "A𠈓C";
Console.WriteLine("Length a = {0}", a.Length);
Console.WriteLine("Length b = {0}", b.Length);

출력 :

Length a = 3
Length b = 4

왜? 내가 상상할 수있는 유일한 것은 한자가 2 바이트 길이이고 .Length메소드가 바이트 수를 반환한다는 것입니다.


10
제목을 보면 대리 쌍 문제라는 것을 어떻게 알았습니까? 아, 좋은 '올 System.Globalization은 당신의 동맹국입니다!
크리스 Cirefice

9
UTF-16에서 4 바이트 길이이고 2가 아닌
phuclv

char의 10 진수 값 𠈓은 131603이며 char이 부호없는 바이트이므로 4가 아닌 2 문자로 값을 얻을 수 있음을 의미합니다 (최대 16 비트 값은 65535 (또는 65536 변형)이며 2 문자를 사용하여 허용합니다. 65536 * 2 (131072)가 아니라 65536 * 65536 변형 (4,294,967,296, 사실상 32 비트 값)의 최대 변형 수
GMasucci

3
@GMAsucci : UTF-16 문자의 크기는 2 바이트이므로 UTF-16의 2 자이지만 4 바이트입니다. 그렇지 않으면 65536 변형을 저장할 수 없지만 256 개만 저장할 수 있습니다.
Kaiserludi

4
필자는 '유니 코드 및 문자 집합에 대해 반드시 알아야 할 절대 최소 모든 소프트웨어 개발자 (변명 없음)' 라는
ItsMe

답변:


232

다른 모든 사람들은 표면적 해답을 제시하지만 더 깊은 근거도 있습니다. "문자"의 수는 정의하기 어려운 질문이며 놀랍게도 계산 비용이 많이 들지만 길이 속성은 빠릅니다.

정의하기 어려운 이유는 무엇입니까? 글쎄, 몇 가지 옵션이 있으며 다른 옵션보다 더 유효한 것은 없습니다.

  • 코드 단위의 수 (바이트 또는 기타 고정 크기 데이터 청크; C # 및 Windows는 일반적으로 UTF-16을 사용하므로 2 바이트 수를 반환 함)는 컴퓨터가 여전히 해당 형식의 데이터를 처리해야하므로 관련성이 있습니다. 많은 목적을 위해 (예를 들어, 파일에 쓰는 것은 문자가 아닌 바이트를 고려합니다)

  • 유니 코드 코드 포인트의 수는 계산하기가 쉬우 며 (대리 쌍을 위해 문자열을 스캔해야하기 때문에 O (n)이지만) 텍스트 편집기에는 중요 할 수 있지만 실제로는 문자 수와 동일하지 않습니다. 화면에 인쇄됩니다 (graphemes). 예를 들어, 일부 악센트 문자는 두 가지 형식으로 표시 될 수 있습니다. 단일 코드 포인트 또는 서로 짝을 이루는 두 점 (문자를 나타내는 문자 및 "내 파트너 문자에 악센트 추가"). 쌍이 두 문자 또는 하나입니까? 이를 돕기 위해 문자열을 정규화 할 수 있지만 모든 유효한 문자에 단일 코드 포인트 표현이있는 것은 아닙니다.

  • grapheme의 수조차도 다른 요인들 중 글꼴에 따라 인쇄 문자열의 길이와 동일하지 않으며 일부 문자는 많은 글꼴 (커닝)에서 일부 겹침으로 인쇄되기 때문에 화면의 문자열 길이 그래도 그래 핀 길이의 합과 반드시 ​​같을 필요는 없습니다!

  • 일부 유니 코드 포인트는 전통적인 의미의 문자가 아니라 일종의 제어 마커입니다. 바이트 순서 마커 또는 오른쪽에서 왼쪽으로 표시기와 같습니다. 이것도 중요합니까?

간단히 말해서, 문자열의 길이는 실제로 엄청나게 복잡한 질문이며 계산하는 데 데이터 테이블뿐만 아니라 많은 CPU 시간이 걸릴 수 있습니다.

게다가 요점이 뭐야? 이러한 측정 항목이 중요한 이유 글쎄, 오직 당신 만이 당신의 사건에 대해 대답 할 수 있지만 개인적으로 나는 그것들이 일반적으로 관련이 없다는 것을 알았습니다. 내가 찾은 데이터 입력 제한은 바이트 제한에 의해보다 논리적으로 수행됩니다. 어쨌든 전송하거나 저장해야하기 때문입니다. 메시지 크기가 100 픽셀 인 경우 적합한 문자 수는 데이터 계층 소프트웨어에서 알려지지 않은 글꼴 등에 따라 다릅니다. 마지막으로, 유니 코드 표준의 복잡성을 감안할 때 다른 방법을 시도하면 어쩌면 가장자리에 버그가있을 수 있습니다.

따라서 범용 용도가 많지 않은 어려운 질문입니다. 코드 단위의 수는 계산하기가 쉽지 않습니다. 기본 데이터 배열의 길이 일 뿐이며 간단한 정의로 일반적인 규칙으로 가장 의미 있고 유용합니다.

그렇기 때문에 "문서가 그렇게 말했기 때문에"이라는 표면적 설명을 넘어선 b길이 4입니다.


9
본질적으로 '.Length'는 대부분의 코더가 생각하는 것이 아닙니다. 더 구체적인 속성 (예 : GlyphCount)과 길이가 사용되지 않음으로 표시되어 있어야합니다!
redcalx

8
@locster 동의하지만 Length배열과의 유추를 유지하기 위해 더 이상 쓸모 가 없다고 생각 합니다.
Kroltan

2
@locster 더 이상 사용되지 않아야합니다. 파이썬은 많은 의미를 가지며 아무도 그것에 대해 질문하지 않습니다.
simonzack

1
.Length는 그것이 무엇인지, 왜 그런지 이해하는 한 많은 의미가 있고 자연적인 재산이라고 생각합니다. 그런 다음 다른 배열처럼 작동합니다 (D와 같은 일부 언어의 경우 문자열은 문자 그대로 배열이 언어에 관한 한 실제로 배열입니다)
Adam D. Ruppe

4
UTF-32의 경우 lengthInBytes / 4는 코드 포인트 수를 제공 하지만 "문자"또는 그래프 수와 동일 하지는 않습니다 (일반적인 오해) . 단일 문자로 인쇄되는 단일 문자로 인쇄되는 단일 문자 E와 그 뒤에 단일 코드 포인트로 정규화 할 수는 있지만 UTF-32에서도 여전히 두 단위 길이입니다.
Adam D. Ruppe

62

속성 의 문서 에서 String.Length:

Length 속성은 이 인스턴스에서 유니 코드 문자 수가 아닌 Char 객체 수를 반환합니다 . 그 이유는 유니 코드 문자가 둘 이상의 Char 로 표시 될 수 있기 때문입니다 . 각 Char 대신 각 유니 코드 문자로 작업 하려면 System.Globalization.StringInfo 클래스를 사용하십시오 .


3
Java String b는 char 배열에서 UTF-16 표현을 사용하므로 동일한 방식으로 작동합니다 (또한 4를 인쇄 함 ). UTF-8의 4 바이트 문자입니다.
Michael

32

색인 1의 캐릭터 "A𠈓C"SurrogatePair입니다.

기억해야 할 요점은 서로 게이트 쌍이 32 비트 단일 문자를 나타냅니다 .

이 코드를 시도하면 반환됩니다 True

Console.WriteLine(char.IsSurrogatePair("A𠈓C", 1));

Char.IsSurrogatePair 메서드 (String, Int32)

trues 매개 변수가 index 및 index + 1 위치에 인접한 문자를 포함 하고 위치 인덱스 에있는 문자의 숫자 값이 U + D800-U + DBFF 범위이고, index + 1 위치에있는 문자의 숫자 값이 U 범위 인 경우 + DC00 내지 U + DFFF; 그렇지 않으면 false.

이것은 String.Length 속성 에서 더 설명됩니다 .

Length 속성은 이 인스턴스에서 유니 코드 문자 수가 아닌 Char 객체 수를 반환합니다 . 그 이유는 유니 코드 문자가 둘 이상의 Char로 표시 될 수 있기 때문입니다. 각 Char 대신 각 유니 코드 문자로 작업하려면 System.Globalization.StringInfo 클래스를 사용하십시오.


24

다른 답변에서 지적했듯이 3 개의 보이는 문자가 있어도 4 개의 char객체 로 표시 됩니다. 이것이 Length3이 아닌 4 인 이유 입니다.

MSDN은

Length 속성은이 인스턴스에서 유니 코드 문자 수가 아닌 Char 객체 수를 반환합니다.

그러나 실제로 알고 싶은 것이 "텍스트 요소"의 수이고 Char객체 의 수가 아니라면 StringInfo클래스를 사용할 수 있습니다 .

var si = new StringInfo("A𠈓C");
Console.WriteLine(si.LengthInTextElements); // 3

다음과 같이 각 텍스트 요소를 열거 할 수도 있습니다.

var enumerator = StringInfo.GetTextElementEnumerator("A𠈓C");
while(enumerator.MoveNext()){
    Console.WriteLine(enumerator.Current);
}

foreach문자열을 사용 하면 중간 "글자"가 두 char개체로 분할 되고 인쇄 된 결과가 문자열과 일치하지 않습니다.


20

Length속성 이 유니 코드 문자 수가 아닌 char 객체 수를 반환 하기 때문 입니다. 귀하의 경우, 유니 코드 문자 중 하나는 둘 이상의 char 객체 (SurrogatePair)로 표현됩니다.

Length 속성은이 인스턴스에서 유니 코드 문자 수가 아닌 Char 객체 수를 반환합니다. 그 이유는 유니 코드 문자가 둘 이상의 Char로 표시 될 수 있기 때문입니다. 각 Char 대신 각 유니 코드 문자로 작업하려면 System.Globalization.StringInfo 클래스를 사용하십시오.


1
이 답변에서 "문자"를 모호하게 사용합니다. 적어도 첫 번째 용어를 정확한 용어로 바꾸는 것이 좋습니다.
궤도에서 가벼움 경주

1
감사합니다. 모호성을 수정했습니다.
유발 이츠 차 코프

10

다른 사람들이 말했듯이 문자열의 문자 수가 아니라 Char 객체의 수입니다. 문자 𠈓는 코드 포인트 U + 20213입니다. 값이 16 비트 char 유형의 범위를 벗어나므로 surrogate 쌍으로 UTF-16으로 인코딩됩니다 D840 DE13.

문자의 길이를 얻는 방법은 다른 답변에서 언급되었습니다. 그러나 유니 코드로 문자를 나타내는 여러 가지 방법이있을 수 있으므로주의해서 사용해야합니다. "à"는 1 자로 구성되거나 2 자로 구성 될 수 있습니다 (+ 분음 부호). 트위터 의 경우처럼 정규화가 필요할 수 있습니다 .

당신은 절대적으로 모든 소프트웨어 개발자의 절대 최소값을 읽어야합니다
.


6

length()이보다 크지 않은 유니 코드 코드 포인트에만 작동하기 때문 U+FFFF입니다. 이 코드 포인트 세트를 BMP ( Basic Multilingual Plane )라고하며 2 바이트 만 사용합니다.

외부의 유니 코드 코드 포인트 BMP는 4 바이트 서로 게이트 쌍을 사용하여 UTF-16으로 표시됩니다.

문자 수 (3)를 올바르게 세려면 StringInfo

StringInfo b = new StringInfo("A𠈓C");
Console.WriteLine(string.Format("Length 2 = {0}", b.LengthInTextElements));

6

.Net 및 C #에서 모든 문자열은 UTF-16LE 로 인코딩됩니다 . A string는 일련의 문자로 저장됩니다. 각각 char은 2 바이트 또는 16 비트의 스토리지를 캡슐화합니다.

"종이 또는 화면에서"를 단일 문자, 문자, 글리프, 기호 또는 문장 부호로 보는 것은 단일 텍스트 요소로 생각할 수 있습니다. Unicode Standard Annex # 29 UNICODE TEXT SEGMENTATION에 설명 된대로 각 텍스트 요소는 하나 이상의 코드 포인트로 표시됩니다. 전체 코드 목록은 여기에서 찾을 수 있습니다 .

각 코드 포인트는 컴퓨터에 의한 내부 표현을 위해 바이너리로 인코딩되어야합니다. 명시된 바와 같이, 각각 char은 2 바이트를 저장한다. 또는 그 이하의 코드 포인트 U+FFFF는 단일로 저장할 수 있습니다 char. 위의 코드 포인트 U+FFFF는 두 개의 문자를 사용하여 단일 코드 포인트를 나타내는 서로 게이트 쌍으로 저장됩니다.

우리가 지금 추론 할 수있는 것을 알면, 텍스트 요소는 하나 char의 문자, 두 문자의 대리 쌍으로 저장 되거나 텍스트 요소가 여러 코드 포인트로 표시되는 경우 단일 문자와 대리 쌍의 일부 조합 으로 저장 될 수 있습니다 . 그것이 충분히 복잡하지 않은 것처럼, 일부 텍스트 요소는 Unicode Standard Annex # 15, UNICODE NORMALIZATION FORMS에 설명 것처럼 코드 포인트의 다른 조합으로 표현 될 수 있습니다 .


막간

따라서 렌더링 할 때 똑같이 보이는 문자열은 실제로 다른 문자 조합으로 구성 될 수 있습니다. 이러한 두 문자열의 서수 (바이트 단위) 비교는 차이를 감지하므로 예상치 못한 또는 바람직하지 않을 수 있습니다.

.Net 문자열을 다시 인코딩 할 수 있습니다. 동일한 정규화 양식을 사용합니다. 정규화되면 같은 텍스트 요소를 가진 두 개의 문자열이 같은 방식으로 인코딩됩니다. 이렇게하려면 string.Normalize 함수를 사용하십시오 . 그러나 일부 다른 텍스트 요소는 서로 비슷하게 보입니다. :-에스


질문과 관련하여 이것이 무엇을 의미합니까? 텍스트 요소 '𠈓'는 단일 코드 포인트 U + 20213 cjk 통합 표의 문자 확장명 b로 표시 됩니다. 즉, 단일 char문자 로 인코딩 할 수 없으며 두 문자를 사용하여 서로 게이트 쌍으로 인코딩해야합니다. 이유는 string b하나 char이상 해당 string a.

텍스트 요소 수를 안정적으로 계산해야하는 경우 (주의 사항 참조) 이 클래스를 string사용해야합니다 System.Globalization.StringInfo.

using System.Globalization;

string a = "abc";
string b = "A𠈓C";

Console.WriteLine("Length a = {0}", new StringInfo(a).LengthInTextElements);
Console.WriteLine("Length b = {0}", new StringInfo(b).LengthInTextElements);

출력을주고

"Length a = 3"
"Length b = 3"

예상대로.


경고

StringInfoTextElementEnumerator클래스 에서 유니 코드 텍스트 세그먼트의 .Net 구현은 일반적으로 유용해야하며 대부분의 경우 호출자가 기대하는 응답을 생성합니다. 그러나 유니 코드 표준 부록 # 29에 명시된 바와 같이 , "텍스트에만 항상 경계를 명확하게 결정할 수있는 충분한 정보가 포함되어 있지 않기 때문에 사용자 인식 일치 목표는 항상 정확하게 충족 될 수 없습니다."


귀하의 답변이 혼란 스럽다고 생각합니다. 이 경우 𠈓는 단일 코드 포인트 일 뿐이지 만 코드 포인트가 0xFFFF를 초과하므로 서로 게이트 쌍을 사용하여 2 개의 코드 단위로 표시되어야합니다. Grapheme은 한국어의 한글 또는 많은 라틴어 기반 언어에서 볼 수 있듯이 단일 코드 포인트 또는 여러 코드 포인트로 grapheme을 표현할 수있는 코드 포인트 위에 구축 된 또 다른 개념입니다.
nhahtdh

@nhahtdh, 동의합니다. 제 대답은 잘못되었습니다. 나는 그것을 다시 작성했으며 이제는 더 큰 선명도를 얻길 바랍니다.
Jodrell
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.