Java的主要目标之一是使用基本原始类型表示任何语言中的每个字形;当Java诞生时,有Unicode,此时Unicode按数字定义了字形,所有这些字形都小于或等于65535。
因此char
诞生于Java:无符号的16位整数。
然而,Unicode领域的情况发生了变化。现在有许多字形,其数量大于65535。
虽然Java已经承认了这一点,并且使用代理对代表了这样的代码点(实际上,char
现在是UTF-16代码单元),但标准JDK并没有提供一种方法来反转字符串代码点(例如,StringBuilder#reverse
只关心个别字符。)
假设Java 8,您如何编写一个真正的字符串反转方法,即将BMP之外的代码点考虑在内?
答案 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();
}