2017-12-13 1 views
1

임의의 유니 코드 문자 열을 생성하려고합니다. 각각의 문자가 차지하는 바이트 수를 지정하고 싶습니다. (결국은 UTF-8 바이트 배열로 변환하기 때문에 1 ~ 4 바이트) 문자 수는 물론입니다. 예를 들어, 내가 문자 당 바이트로 내 문자열과 3의 문자 수로 (10)를 지정하면, 나는 문자열 str을 얻을해야 내가Java에서 3 바이트 (0x800에서 0xffff) UTF-8 인코딩 생성

str.getBytes(StandardCharsets.UTF_8).length 

를 호출 할 때 나는 30 바이트를 얻어야한다.

내 코드는 1, 2 및 4 바이트를 사용하여 문자에 대해 올바른 문자열을 생성합니다. 그러나 0x800에서 0xffff까지의 코드 포인트에 대해 반환 된 문자열에서 getBytes를 호출하면 매번 다른 바이트 수를 얻게됩니다. 이것이 일어날 수있는 이유는 무엇입니까?

private String generateRandomString(int numberOfCharacters, int bytesPerCharacter) { 

     int start; 
     int end; 

     switch (bytesPerCharacter) { 
      case 1: 
       start = 0; 
       end = 0x7f; 
       break; 
      case 2: 
       start = 0x80; 
       end = 0x7ff; 
       break; 
      case 3: 
       start = 0x800; 
       end = 0xffff; 
       break; 
      case 4: 
       start = 0x10000; 
       end = 0x10ffff; 
       break; 
      default: 
       throw new ArgumentException("Invalid value for the bytes per character"); 
     } 
     StringBuilder builder = new StringBuilder(numberOfCharacters); 
     int count = 0; 
     int range = end - start; 
     for (int i = 0; i < numberOfCharacters; i++) { 
      builder.appendCodePoint((int) (Math.random() * range + start)); 
     } 
     return builder.toString(); 
} 
+0

발생하는 문제를 재현하지 못하는 것 같습니다. –

+0

비 결정적입니다. 높은 numberOfCharacters (200 이상)로 시도하면 보통 재현 할 수 있습니다. – mewsicalcat

답변

1

매우 흥미로운 질문

TL; DR

대답은 생성 된 코드 포인트 중 일부는 유효한 유니 코드가 아니며 Java는이를 알고 있으며 카운트를 버리는 UTF-8로 인코딩 할 때 ?으로 대체합니다. UTF-8로 인코딩 할 때 한 바이트 만 출력되므로 3 대신에 코드 포인트.

이 예를 들어, 미만 60 바이트 밖으로 생산 일부 실행에 설명

public static void main(String[] args) { 
    int start = 0x800; 
    int end = 0xffff; 
    int range = end-start; 
    StringBuilder b = new StringBuilder(); 
    for (int i=0; i<20; i++) 
    { 
     int a = (int)(Math.random() * range + start); 
     b.appendCodePoint(a); 
     System.out.printf("Code point %5d length=%d\n", a, b.length()); 
    } 
    byte[] result = b.toString().getBytes(StandardCharsets.UTF_8); 
    System.out.println(result.length); 
    for (byte x : result) 
    { 
     // newline before any byte matching 1110 xxxx (start of 3-byte UTF-8) 
     if ((x & 0xF0) == 0xE0) System.out.println(); 
     System.out.printf("%02x ", x); 
    } 
    System.out.println(); 
} 

이 하나

Code point 35798 length=1 
Code point 30523 length=2 
Code point 43674 length=3 
Code point 2743 length=4 
Code point 64416 length=5 
Code point 2438 length=6 
Code point 15808 length=7 
Code point 56254 length=8 
Code point 20690 length=9 
Code point 48789 length=10 
Code point 52635 length=11 
Code point 9128 length=12 
Code point 8445 length=13 
Code point 27765 length=14 
Code point 63710 length=15 
Code point 53350 length=16 
Code point 41031 length=17 
Code point 25939 length=18 
Code point 56414 length=19 
Code point 46327 length=20 
56 

e8 af 96 
e7 9c bb 
ea aa 9a 
e0 aa b7 
ef ae a0 
e0 a6 86 
e3 b7 80 3f 
e5 83 92 
eb ba 95 
ec b6 9b 
e2 8e a8 
e2 83 bd 
e6 b1 b5 
ef a3 9e 
ed 81 a6 
ea 81 87 
e6 95 93 3f 
eb 93 b7 

참고 UTF-8의 진수 덤프 만 18 라인이있다 , 및 0x3f = ?. 8 번째와 19 번째 위치에서 생성 된 "코드 포인트"를 조사하면 잘못된 유니 코드 코드 포인트임을 알 수 있습니다.

  • 코드 포인트 56254
  • 코드 포인트 당신은 임의의 정수 값을 생성하고 모든 유니 코드 유효한 것으로 기대할 수 없다

    56414

결론. 그러한 코드 포인트를 포함하는 String을 인코딩하면 유효하지 않은 코드 포인트가 0x3f ('?')으로 인코딩됩니다.

+1

코드 포인트의 U + 0800 - U + FFFF 범위에서 예약 된 U + D800 - U + DFFF에주의하십시오. 유니 코드 문자열로 나타나지 않아야합니다. 또한 개인 사용 코드 포인트 인 U + E000 - U + F8FF도 있습니다. 일단 U + FFFF를 초과하면 많은 비공개/할당되지 않은 코드 포인트가 있습니다. 어쨌든 "getBytes (UTF8)"를 사용하여 건너 뛰고 코드 포인트를 "합법적"이 아니더라도 수동으로 인코딩 할 수 있습니다. UTF-8은 핸들을 사용하여 구현하기가 쉽고 비트 인코딩에 대해서만 염려하지만 비트를 그룹화하는 것 이외의 실제 코드 포인트 값은 상관하지 않습니다. –