如何获取反转字符串(unicode 安全)

2022-09-02 10:59:48

假设我们要还原以下字符串“áe”。

其 unicode 是“\u0061\u0301\u0065”。

恢复它的天真无邪的挑衅将是一个又一个的字符

private static String reverseStringNaive(String s) {
    char[] characters = new char[s.length()];
    for (int i = s.length() - 1; i >= 0; i--) {
        int j = s.length() - i - 1;
        characters[j] = s.charAt(i); 
    }
    return new String(characters);
}

这给了我们“éa”(\u0065\u0301\u0061)当我们希望得到“eá”(\u0065\u0061\u0301)时。重音“'”应与“a”一起,而不是更改为“e”。

下面的代码为我提供了该字符串的预期结果:

private static String reverseString(String s) {
    char[] characters = new char[s.length()];
    for (int i = s.length() - 1; i >= 0; i--) {
        int j = s.length() - i - 1;
        if (Character.isLetterOrDigit(s.charAt(i)) || Character.isISOControl(s.charAt(i))) {
            characters[j] = s.charAt(i); 
        } else {
            characters[j] = s.charAt(i-1);
            characters[j+1] = s.charAt(i);
            i--;
        }
    }
    return new String(characters);
}

我正在检查每个字符是字母,数字还是ISO控制。如果没有,我认为它应该与前一个字符粘在一起。

问题是,还有其他事情我应该检查或担心吗?我的阿普鲁克还天真吗?


答案 1

也可以通过将字符串转换为规范分解形式 NFC 来解决您的问题。基本上,java.text.Normalizer 类可用于将重音符号和其他组合字符与其基本字符组合在一起,以便您能够正确反转。

所有这些其他想法(String.reverse(),StringBuffer.reverse())将正确地反转缓冲区中的字符,但是如果您从分解的字符开始,则可能无法:)获得预期的内容。

在一些“分解形式”中,重音字符与其基本形式(作为单独的字符)分开存储,但在“组合”形式中,它们不是。因此,在一种形式中,“áe”存储为三个字符,而在另一种形式中,组合形式存储为两个字符。

但是,这种规范化不足以处理其他类型的字符组合,也不能解释Unicode星体平面中的字符,这些字符在Java中存储为两个字符(或更多?)。

感谢 tchrist 指出 ICU 对文本分割的支持,包括扩展的字素簇,例如在下面的评论中确定的字素簇(参见 virama)。这个资源似乎是关于这种东西的权威信息来源。


答案 2