如何遍历Java String的unicode代码点?

ram*_*ion 99 java string unicode

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

我正在考虑尝试这样的事情:

但我担心的是

  • 我不确定自然处于高代理范围内的代码点是否会存储为两个char值或一个值
  • 这似乎是迭代字符的一种非常昂贵的方式
  • 有人必须想出更好的东西.

Jon*_*erg 138

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

如果你知道你将处理BMP之外的字符,那么这是迭代Java字符串字符的规范方法:

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 但你不应该.例如,如果您的程序输出XML,并且如果有人给它一些模糊的数学运算符,那么您的XML可能会无效. (23认同)
  • @Mechanicalsnail我不明白你的评论.为什么输出XML导致这个答案行为不端? (3认同)
  • @Gili 答案很好。他指的是@Jonathan Feinberg 的评论,其中他主张使用 `charAt()`,这是一个坏主意 (3认同)
  • 至于它是否"昂贵",那么...... Java中没有其他方法.但是如果你只处理拉丁语/欧洲语/西里尔语/希腊语/希伯来语/阿拉伯语的脚本,那么你只需要s.charAt()了解你的内心.:) (2认同)
  • 我会使用`offset = s.offsetByCodePoints(offset,1);`.使用`offset + = Character.charCount(codepoint);是否有一些好处? (2认同)
  • @PaulGroke是的.函数`offsetByCodePoints`(它重定向到`Character.offsetByCodePoints`)就像50行一样有循环和东西,同时`charCount`只是一个带有数字`if`的衬里,所以我猜有很多性能失利. (2认同)
  • 小修改使其更加“继续”友好:“final int length = s.length(); for (int codepoint, offset = 0; offset &lt; length; offset += Character.charCount(codepoint)) { codepoint = s.codePointAt(offset); } // 用代码点做一些事情 }` (2认同)

Ale*_*lex 62

Java 8添加CharSequence#codePoints了返回IntStream包含代码点的内容.您可以直接使用流来迭代它们:

string.codePoints().forEach(c -> ...);
Run Code Online (Sandbox Code Playgroud)

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

for(int c : string.codePoints().toArray()){
    ...
}
Run Code Online (Sandbox Code Playgroud)

这些方法可能比Jonathan Feinbergs的解决方案更昂贵,但它们的读/写速度更快,性能差异通常无关紧要.

  • `for (int c : (Iterable&lt;Integer&gt;) () -&gt; string.codePoints().iterator())` 也有效。 (3认同)
  • @saka1029:s 代码的略短版本:`for (int c : (Iterable&lt;Integer&gt;) string.codePoints()::iterator) ...` (2认同)

rog*_*ack 7

以为我会添加一个与foreach循环(ref)一起使用的变通方法,并且当你转移到java 8时,你可以轻松地将它转换为java 8的新String#codePoints方法:

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

 for(int codePoint : codePoints(myString)) {
   ....
 }
Run Code Online (Sandbox Code Playgroud)

这是助手方法:

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();
        }
      };
    }
  };
}
Run Code Online (Sandbox Code Playgroud)

或者,如果您只想将字符串转换为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;
  }
Run Code Online (Sandbox Code Playgroud)

谢天谢地,使用"codePoints"安全地处理UTF-16的代理配对(java的内部字符串表示).


Ale*_*ger 6

迭代代码点是作为Sun的功能请求提交的.

请参阅Sun Bug Entry

还有一个关于如何在那里迭代String CodePoints的例子.

  • Java 8现在有一个内置于String的codePoints()方法:http://docs.oracle.com/javase/8/docs/api/java/lang/CharSequence.html#codePoints (5认同)