Обработка суррогатных значений Unicode в строках Java - PullRequest
9 голосов
/ 08 июня 2009

Рассмотрим следующий код:

byte aBytes[] = { (byte)0xff,0x01,0,0,
                  (byte)0xd9,(byte)0x65,
                  (byte)0x03,(byte)0x04, (byte)0x05, (byte)0x06, (byte)0x07,
                  (byte)0x17,(byte)0x33, (byte)0x74, (byte)0x6f,
                   0, 1, 2, 3, 4, 5,
                   0 };
String sCompressedBytes = new String(aBytes, "UTF-16");
for (int i=0; i<sCompressedBytes.length; i++) {
    System.out.println(Integer.toHexString(sCompressedBytes.codePointAt(i)));
}

Получает следующий неверный вывод:

ff01, 0, fffd, 506, 717, 3374, 6f00, 102, 304, 500.

Однако, если 0xd9 во входных данных изменяется на 0x9d, то получается следующий правильный вывод:

ff01, 0, 9d65, 304, 506, 717, 3374, 6f00, 102, 304, 500.

Я понимаю, что функциональность обусловлена ​​тем фактом, что байт 0xd9 является высокосуррогатным маркером Unicode.

Вопрос: Есть ли способ подачи, идентификации и извлечения суррогатных байтов (0xd800 до 0xdfff) в строке Java Unicode?
Спасибо

Ответы [ 2 ]

10 голосов
/ 08 июня 2009

РЕДАКТИРОВАТЬ: Это отвечает на вопрос из комментария

Если вы хотите закодировать произвольные двоичные данные в строку, вы должны не использовать обычную кодировку текста. У вас нет действительного текста в этой кодировке - у вас просто произвольные двоичные данные.

Base64 - путь сюда. В Java нет поддержки base64 напрямую (во всяком случае, в открытом классе), но вы можете использовать различные сторонние библиотеки, такие как в библиотеке кодеков Apache Commons .

Да, base64 увеличит размер данных, но позволит позже декодировать их без потери информации.

РЕДАКТИРОВАТЬ: Это обращается к первоначальному вопросу

Я считаю, что проблема в том, что вы не указали правильную суррогатную пару . Вы должны указать байты, представляющие низкий суррогат, а затем высокий суррогат. После этого вы сможете добавить соответствующий код. В вашем случае, вы дали низкий суррогат сам по себе.

Вот код, демонстрирующий это:

public class Test
{
    public static void main(String[] args)
        throws Exception // Just for simplicity
    {
        byte[] data = 
        {
            0, 0x41, // A
            (byte) 0xD8, 1, // High surrogate
            (byte) 0xDC, 2, // Low surrogate
            0, 0x42, // B
        };

        String text = new String(data, "UTF-16");

        System.out.printf("%x\r\n", text.codePointAt(0));
        System.out.printf("%x\r\n", text.codePointAt(1));
        // Code point at 2 is part of the surrogate pair
        System.out.printf("%x\r\n", text.codePointAt(3));       
    }
}

Выход:

41
10402
42
4 голосов
/ 09 июня 2009

Есть ли способ подачи, идентификации и извлечения суррогатных байтов (от 0xd800 до 0xdfff) в строке Java Unicode?

Поскольку никто не упомянул об этом, я укажу, что класс Character включает методы для работы с суррогатными парами. Например. isHighSurrogate (char) , codePointAt (CharSequence, int) и toChars (int) . Я понимаю, что это не только вопрос поставленной проблемы.

new String(aBytes, "UTF-16");

Это операция декодирования, которая преобразует входные данные. Я почти уверен, что это недопустимо, потому что выбранная операция декодирования требует, чтобы ввод начинался с 0xfe 0xff или 0xff 0xfe ( метка порядка байтов ). Кроме того, не каждое возможное значение байта может быть правильно декодировано, поскольку UTF-16 представляет собой кодировку переменной ширины .

Если вы хотите симметричное преобразование произвольных байтов в строку и обратно, вам лучше использовать 8-битную однобайтовую кодировку, поскольку каждое значение байта является допустимым символом:

Charset iso8859_15 = Charset.forName("ISO-8859-15");
byte[] data = new byte[256];
for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) {
  data[i - Byte.MIN_VALUE] = (byte) i;
}
String asString = new String(data, iso8859_15);
byte[] encoded = asString.getBytes(iso8859_15);
System.out.println(Arrays.equals(data, encoded));

Примечание: количество символов будет равно количеству байтов (удвоение размера данных); результирующая строка не обязательно будет пригодна для печати (как бы она могла содержать набор управляющих символов ).

Я с Джоном , хотя - помещать произвольные последовательности байтов в строки Java почти всегда плохая идея.

...