如何遍历Java字符串的unicode代码点?


105

所以我知道String#codePointAt(int),但是它是由char偏移量而不是由代码点偏移量索引的。

我正在考虑尝试类似的方法:

但是我担心的是

  • 我不确定自然位于高代理范围内的代码点将存储为两个char值还是一个
  • 这似乎是一种遍历字符的可怕的昂贵方法
  • 一定有人想出了更好的东西。

Answers:


143

是的,Java对字符串的内部表示使用UTF-16式编码,是的,它使用替代方案对基本多语言平面(BMP)之外的字符进行编码。

如果您知道将要处理BMP之外的字符,那么以下是遍历Java String字符的规范方法:

final int length = s.length();
for (int offset = 0; offset < length; ) {
   final int codepoint = s.codePointAt(offset);

   // do something with the codepoint

   offset += Character.charCount(codepoint);
}

2
至于它是否“昂贵”,好吧……Java中没有其他内置方法。但是,如果您只处理拉丁语/欧洲语/西里尔语/希腊语/希伯来语/阿拉伯语脚本,则只需s.charAt()就可以了。:)
乔纳森·芬伯格

24
但是你不应该。例如,如果您的程序输出XML,并且如果有人给它一些模糊的数学运算符,则您的XML可能突然无效。
机械蜗牛

2
我会用的offset = s.offsetByCodePoints(offset, 1);。使用offset += Character.charCount(codepoint);代替有什么好处吗?
Paul Groke 2015年

3
@Mechanicalsnail我不明白您的评论。为什么输出XML会导致此答案不正确?
吉利2015年

3
@吉利答案很好。他指的是@Jonathan Feinberg的评论,他在评论中主张使用charAt()这是一个坏主意
RecursiveExceptionException

72

Java 8已添加CharSequence#codePoints,它返回一个IntStream包含代码点的。您可以直接使用流对它们进行迭代:

string.codePoints().forEach(c -> ...);

或使用for循环将流收集到数组中:

for(int c : string.codePoints().toArray()){
    ...
}

这些方法可能比Jonathan Feinbergs的解决方案昂贵,但它们的读/写速度更快,并且性能差异通常不明显。


3
for (int c : (Iterable<Integer>) () -> string.codePoints().iterator())也可以。
saka1029

2
@ saka1029:s代码的简短版本:for (int c : (Iterable<Integer>) string.codePoints()::iterator) ...
Lii


7

以为我会添加一种适用于foreach循环(ref)的解决方法,另外,当您移至Java 8时,可以轻松地将其转换为Java 8的新String#codePoints方法:

您可以将它与foreach一起使用,如下所示:

 for(int codePoint : codePoints(myString)) {
   ....
 }

这是助手的方法:

public static Iterable<Integer> codePoints(final String string) {
  return new Iterable<Integer>() {
    public Iterator<Integer> iterator() {
      return new Iterator<Integer>() {
        int nextIndex = 0;
        public boolean hasNext() {
          return nextIndex < string.length();
        }
        public Integer next() {
          int result = string.codePointAt(nextIndex);
          nextIndex += Character.charCount(result);
          return result;
        }
        public void remove() {
          throw new UnsupportedOperationException();
        }
      };
    }
  };
}

或者,如果您只想将字符串转换为int数组(可能比上述方法使用更多的RAM),请执行以下操作:

 public static List<Integer> stringToCodePoints(String in) {
    if( in == null)
      throw new NullPointerException("got null");
    List<Integer> out = new ArrayList<Integer>();
    final int length = in.length();
    for (int offset = 0; offset < length; ) {
      final int codepoint = in.codePointAt(offset);
      out.add(codepoint);
      offset += Character.charCount(codepoint);
    }
    return out;
  }

值得庆幸的是,使用“ codePoints”可以安全地处理UTF-16(Java的内部字符串表示形式)的代理配对。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.