将char与码点进行比较?

38

如何“正确”地将一个码点与Java字符进行比较?例如:

int codepoint = String.codePointAt(0);
char token = '\n';
我知道我可能可以这样做:
if (codepoint==(int) token)
{ ... }

但是这段代码看起来比较脆弱。是否有正式的API方法可以用于比较 codepointschars,或将 char 转换为 codepoint 以进行比较?

5个回答

51
一些背景:Java出现于1995年,char类型基于原始的 "Unicode 88" 规范,该规范仅限于16位。一年后,当实现Unicode 2.0时,引入了代理字符的概念,以超越16位的限制。
Java在内部以UTF-16格式表示所有String。对于超过U+FFFF的码点,使用代理对来表示该码点,即第一个为高代理项代码单元(在范围 \uD800-\uDBFF 内),第二个为低代理项代码单元(在范围 \uDC00-\uDFFF 内)的两个char
从早期开始,所有基本的Character方法都是基于这样一个假设:一个码点可以用一个char表示,因此方法签名看起来是这样的。我想为了保持向后兼容性,在Unicode 2.0出现时没有改变,处理它们时需要谨慎。引用自Java文档
  • 只接受 char 值的方法不支持补充字符。它们将代理范围内的 char 值视为未定义字符。例如,Character.isLetter('\uD840') 返回 false,即使该特定值在字符串中跟随任何低代理值,也表示一个字母。
  • 接受 int 值的方法支持所有 Unicode 字符,包括补充字符。例如,Character.isLetter(0x2F81A) 返回 true,因为代码点值表示一个字母(CJK 表意文字)。

像您在示例中所做的那样,将 char 强制转换为 int 是可行的。


2
http://java.sun.com/developer/technicalArticles/Intl/Supplementary/ 讨论了Java中代码点背后的设计决策。 - Gili
char转换为int完全是不必要的。 - David Conrad

11

Character类包含许多有用的方法来处理Unicode码点。需要注意的是,像Character.toChars(int)这样返回字符数组的方法。如果您的代码点位于补充范围内,则该数组的长度将为两个字符。

如何比较这些值取决于您是否想支持完整的Unicode值范围。以下示例代码可用于遍历字符串的代码点,测试是否存在补充字符 MATHEMATICAL_FRAKTUR_CAPITAL_G(𝔊 - U+1D50A)的匹配项:

public final class CodePointIterator {

  private final String sequence;
  private int index = 0;

  public CodePointIterator(String sequence) {
    this.sequence = sequence;
  }

  public boolean hasNext() {
    return index < sequence.length();
  }

  public int next() {
    int codePoint = sequence.codePointAt(index);
    index += Character.charCount(codePoint);
    return codePoint;
  }

  public static void main(String[] args) {
    String sample = "A" + "\uD835\uDD0A" + "B" + "C";
    int match = 0x1D50A;
    CodePointIterator pointIterator = new CodePointIterator(sample);
    while (pointIterator.hasNext()) {
      System.out.println(match == pointIterator.next());
    }
  }
}

从Java 8开始,可以使用CharSequence.codePoints()方法:

public static void main(String[] args) {
  String sample = "A" + "\uD835\uDD0A" + "B" + "C";
  int match = 0x1D50A;
  sample.codePoints()
        .forEach(cp -> System.out.println(cp == match));
}

我创建了一个表格,以便更好地处理需要处理的Unicode字符串长度和比较情况。


1
next() 的主体可以编写为 int codePoint = sequence.codePointAt(index); index += Character.charCount(codePoint); return codePoint;,这样可能会更易读且稍微更有效率一点。 - Steve Waring
要将字符连接到字符串中,可以使用StringBuffer.appendCodePoint(int codePoint)。 - IceArdor

3

如果一个字符可以用单个字符表示(16位,基本多语言平面),你只需要将字符转换为整数来获取码点(正如问题所建议的那样),因此无需使用特殊方法进行转换。

如果您要比较字符和码点,则不需要任何特殊情况。只需将字符直接与整数进行比较即可(正如问题所建议的那样)。如果整数代表基本多语言平面之外的码点,则结果将始终为false。


2

对于基本多语言平面中的字符,将char强制转换为int将获得代码点。这对应于可以编码为单个16位char值的所有Unicode值。超出此平面的值(其代码点超过0xffff)无法表示为单个字符。这可能是为什么没有Character.toCodePoint(char value)的原因。


1

Java使用16位(UTF-16)模型处理字符,因此任何代码点> 0xFFFF的字符都将使用两个代理字符作为一对16位字符存储在字符串中,以表示平面和平面内的字符。

如果您想根据完整的Unicode标准正确处理字符和字符串,则需要考虑这一点来处理字符串。

XML非常关注此问题;访问Xerces中的XMLChar类(随Java版本5.0及更高版本一起提供)可用于与字符相关的代码。

看看Saxon XSLT/XQuery处理器也是有益的,因为作为一个行为良好的XML应用程序,它必须考虑Java如何在字符串中存储代码点。XQuery 1.0和XPath 2.0具有codepoints-to-stringstring-to-codepoints函数;获取Saxon的副本并尝试使用它们来了解它们的工作方式可能是有益的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接