【问题标题】:Comparing a char to a code-point?将字符与代码点进行比较?
【发布时间】:2010-11-05 00:28:03
【问题描述】:

将代码点与 Java 字符进行比较的“正确”方式是什么?例如:

int codepoint = String.codePointAt(0);
char token = '\n';

我知道我可能可以做到:

if (codepoint==(int) token)
{ ... }

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

【问题讨论】:

    标签: java unicode


    【解决方案1】:

    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));
    }
    

    我创建了一个table 来帮助处理有时需要处理的 Unicode 字符串长度和比较情况。

    【讨论】:

    • next() 的主体可以写成int codePoint = sequence.codePointAt(index); index += Character.charCount(codePoint); return codePoint; ,这样读起来会更好,效率也更高。
    • 要将字符连接成一个字符串,StringBuffer.appendCodePoint(int codePoint)。
    【解决方案2】:

    一点背景知识:Java 在 1995 年出现时,char 类型是基于最初的“Unicode 88”规范,限制为 16 位。一年后,当 Unicode 2.0 实施时,引入了代理字符的概念,超越了 16 位的限制。

    Java 在内部以 UTF-16 格式表示所有 Strings。对于超过 U+FFFF 的代码点,代码点由代理对表示,即两个 chars,第一个是高代理代码单元(在 \uD800-\uDBFF 范围内),第二个是低代理代码单元(在 \uDC00-\uDFFF 范围内)。

    从早期开始,所有基本的Character 方法都基于一个代码点可以用一个char 表示的假设,所以这就是方法签名的样子。我猜想是为了保留在 Unicode 2.0 出现时没有改变的向后兼容性,并且在处理它们时需要小心。引用Java documentation

    • 仅接受 char 值的方法不支持补充字符。它们将代理范围中的 char 值视为未定义字符。例如,Character.isLetter('\uD840') 返回 false,即使此特定值后跟字符串中的任何低代理值将表示一个字母。
    • 接受 int 值的方法支持所有 Unicode 字符,包括补充字符。例如,Character.isLetter(0x2F81A) 返回 true,因为代码点值表示一个字母(CJK 表意文字)。

    char 转换为int,就像您在示例中所做的那样,但效果很好。

    【讨论】:

    【解决方案3】:

    对于可以由单个 char(16 位,基本多语言平面)表示的字符,您只需将 char 转换为整数即可获得代码点(如问题所示),因此无需特殊方法来执行转换。

    如果您将字符与代码点进行比较,则不需要任何特殊的大小写。只需将 char 直接与 int 进行比较(如问题所示)。如果 int 表示基本多语言平面之外的代码点,则结果将始终为 false。

    【讨论】:

      【解决方案4】:

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

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

      XML 非常关心这一点;访问 Xerces(Java 5.0 及更高版本附带)中的 XMLChar 类以获取与字符相关的代码很有用。

      看看Saxon XSLT/XQuery 处理器也很有启发意义,因为它是一个表现良好的 XML 应用程序,它必须考虑 Java 如何将代码点存储在字符串中。 XQuery 1.0 和 XPath 2.0 具有 codepoints-to-stringstring-to-codepoints 的函数;获取 Saxon 的副本并与他们一起玩,看看他们是如何工作的,这可能是有益的。

      【讨论】:

        【解决方案5】:

        对于基本多语言平面中的字符,将 char 转换为 int 可以获得代码点。这对应于可以在单个 16 位 char 值中编码的所有 unicode 值。此平面之外的值(代码点超过 0xffff)不能表示为单个字符。这可能就是没有 Character.toCodePoint(char value) 的原因。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-05-21
          • 2012-10-06
          • 2019-01-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多