如何在Java中获得其(unicode)名称的字符?我需要性格的反面.getName (int codePoint)



如何在Java中使用Unicode名称查找字符或int代码点?

例如,if

Character.getName('u00e4')

返回"LATIN SMALL LETTER A WITH DIAERESIS",我如何使用"普通"Java执行反向操作(即从"LATIN SMALL LETTER A WITH DIAERESIS"'u00e4') ?

编辑:为了阻止我想要或不想要的评论洪流,我会在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中做同样的事情。

而且,顺便说一句,我不想"打印unicode转义"——实际上,为char获取十六进制很容易,但我想要一个包含给定名称的字符。

换句话说我想做与Character.getName(int)相反的事情。

对于JDK 9及以后的版本,使用静态方法Character.codePointOf(String name)是最简单的方法:

public static int codePointOf (String name)

返回由给定的Unicode字符名指定的Unicode字符的码点值。

这适用于所有unicode字符,而不仅仅是基本多语言平面中的字符。例如,在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名称,使用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中,而Unicode 11仅由JDK版本>= 12支持。如果你使用较早的JDK版本运行,在调用Character.codePointOf("DNA Double Helix")时会得到一个IllegalArgumentException
  • 如果一个白色方块被显示在Unicode字符的位置,然后尝试改变字体(例如Segoe UI Emoji用于渲染Emoji字符)。

ICU4J库可以在这里提供帮助。它有一个类UCharactergetCharFromName以及其他相关的方法,可以从各种类型的字符名称字符串映射回它们所代表的int代码点。

然而,如果你正在处理硬编码的字符名称(即源代码中引用的字符串字面量),那么一次翻译将更加有效-在源代码中使用u转义并在必要时添加带有全名的注释-而不是每次在运行时都产生解析名称表的成本。如果字符名来自于读取文件或类似文件,那么显然你必须在运行时进行转换。

我希望这个只依赖于"普通"Java的类会对某些人有用。它利用惰性填充的查找表,可以随时通过reset(false)调用清除以释放内存(有可能自动填充表并在需要时再次使用它)。如果要查找的字符位于较低的Unicode块(通常是这种情况),那么该表的填充时间几乎是不明显的。我添加了可选的可能性,通过调用reset(true)来预填充整个表。

还要注意U+0007U+1F514之间存在已知的Unicode名称冲突。对于前者,Java的Character.getName()仍然返回"BELL"。所呈现的类试图修复这个问题,至少对于反向操作,为分配给它的批准的唯一名称"ALERT"返回U+0007

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;
        }
    }
}

好,看看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 !在我的机器上,位于Java安装的resources.jar中。还是一堆字节。因此,您可以自己解析该文件(不是很有趣,需要大量的数据处理),或者使用库(如上所述)。因此,如果您仅限于本地Java,您可能想看看CharacterName类,看看是否可以在HashMap<String, Character>中获得一些东西。

相关内容

最新更新