【问题标题】:How do I get a unicode character from an id in a variable?如何从变量中的 id 获取 unicode 字符?
【发布时间】:2021-07-29 19:30:44
【问题描述】:

我正在尝试生成一个包含每个 Unicode 变量的文件。我已经能够将 unicode 提高到 U+FFFF,但是我需要将它提高到 U+231F4。我已经尝试搜索答案,但是当符号 id 位于变量中而不是仅键入时,它们都不起作用。

现在,我有这个:

for (int i = 0; i < 143860; i++) {
            System.out.println((char)i);
        }

它不是上升到 U+231F4,而是上升到 U+FFFF,并在打印到的文档中循环。如何让它转到更高的 Unicode ID?

【问题讨论】:

    标签: java unicode


    【解决方案1】:

    [ OP 说“我需要把它升级到 U+231F4”,我回答了这个问题。但他们的意思是他们想要打印 Unicode 定义的 143,859 个代码点。看另一个答案。我现在不能删除这个 它已被接受。 ]

    Java 字符串不是由 Unicode 代码点构成,而是由 UTF-16 代码单元构成。对于 U+FFFF 以上的 Unicode 代码点,您需要使用代理对。例如,

         U+0   ⇒   0x0000            ⎫
         U+1   ⇒   0x0001            ⎪
               ⋮                      ⎬ Character in the BMP result
      U+D7FE   ⇒   0xD7FE            ⎪   in a single UTF-16 code unit.
      U+D7FF   ⇒   0xD7FF            ⎭
    
      U+D800   ⇒   ------            ⎫
      U+D801   ⇒   ------            ⎪
               ⋮                      ⎬ Can't be encoded using UTF-16.
      U+DFFE   ⇒   ------            ⎪   Illegal for interchange for this reason.
      U+DFFF   ⇒   ------            ⎭
    
      U+E000   ⇒   0xE000            ⎫
      U+E001   ⇒   0xE001            ⎪
               ⋮                      ⎬ Character in the BMP result
      U+FFFE   ⇒   0xFFFE            ⎪   in a single UTF-16 code unit.
      U+FFFF   ⇒   0xFFFF            ⎭
    
     U+10000   ⇒   0xD800, 0xDC00    ⎫
     U+10001   ⇒   0xD800, 0xDC01    ⎪
               ⋮                      ⎬ Those outside result in two.
     U+231F2   ⇒   0xD84C, 0xDDF2    ⎪
     U+231F3   ⇒   0xD84C, 0xDDF3    ⎭
    
     U+231F4   ⇒   0xD84C, 0xDDF4    ⎫
     U+231F5   ⇒   0xD84C, 0xDDF5    ⎪
               ⋮                      ⎬ We don't care about these.
    U+10FFFE   ⇒   0xDBFF, 0xDFFE    ⎪
    U+10FFFF   ⇒   0xDBFF, 0xDFFF    ⎭
    

    有关代理对的详细信息,您可以查阅UTF-16 的维基百科页面。

    解决方案 1:printf %c

    这些细节无关紧要,因为我们可以使用 printf %c 将 Unicode 代码点编码为 UTF-16 代码单元。 (感谢@VGR。)

    for (int cp=0; cp<0x231F4; ++cp) {
       if (cp < 0xD800 || cp >= 0xE000) {
          System.out.printf("%c%n", cp);
       }
    }
    

    优化:

    for (int cp=0; cp<0xD800; ++cp) {
       System.out.println((char)cp);
    }
    
    for (int cp=0xE000; cp<0x10000; ++cp) {
       System.out.println((char)cp);
    }
    
    for (int cp=0x10000; cp<0x231F4; ++cp) {
       System.out.printf("%c%n", cp);
    }
    

    解决方案 2:Character.toChars

    或者,我们可以使用Character.toChars(codePoint) 生成包含Unicode 代码点的UTF-16 代码单元的char[]

    for (int cp=0; cp<0x231F4; ++cp) {
       if (cp < 0xD800 || cp >= 0xE000) {
          System.out.println(Character.toChars(cp));
       }
    }
    

    优化:

    for (int cp=0; cp<0xD800; ++cp) {
       System.out.println((char)cp);
    }
    
    for (int cp=0xE000; cp<0x10000; ++cp) {
       System.out.println((char)cp);
    }
    
    for (int cp=0x10000; cp<0x231F4; ++cp) {
       System.out.println(Character.toChars(cp));
    }
    

    我相信上面仍然会创建很多数组。自己实现转换可以避免这种情况,因此应该更快。

    // Up to but excluding U+231F4 ⇒ 0xD84C, 0xDDF4
    
    for (int cp=0; cp<0xD800; ++cp) {
       System.out.println((char)cp);
    }
    
    for (int cp=0xE000; cp<0x10000; ++cp) {
       System.out.println((char)cp);
    }
    
    char pair[2];
    for (int hisurro=0xD800; hisurro<0xD84C; ++hisurro) 
       pair[0] = (char)hisurro;
       for (int losurro=0xDC00; losurro<0xE000; ++losurro) 
          pair[1] = (char)losurro;
          System.out.println(pair);
       }
    }
    
    pair[0] = 0xD84C;
    for (int losurro=0xDC00; losurro<0xDDF4; ++losurro) 
       pair[1] = (char)losurro;
       System.out.println(pair);
    }
    

    请注意,结果不会在您的终端中完全可读。输出包括不可打印字符(例如控制字符)、标记(与其他字符组合)、未分配的代码点、私人使用的代码点等。

    【讨论】:

    • 另一种选择:for (int i = 0; i &lt; 143860; i++) System.out.printf("%c%n", i);
    • @VGR,谢谢,我对 Java 的了解有限。添加到答案。
    • 不要与过时的 char 类型和代理对争吵,只需使用 Unicode code point 整数。 Java 完全支持代码点,但在笨拙和老化的 API 中并不明显。请参阅my Answer 中的代码。
    • @BasilBourque 呃,你看我的回答了吗?我发布的两个解决方案都使用代码点而不是字符。仅仅这些解决方案的优化版本使用字符,因为使用代码点不会提供任何优化。
    【解决方案2】:

    tl;博士

    这里是单线。 (为了好玩,不是我推荐的。)

    IntStream
            .rangeClosed( 0 , Character.MAX_CODE_POINT )
            .filter(
                    codePoint ->
                            !
                                    List
                                            .of( Character.CONTROL , Character.FORMAT , Character.SURROGATE , Character.PRIVATE_USE , Character.UNASSIGNED )
                                            .contains( ( byte ) Character.getType( codePoint ) )
            )
            .forEach(
               codePoint -> System.out.println( codePoint + " code point is named: " + Character.getName( codePoint ) + " = " + Character.toString( codePoint ) )
            )
    ;
    

    运行时。

    32 code point is named: SPACE =  
    33 code point is named: EXCLAMATION MARK = !
    34 code point is named: QUOTATION MARK = "
    …
    917997 code point is named: VARIATION SELECTOR-254 = ?
    917998 code point is named: VARIATION SELECTOR-255 = ?
    917999 code point is named: VARIATION SELECTOR-256 = ?
    

    避免char

    Java 中的 char 类型已过时。该数据类型甚至无法表示 Unicode 中定义并受 Java 支持的一半字符。

    Java 现在提供对代码点的全面支持。不幸的是,这种支持并不明显,多年后已在 CharacterStringStringBuilder 类中使用老化的 API。你必须回顾过去涉及char的过时方法。

    使用代码点

    养成使用 Unicode code point 整数的习惯,不使用任何 char。代码点是分配给 Unicode 定义的 143,859 个字符中的每一个字符的数字。

    这些代码点编号的分配范围为 0 到 10FFFF 十六进制,0 到 1,114,111 十进制。显然,这一百万范围内的大部分都是空的,要么当前未分配,要么留作私人使用的储备。

    你说:

    但是我需要把它升级到 U+231F4。

    不,您需要转到 U+10FFFF(十进制 1,114,111)。

    顺便说一句,Unicode 一直在增长。因此,不要执着于诸如 143,860 之类的字符数。我们永远不会有太多的表情符号!还有一些严肃的角色还在添加中。

    所以你的循环:

    for (int i = 0; i < 143_860; i++) {  // NO! Wrong limit. 
    

    ... 需要将其限制从 143_860 更改为 1_114_111 十进制(10FFFF 十六进制)。

    for (int i = 0; i < 1_114_111; i++) { // YES! Correct limit.
    

    或者,对于这个限制,使用常量Character.MAX_CODE_POINT

    for (int i = 0; i < Character.MAX_CODE_POINT; i++) {  // Use named constant rather than "magic" mystery number.
    

    还有一件事……MAX_CODE_POINT 包含,所以我们应该测试“小于或等于”而不是“小于”。将&lt; 更改为&lt;=

    for (int i = 0; i <= Character.MAX_CODE_POINT; i++) {  // Use named constant rather than "magic" mystery number.
    

    Character 类可以告诉我们是否为code point is valid。从 0 到最大 1,114,111 的所有代码点都是有效数字。负数和超过最大值的数字无效。

    同一个类可以告诉我们一个代码点代表什么类型的字符。 Unicode 标准定义了 30 种,正式称为“通用类别”。这些类别在Character 类中定义为命名常量,不幸的是与其他不同的常量混合在一起。

    我们想跳过其中的某些类别,特别是五个:

    • Character.CONTROL
    • Character.FORMAT
    • Character.SURROGATE
    • Character.PRIVATE_USE
    • Character.UNASSIGNED

    要确定代码点的类别,请调用Character.getType。不幸的是,该方法仅返回 int 而不是专用的枚举对象。

    如上所示,Character 类为通用类别定义了各种byte 常量,但无法将getType 返回的数字轻松转换为类别名称。请参阅相关问题How to get the category name of the character type in Java?There is a feature-request 在书上,但尚未实施。所以我们必须自己动手。

    这里我们使用一个名为unicodeGeneralCategoryCodesToAvoidByte 对象列表,其中一个元素对应五个感兴趣的常量。

    要从代码点 int 数字移动到实际字符,请调用 Character.toString( codePoint ) 以生成包含单个字符的 String

    要获取 Unicode 标准定义的字符的正式名称,请调用 Character.getName( codePoint )

    List < Byte > unicodeGeneralCategoryCodesToAvoid = List.of( Character.CONTROL , Character.FORMAT , Character.SURROGATE , Character.PRIVATE_USE , Character.UNASSIGNED );
    for ( int codePoint = 0 ; codePoint <= Character.MAX_CODE_POINT ; codePoint++ ) {
        if ( Character.isValidCodePoint( codePoint ) )    // If code point is valid.
        {
            if ( unicodeGeneralCategoryCodesToAvoid.contains( ( byte ) Character.getType( codePoint ) ) ) // If control character or if unassigned code point, skip it.
            {
                // No code needed. Skip over this code point as it does not represent a printable character.
            } else {
                System.out.println( codePoint + " code point is named: " + Character.getName( codePoint ) + " = " + Character.toString( codePoint ) );
            }
        } else {
            System.out.println( "ERROR - Invalid code point number: " + codePoint );
        }
    }
    

    运行时。

    INFO - Demo starting. 
    32 code point is named: SPACE =  
    33 code point is named: EXCLAMATION MARK = !
    34 code point is named: QUOTATION MARK = "
    35 code point is named: NUMBER SIGN = #
    36 code point is named: DOLLAR SIGN = $
    37 code point is named: PERCENT SIGN = %
    …
    123 code point is named: LEFT CURLY BRACKET = {
    124 code point is named: VERTICAL LINE = |
    125 code point is named: RIGHT CURLY BRACKET = }
    126 code point is named: TILDE = ~
    160 code point is named: NO-BREAK SPACE =  
    161 code point is named: INVERTED EXCLAMATION MARK = ¡
    162 code point is named: CENT SIGN = ¢
    …
    917997 code point is named: VARIATION SELECTOR-254 = ?
    917998 code point is named: VARIATION SELECTOR-255 = ?
    917999 code point is named: VARIATION SELECTOR-256 = ?
    INFO - Demo ending. 
    

    有关我处理 Unicode 通用类别的更多代码,请参阅问题my AnswerHow to get the category name of the character type in Java?

    【讨论】:

    • @ikegami OP 可能打算使用代码点。但鉴于143860 的限制,他们显然将分配的代码点的count 与可能的代码点的范围 (1,114,111) 混为一谈。
    • 因为它没有按照 OP 的要求做。但它确实符合 OP 的意思。我之前提到过。无论如何,移除 cmets 以避免混淆。不久前还给了+1
    猜你喜欢
    • 1970-01-01
    • 2018-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-22
    • 1970-01-01
    • 2014-09-16
    • 1970-01-01
    相关资源
    最近更新 更多