Как получить символ по его (unicode) имени в Java? Мне нужна обратная сторона Character.getName(int codePoint)

Как найти символ или int codepoint в Java, используя его имя Unicode?

Например, если

Character.getName('\u00e4')

возвращает "LATIN SMALL LETTER A WITH DIAERESIS", как выполнить обратную операцию (т.е. перейти от "LATIN SMALL LETTER A WITH DIAERESIS" в '\u00e4') с помощью "простой" Java?

Изменить: Чтобы остановить торрент комментариев, что я хочу, или я не хочу, вот что я хотел бы сделать в Python:

"\N{LATIN SMALL LETTER A WITH DIAERESIS}" # this gives me what I want as a literal

unicodedata.lookup("LATIN SMALL LETTER A WITH DIAERESIS") # a dynamic version

Теперь возникает вопрос: выполните то же самое в Java.

И, BTW, я не хочу "печатать unicode escapes" - на самом деле получение hex для char очень просто, но я хочу, чтобы char имел данное имя.

Другими словами, я хочу сделать обратное тому, что делает Character.getName(int).

Ответ 1

Для выпуска JDK 9 и более поздних версий статический метод Character.codePointOf(String name) является наиболее простым подходом:

public static int codePointOf​(String name)

Возвращает значение кодовой точки символа Unicode, указанного в имени данного символа Unicode.

Это работает для всех персонажей Uniocde, а не только для тех, кто находится на базовой многоязычной плоскости. Например, запуск этого кода на Java 12...

String s1 = "LATIN SMALL LETTER A WITH DIAERESIS";
int cp1 = Character.codePointOf(s1);
System.out.println("Unicode name \"" + Character.getName(cp1) + "\" => code point " + cp1 + " => character " + Character.toString(cp1));

String s2 = "EYES";
int cp2 = Character.codePointOf(s2);
System.out.println("Unicode name \"" + Character.getName(cp2) + "\" => code point " + cp2 + " => character " + Character.toString(cp2));

String s3 = "DNA Double Helix"; // Only works with JDK12 and later. Otherwise java.lang.IllegalArgumentException is thrown.
int cp3 = Character.codePointOf(s3);
System.out.println("Unicode name \"" + Character.getName(cp3) + "\" => code point " + cp3 + " => character " + Character.toString(cp3));

... производит этот вывод...

Unicode name "LATIN SMALL LETTER A WITH DIAERESIS" => code point 228 => character ä
Unicode name "EYES" => code point 128064 => character 👀
Unicode name "DNA DOUBLE HELIX" => code point 129516 => character 🧬

Чтобы подвести итог конверсий:

  • Для кодовой точки => Unicode name, используйте Character.getName(codepoint)
  • Для кодовой точки => представления символов используйте Character.toString(codepoint)
  • Для имени Unicode => кодовая точка, используйте Character.codePointOf(name)
  • Для Unicode name => символьное представление, в настоящее время не существует метода JDK. Вместо этого сделайте это косвенно, используя кодовую точку имени Unicode, как показано выше. Например: Character.toString(Character.codePointOf("LATIN SMALL LETTER A WITH DIAERESIS"));.

Примечания:

  • Убедитесь, что используемый выпуск JDK поддерживает указанные имена Unicode. Например, персонаж с именем Unicode "DNA Double Helix" был добавлен в Unicode 11, который поддерживается только в выпусках JDK> = 12. Если вы используете более раннюю версию JDK, вы получите IllegalArgumentException при вызове Character.codePointOf("DNA Double Helix").
  • Если вместо символа Unicode отображается белый квадрат, попробуйте изменить шрифт (например, Segoe UI Emoji для рендеринга символов Emoji).

Ответ 2

Библиотека ICU4J может помочь вам здесь. Он имеет класс UCharacter с getCharFromName и другие связанные методы, которые могут отображаться из различных типов строк имен символов обратно в int которые они представляют.

Однако, если вы работаете с жестко закодированными именами символов (т.е. цитированными строковыми литералами в исходном коде), тогда было бы гораздо эффективнее выполнить перевод один раз - используйте escape-код \u в исходном коде и добавьте комментировать с полным именем, если это необходимо, вместо того, чтобы брать на себя затраты на парсинг таблиц имен во время выполнения каждый раз. Если имена символов поступают от чтения файла или аналогичного, то, очевидно, вам придется преобразовать во время выполнения.

Ответ 3

Ну, посмотрев исходный код для Character.class:

public static String getName(int codePoint) {
    if (!isValidCodePoint(codePoint)) {
        throw new IllegalArgumentException();
    }
    String name = CharacterName.get(codePoint);
    if (name != null)
        return name;
    ...
}

CharacterName - это пакет-частный класс, который лениво инициализирует пул SoftReference<byte[]> имен символов (я думаю). Одна строка, в частности, представляет интерес, хотя, зарывается внутри серии различных конструкторов входных потоков:

private static synchronized byte[] initNamePool() {
    ...
        return getClass().getResourceAsStream("uniName.dat");
    ...
}

Теперь я делаю рытье, и по какой-то причине этот uniName.dat, похоже, не существует в источнике OpenJDK. Я нашел uniName.dat - как часть моего дистрибутива TeX Live, как ни странно. Открытие его в шестнадцатеричном редакторе показывает беспорядки байтов - поэтому содержимое каким-то образом кодируется. Как, я понятия не имею. Я рассмотрю второй исходный код, но может потребоваться некоторое время для декодирования, если я смогу понять это вообще.

Кроме того, отладчик в моей копии Eclipse, по-видимому, сломан (не может разрешить переменные по той или иной причине), поэтому я не могу проверить входной поток, чтобы попытаться увидеть, где он читает.

Короче говоря, похоже, что вы не можете сделать это в родной Java, если не чувствуете, что вы копируете код пула имен из CharacterName или сворачиваете свой собственный код, который расшифровывает этот файл (если вы его найдете)


Изменить: Найдено uniName.dat! На моей машине, расположенной в resources.jar на Java-установке. Еще куча байтов. Таким образом, вы можете либо самостоятельно разобрать этот файл (не очень весело, вовлекаете много бит), либо использовать библиотеку (предложенную выше). Поэтому, если вы ограничены родной Java, вы можете взглянуть на класс CharacterName и посмотреть, можете ли вы что-то сделать в HashMap<String, Character>.

Ответ 4

Надеюсь, этот класс, опирающийся только на "простую" Java, будет кому-то полезен. Он использует ленивую справочную таблицу, которая может быть очищена в любое время с помощью вызова reset(false) для освобождения памяти (с возможностью автоматического заполнения таблицы и ее использования в случае необходимости). Если искомые символы находятся в нижних блоках Юникода (как это обычно бывает), тогда время заполнения этой таблицы практически незаметно. Я добавил опциональную возможность предварительно заполнить всю таблицу с помощью вызова reset(true).

Также обратите внимание, что существует известное коллизия имен Unicode между U+0007 и U+1F514. Java Character.getName() по-прежнему возвращает "BELL" для первого. Представляемый класс пытается исправить это, по крайней мере, для обратной операции, возвращая U+0007 для утвержденного уникального имени "ALERT", присвоенного ему.

import java.util.Map;
import java.util.HashMap;

public class UnicodeTable {
    public static final char INVALID_CHAR = '\uFFFF';
    private static final Map<String, Integer> charMap = new HashMap<>();
    private static boolean incomplete;
    private static int lastLookup;

    static {
        reset(false);
    }

    public static int getCodePoint(String name) {
        Integer cp = charMap.get(name);
        if (cp == null && incomplete) {
            while (++lastLookup <= Character.MAX_CODE_POINT) {
                String uName = Character.getName(lastLookup);
                if (uName != null) {
                    charMap.put(uName, lastLookup);
                    if (uName.equals(name))
                        return lastLookup;
                }
            }
            incomplete = false;
        }
        return cp == null ? INVALID_CHAR : cp;
    }

    public static char getChar(String name) {
        int cp = getCodePoint(name);
        return Character.isBmpCodePoint(cp) ? (char)cp : INVALID_CHAR;
    }

    private static final int ALERT = 0x000007;
    private static final int BELL = 0x01F514;

    public static void reset(boolean fillUp) {
        if (!fillUp) {
            charMap.clear();
            incomplete = true;
            lastLookup = Character.MIN_CODE_POINT - 1;
            charMap.put("ALERT", ALERT);
            String bName = Character.getName(BELL);
            if (bName.equals(Character.getName(ALERT))) {
                getCodePoint(bName);
                charMap.put(bName, BELL);
            }
        } else if (incomplete) {
            while (++lastLookup <= Character.MAX_CODE_POINT) {
                String uName = Character.getName(lastLookup);
                if (uName != null)
                    charMap.put(uName, lastLookup);
            }
            incomplete = false;
        }
    }
}