Обработка следующего символа (полная кодовая точка Unicode) из потока ввода Java - PullRequest
0 голосов
/ 28 января 2019

Мне нужно проанализировать входной потоковый символ UTF-8 за символом (кодовая точка UTF-8, а не символ Java).Каков наилучший подход?

Обновление вопроса, чтобы сделать его более понятным (спасибо @skomisa): Итак, следующее не потоковое:

private static String replaceNonBPMWithUnknownCharacter(final String input) {
    StringBuilder result = new StringBuilder(input.length());
    input.codePoints().forEach((codePoint) -> {
        if (isBmpCodePoint(codePoint)) {
            result.append('\ufffd');
        } else {
            result.append(isBmpCodePoint(codePoint) ? toChars(codePoint) : REPLACEMENT_CHAR);
        }
    });
    return result.toString();
}


String result = replaceNonBPMWithUnknownCharacter("\uD83D\uDE0E? X")

Я хотел бы иметь потоковую версию,Например:

InputStream stream = replaceNonBPMWithUnknownCharacter(new ByteArrayInputStream("\uD83D\uDE0E? Y".getBytes(UTF_8)))

, который использует как можно меньше процессора и памяти.Следующий вопрос похож на similair, но не является потоковым: Чтение следующего символа (полная кодовая точка Unicode) из входного потока Java .

Самое важное: Как прочитать кодовую точку из потока?(так как я могу преобразовать поток байтов, из которого я знаю, что они кодированы в UTF-8, в поток кодовых точек).

1 Ответ

0 голосов
/ 04 февраля 2019

Первое замечание:

  • Символ UTF-8 может состоять из последовательности байтов 1, 2, 3 или 4.
  • Количество байтов в символеопределяется определенными битовыми настройками в первом (или единственном) байте.Подробности см. В таблице 3.6. Распределение битов UTF-8 в спецификации Unicode .

Таким образом, общий подход:

  • Прочитать первый байти проверьте его битовую комбинацию, чтобы определить количество байтов в первом символе.
  • Считайте любые последующие байты, необходимые для первого символа.
  • Создайте String на основе байтов, которые составляютсимвола, а затем вызовите String.codePointAt(), чтобы получить его кодовую точку.
  • Добавьте эту кодовую точку к List<Integer>.
  • . Повторите все предыдущие шаги для последующих байтов до EOF.
  • Возвращает List<Integer> как поток кодовых точек.

Вот код, использующий некоторые случайные символы Unicode переменной длины байта для данных:

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static java.nio.charset.StandardCharsets.UTF_8;

public class Main {

    public static void main(String[] args) {
        String text = "¢\uD841\uDF31\u30918औWあش";
        Stream<Integer> codePoints = Main.processByteStream(new ByteArrayInputStream(text.getBytes(UTF_8)));
        codePoints.forEach(System.out::println);
    }

    /**
     * Processes a stream of bytes, and returns a Stream of Unicode codepoints
     * associated with the characters derived from that byte stream.
     *
     * @param bais ByteArrayInputStream to be processed.
     * @return A stream of Unicode codepoints derived from UTF-8 characters in the supplied stream.
     */
    private static Stream<Integer> processByteStream(ByteArrayInputStream bais) {

        int nextByte = 0;
        byte b = 0;
        byte[] utf8Bytes = null;
        int byteCount = 0;
        List<Integer> codePoints = new ArrayList<>();

        while ((nextByte = bais.read()) != -1) {
            b = (byte) nextByte;
            byteCount = Main.getByteCount(b);
            utf8Bytes = new byte[byteCount];
            utf8Bytes[0] = (byte) nextByte;
            for (int i = 1; i < byteCount; i++) { // Get any subsequent bytes for this UTF-8 character.
                nextByte = bais.read();
                utf8Bytes[i] = (byte) nextByte;
            }
            int codePoint = new String(utf8Bytes, StandardCharsets.UTF_8).codePointAt(0);
            codePoints.add(codePoint);
        }
        return codePoints.stream();
    }

    /**
     * Returns the number of bytes in a UTF-8 character based on the bit pattern
     * of the supplied byte. The only valid values are 1, 2 3 or 4. If the
     * byte has an invalid bit pattern an IllegalArgumentException is thrown.
     *
     * @param b The first byte of a UTF-8 character.
     * @return The number of bytes for this UTF-* character.
     * @throws IllegalArgumentException if the bit pattern is invalid.
     */
    private static int getByteCount(byte b) throws IllegalArgumentException {
        if ((b >= 0)) return 1;                                             // Pattern is 0xxxxxxx.
        if ((b >= (byte) 0b11000000) && (b <= (byte) 0b11011111)) return 2; // Pattern is 110xxxxx.
        if ((b >= (byte) 0b11100000) && (b <= (byte) 0b11101111)) return 3; // Pattern is 1110xxxx.
        if ((b >= (byte) 0b11110000) && (b <= (byte) 0b11110111)) return 4; // Pattern is 11110xxx.
        throw new IllegalArgumentException(); // Invalid first byte for UTF-8 character.
    }
}

Вотвыход из его запуска.Он просто перечисляет кодовые точки в возвращенных Stream<Integer>:

C:\Java\openJDK\jdk-11.0.2\bin\java.exe -javaagent:C:\Users\johndoe\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\191.4738.6\lib\idea_rt.jar=60544:C:\Users\johndoe\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\191.4738.6\bin -Dfile.encoding=UTF-8 -classpath C:\Users\johndoe\IdeaProjects\Codepoint\out\production\Codepoint Main
162
132913
12433
56
2324
87
12354
1588

Process finished with exit code 0

Примечания:

  • Возможно получить кодовую точку непосредственно из байтов символа, но это запутанный процесс .Вызов String.codePointAt() является менее эффективным, но более чистым альтернативным подходом.
  • Мне не удалось создать неверные данные.Кажется, что любые недопустимые байты каким-то образом переводятся в U + FFFD (десятичное 65533), заменяющий символ , поэтому выбрасывание IllegalArgumentException, возможно, не является необходимым.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...