如何在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)
对于代码点=>字符表示,使用 - Unicode名称=>代码点,使用
Character.codePointOf(name)
- 对于Unicode name =>字符表示,目前不存在JDK方法。相反,可以使用Unicode名称的代码点间接地执行此操作,如上所示。例如:
Character.toString(Character.codePointOf("LATIN SMALL LETTER A WITH DIAERESIS"));
.
Character.toString(codepoint)
指出:
- 确保使用的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库可以在这里提供帮助。它有一个类UCharacter
和getCharFromName
以及其他相关的方法,可以从各种类型的字符名称字符串映射回它们所代表的int
代码点。
然而,如果你正在处理硬编码的字符名称(即源代码中引用的字符串字面量),那么一次翻译将更加有效-在源代码中使用u
转义并在必要时添加带有全名的注释-而不是每次在运行时都产生解析名称表的成本。如果字符名来自于读取文件或类似文件,那么显然你必须在运行时进行转换。
我希望这个只依赖于"普通"Java的类会对某些人有用。它利用惰性填充的查找表,可以随时通过reset(false)
调用清除以释放内存(有可能自动填充表并在需要时再次使用它)。如果要查找的字符位于较低的Unicode块(通常是这种情况),那么该表的填充时间几乎是不明显的。我添加了可选的可能性,通过调用reset(true)
来预填充整个表。
还要注意U+0007
和U+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>
中获得一些东西。