9

我有一个程序通过删除任何不是字母或数字的字符来过滤字符串。该程序支持多种语言,包括中文、俄语、阿拉伯语等。程序如下:

StringBuilder strBuilder = new StringBuilder();

for (int i = 0; i < src.length(); i++) {
    int ch = src.codePointAt(i);
    if (Character.isLetterOrDigit(ch)) {
        strBuilder.appendCodePoint(ch);
    }
}

我使用codePointAt方法通过高和低代理支持以 UTF 32 位表示的字符。我需要知道在执行过滤之前是否需要对每个字符串进行规范化?我指的是Normalizer.normalize在执行循环之前调用该方法。如果是,Normalizer.Form我应该使用哪个?

谢谢。

4

2 回答 2

4

这一切都取决于你真正希望你的算法如何表现。

例如,让我们考虑字符串"a\u0308"(u+0061ʟᴀᴛɪɴ&nbsp;sᴍᴀʟʟᴀᴀᴀ,然后是u+0308ᴄᴏᴍʙɪɴɪɴɢs),它在规范上等同于OR "ä""\u00e4"u+00e4ʟᴀᴛɪɴe4ʟᴀᴛɪɴe4ʟᴀᴛɪɴs,ssᴍᴀʟʟsᴍᴀʟʟᴍᴀʟʟʟᴇᴛᴛᴇʀᴀ 规范等效意味着您的算法不应区分这两者。使规范等效字符串具有相同行为的一种简单方法是将两者规范化为相同的规范规范化形式:NFC 或 NFD。

根据这些字符串所代表的内容,您可能希望改用兼容性等效(NFKC 或 NFKD)。例如,通常建议将其用于标识符。这两个将兼容性字符转换为其推荐的等效字符(例如 U+2126 ᴏʜᴍ sɪɢɴ 到 U+03A9 ɢʀᴇᴇᴋ ᴄᴀᴘɪᴛᴀʟ ʟᴇᴛᴛᴇʀ ᴏᴍᴇɢᴀ 或连字字符)。

不管你想要哪种等价,原则都是一样的:如果你想把等价的字符串平等地归一化,这是最简单的方法。

一旦您对所有等效字符串具有相同的行为,您需要考虑另一个问题:如果您要丢弃所有“不是字母或数字的字符”,那么带有字母和组合的字符串会发生什么标记,如"\u092C\u093F"(U+092C ᴅᴇᴠᴀɴᴀɢᴀʀɪ ʟᴇᴛᴛᴇʀ ʙᴀ 后跟 U+093F ᴅᴇᴠᴀɴᴀɢᴀʀɪ ᴠᴏᴡᴇʟ sɪɢɴ ɪ)?这是两个独立的代码点,U+093F 不是字母。这两个不构成任何规范化形式。您是否希望删除组合标记(留下ब)?

如果丢弃它们没问题,您可以使用您当前的算法。否则,您可能想要迭代字素簇,粗略地说,它是基本字符序列,后面是组合标记。JavaICU都提供了用于查找字素集群的 API(Java 将这些称为“字符中断”)。

于 2013-03-07T17:28:58.117 回答
0

请注意,您用于迭代代码点的代码并不完全正确,我相信您想要:

for(int cp, i = 0; i < s.length(); i += Character.charCount(cp)) {
    cp = s.codePointAt(i);
    // Process cp...
}

对不起,不知道你是否需要标准化。

于 2013-03-07T15:15:47.017 回答