如何在Java中真正反转String,包括BMP之外的代码点?

时间:2016-09-02 02:15:15

标签: java unicode

Java的主要目标之一是使用基本原始类型表示任何语言中的每个字形;当Java诞生时,有Unicode,此时Unicode按数字定义了字形,所有这些字形都小于或等于65535。

因此char诞生于Java:无符号的16位整数。

然而,Unicode领域的情况发生了变化。现在有许多字形,其数量大于65535。

虽然Java已经承认了这一点,并且使用代理对代表了这样的代码点(实际上,char现在是UTF-16代码单元),但标准JDK并没有提供一种方法来反转字符串代码点(例如,StringBuilder#reverse只关心个别字符。)

假设Java 8,您如何编写一个真正的字符串反转方法,即将BMP之外的代码点考虑在内?

3 个答案:

答案 0 :(得分:5)

一种方法如下:

public static String trueReverse(final String input)
{
    final Deque<Integer> queue = new ArrayDeque<>();
    input.codePoints().forEach(queue::addFirst);

    final StringBuilder sb = new StringBuilder();
    queue.forEach(sb::appendCodePoint);

    return sb.toString();
}

未优化,但功能齐全。试试这个,例如:

public final class Test
{
    public static String trueReverse(final String input)
    {
        final Deque<Integer> queue = new ArrayDeque<>();
        input.codePoints().forEach(queue::addFirst);

        final StringBuilder sb = new StringBuilder();
        queue.forEach(sb::appendCodePoint);

        return sb.toString();
    }

    public static void main(final String... args)
    {
        final String input = "abc\ud83d\udca9de";

        System.out.println(trueReverse(input));
    }
}

是的,这恰好使用a defined character ...现在,您的字体可能会或可能不会正确显示该字符。

请注意这个Unicode字符是如何在Java字符串文字中编码的:\ud83d\udca9

答案 1 :(得分:1)

此解决方案通过使用int数组避免装箱:

public static String reverse(String input) {
    int[] codePoints = input.codePoints().toArray();
    return IntStream.rangeClosed(1, codePoints.length)
            .map(i -> codePoints.length - i)
            .map(i -> codePoints[i])
            .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
            .toString();
}

答案 2 :(得分:1)

感谢@roeland提供的概念关键字,帮助我在http://developers.linecorp.com/blog/?p=3473找到相关信息。这是一个(据说)处理字形集群的实现。

static String reverseGraphemeCluster(String s) {
    StringBuilder sb = new StringBuilder();
    BreakIterator it = BreakIterator.getCharacterInstance();
    it.setText(s);

    int end = it.last();
    for (int start = it.previous();
        start != BreakIterator.DONE;
        end = start, start=it.previous()) {

        sb.append(s, start,end);
    }
    return sb.toString();

}

例如,reverseGraphemeCluster("abcde\ud83d\udca9\ud83c\udd71\ufe0f")提供"\ud83c\udd71\ufe0f\ud83d\udca9ecbda"

\ud83c\udd71\ufe0f是一个字形集群。\ud83d\udca9是BMP之外的代码点)

同样,没有装箱/拆箱,也没有中间的int数组。

如果你只想反转代码点,不管是字形集群,这里有一个更易于阅读的解决方案,并且由于既没有装箱/取消整箱的整数,也没有临时中间int[]

String reverse(String s) {
    StringBuilder sb = new StringBuilder();
    for (int i = s.length(), p=i; i > 0; i=p) {
        p = s.offsetByCodePoints(i, -1);
        sb.appendCodePoint(s.codePointAt(p));
    }
    return sb.toString();
}

或者,甚至忘记通过手动处理代理对来使用codepoint-API:

(这里我们假设字符串包含正确的代理对):

static String reverse(String s) {
    if (s == null || s.length() < 2) return s;

    StringBuilder sb = new StringBuilder();
    for (int i = s.length()-1 ; i >=0; --i) {
        if (Character.isLowSurrogate(s.charAt(i))) {
            --i;
            sb.append(s.charAt(i)).append(s.charAt(i+1));
        } else {
            sb.append(s.charAt(i));
        }
    }
    return sb.toString();
}