V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zhouyin
V2EX  ›  Java

Java 为什么能给 char 类型赋值中文字符

  •  
  •   zhouyin · 4 天前 · 4236 次点击
    java 文档里有写 一个 char 由两个自己组成 但一个 utf8 汉字由三个字节组成
    为什么这样赋值没事?

    char a = '我';
    65 条回复    2025-02-11 09:51:02 +08:00
    cmdOptionKana
        1
    cmdOptionKana  
       4 天前
    问了一下 deepseek ,它说:UTF-8 编码的汉字在 Java 中会自动转换为 UTF-16 编码,因此赋值时不会出现问题。
    theoriz
        2
    theoriz  
       4 天前
    @Livid #1 AI
    soulflysimple123
        3
    soulflysimple123  
       4 天前
    char 是 utf-16 编码
    tbc3211
        4
    tbc3211  
       4 天前
    魔怔
    zhouyin
        5
    zhouyin  
    OP
       4 天前 via Android
    @soulflysimple123
    输入法输入的汉字是三个字节 编译器自动把汉字转成 utf16 进行存储了
    maocat
        6
    maocat  
       4 天前 via Android   ❤️ 52
    @theoriz 哥们你魔怔了吧,人家是把 ai 的提炼了一遍,一眼就能看清说啥,而不是 ai 的长篇大论,你这也举报?
    sagaxu
        7
    sagaxu  
       4 天前
    一个 char 不够那就两个啊,code point 是 int 类型,超出 BMP 的字符不能用单个 char 表示,要用两个 char 组合
    Fca
        8
    Fca  
       4 天前   ❤️ 13
    @theoriz 有文革那味了 人人自危
    zhouyin
        9
    zhouyin  
    OP
       4 天前
    神奇的是 把变量用 FileWriter.write 方法 写入到文件 还是 3 个字节
    zhouyin
        10
    zhouyin  
    OP
       4 天前
    @zhouyin
    可能因为默认编码 utf-8
    zhouyin
        11
    zhouyin  
    OP
       4 天前
    @zhouyin
    FileWriter 默认 iso-8859-1 编码 单字节
    zhouyin
        12
    zhouyin  
    OP
       4 天前
    @zhouyin
    java17 filewriter 默认 encoding utf8
    dandycheung
        13
    dandycheung  
       4 天前 via Android   ❤️ 1
    你把语言里类型的字长跟存储时编码的存储方案搞混了。在 Java 语言里,你把一个中文的字符串取一下长度看是不是 1 ,跟 char 能不能对上,结论就出来了。
    zhouyin
        14
    zhouyin  
    OP
       4 天前
    @dandycheung

    没搞混哦 你的回答比较低级

    我如果这都不知道 就不会问底层的字节了

    这帖的精华是 编译器把输入的 utf8 汉字的三个字节 主动隐式地换成了 2 个字节 utf16 了
    dandycheung
        15
    dandycheung  
       4 天前 via Android   ❤️ 2
    总之就是,Java 语言用两个字节存储一个 char ,而一个汉字在 UTF-8 编码后有三个字节长,但是它仍然是一个 char ,在 Java 语言中占用两个字节;一个英文字母在 UTF-8 编码后是一个字节长,但它也是一个 char ,在 Java 语言中占用两个字节。

    有没搞混无所谓了,你自己判断就好。
    wuyiccc
        16
    wuyiccc  
       4 天前   ❤️ 3
    那你看看这个中文汉字 '𠮷' 还能赋值么
    sagaxu
        17
    sagaxu  
       4 天前
    @zhouyin
    @dandycheung

    2 字节上限只有 65536 个字符,但 unicode 已经超过 10 万个字符了。
    wind1986
        18
    wind1986  
       4 天前   ❤️ 6
    @theoriz 你怕是有什么大病吧?
    xuld
        19
    xuld  
       4 天前
    “一个 char 由两个字节组成 但一个 utf8 汉字由三个字节组成”,这句话本身没有问题,但代码里的 char 变量和这句话里的 char 不是一回事.。

    字符的本质就是一个整数,比如“我”的编码是 25105 ,几乎所有程序在运行时都会直接存储 25105 。

    编译器同理,无论源码里面是什么,用什么编码,最后都会统一解析出 25105 这个数值。

    java 的 char 类型本质是一个 16 位整数。char a = '我'; 本质等价于 short a = 25105 。显然没有问题。

    所以这个问题其实和“Unicode”、“UTF-8”没有任何关系、更不要去扯什么文件编码,那只会越扯越糊涂。
    wuyiccc
        20
    wuyiccc  
       4 天前
    根据 java 核心技术卷说的,char 类型采用 utf16 编码规则,char 描述 utf16 编码规则中的一个代码单元,一些中文用 utf16 编码规则的时候一部分是占用 2 字节-一个代码单元,一部分是 4 字节,2 个代码单元
    moposx
        21
    moposx  
       4 天前
    因为 char 是 16 位无符号整数,用来表示 UTF-16 码位。而 UTF-16 本身是 2 字节或者 4 字节的变长编码,“我”是在 BMP 里的,所以只需要 2 字节即可表示。如果你从扩展 B 区找一个汉字,就会发现它是不能被赋值给 char 的。
    wuyiccc
        22
    wuyiccc  
       4 天前   ❤️ 1
    补充: char 类型不是采用 utf16 编码规则,而是描述了 UTF-16 编码中的一个代码单元
    zhouyin
        23
    zhouyin  
    OP
       4 天前 via Android
    @xuld
    你才是菜鸟 不知道编辑器当前 utf8 编码下 输入一个汉字会插入三个字节 在源代码保存的就是三个字节 只是编译器转成了 utf16 两个字节

    你其实没有理解精髓
    zhouyin
        24
    zhouyin  
    OP
       4 天前 via Android
    @wuyiccc
    对 一般 utf16 是 4 个字节 我还在奇怪 为什么 java unicode 两个字节
    zhouyin
        25
    zhouyin  
    OP
       4 天前
    @wuyiccc

    该字能在 java17 赋值给 char 但只能通过位移得到 2 个有用字节 如果 String.valueOf(a).getBytes("UTF-16") 则得不到有用东西

    必须赋值给 String 才能处理

    这个字的四个字节在此码表网站显示不出来 https://www.toolhelper.cn/Encoding/UTF16
    D842 DFB7
    zhouyin
        26
    zhouyin  
    OP
       4 天前
    @wuyiccc
    在 java16 及以上 可以把这种超出两字节的汉字 赋值给 char 但得不到正确 bytes
    zhouyin
        27
    zhouyin  
    OP
       4 天前
    @wuyiccc
    该字通过 string.getBytes("UTF-8") 得到 4 个字节

    其实它在 utf8 下

    http://www.mytju.com/classCode/tools/encode_utf8.asp

    是 6 个字节
    zhouyin
        28
    zhouyin  
    OP
       4 天前
    @wuyiccc
    对应 utf8 编码 fa a0 ae b7 能在编辑器中正常显示
    可能网站 mytju 给出的 utf8 不准确
    w568w
        29
    w568w  
       4 天前   ❤️ 1
    这种涉及具体设计的东西,为什么不直接看文档呢: https://docs.oracle.com/en/java/javase/22/docs/api/java.base/java/lang/Character.html#unicode

    太长不看:

    char 数据类型基于 Unicode 规范,该规范将字符( characters )定义为固定宽度的 16 位实体。从 U+0000 到 U+FFFF 的字符集有时被称为基本多语言平面 (Basic Multilingual Plane ,BMP)。码位大于 U+FFFF 的字符称为补充字符( supplementary characters )。UTF-16 编码这些补充字符的方式是,利用一对 16 位整数(称为「代用码位」), 第一个来自高代用值范围(\uD800-\uDBFF ),第二个来自低代用值范围(\uDC00-\uDFFF )。

    因此,一个 char 值代表基本多语言平面中的一个码位,包括 UTF-16 编码使用的代用码位。为了表示那些在 UTF-16 中需要多码位编码的补充字符们(如部分汉字、符号等),将用 int 类型来代表一个完整 Unicode 码位。

    因此,那些接受 char 类型的字符串工具函数,将无法处理补充字符;而接受 int 类型的那些,就可以处理所有字符。
    codehz
        30
    codehz  
       4 天前 via Android
    稍微偏个题
    其实你 c 语言里也可以这么写,而且有实际用处(不过一般不是中文,而是四个英文字母组成的字面量,类似 enum State { stop = 'stop' }这样的用法,然后就可以在内存里见到这个字面量了,简易调试的时候很有用(不过有字节序的问题,所以现在也不常用)
    w568w
        31
    w568w  
       4 天前
    @w568w #29 手快发出去了。

    再太长不看:char 就是 16 位整数,所以有的字符你无法赋值给 char 。int 则用于代表任意一个 Unicode 字符。Java 在 char[] 和 String 中储存字符串的方式是 UTF-16 编码。
    cpstar
        32
    cpstar  
       4 天前
    你们不看字节码么?
    这句代码经过编译器之后,就变成了 sipush 20320 ,管你是“你”还是什么,一律按照数字处理的,同理还有 boolean 只有 0 和 1 ,进行比较的时候其实就是判断等于 0 与否。本质都是一个数字,甚至观察 String 的本质,也是一堆 char ,一堆数字。
    sagaxu
        33
    sagaxu  
       4 天前
    @w568w Java 9 之后 String 内部用 byte[],编码有 LATIN1 和 UTF-16 两种
    w568w
        34
    w568w  
       4 天前
    @sagaxu #33 这我倒没了解过,有来源吗?我的断言是上面文档里的描述:

    > The Java platform uses the UTF-16 representation in char arrays and in the String and StringBuffer classes.
    sagaxu
        35
    sagaxu  
       4 天前   ❤️ 1
    my3157
        36
    my3157  
       4 天前
    大多数语言里面, char 都代表的是 single unicode scalar value, 而 utf8 只是编码规则, 长度是 1-4 bytes(问题中的 '我' 就会编码成 3 个 bytes), 覆盖了 BMP(基本多文种平面), 基本上够 99.99% 的各类用途, 而且 uft8 是兼容 ascii 且大小端无关的, uft16 以以上要考虑 ascii 兼容和大小端的问题
    zhouyin
        37
    zhouyin  
    OP
       4 天前
    @cpstar
    这个帖子的初忠是 当前编辑器编码 utf8 输入汉字'你'时 输入了三个字节 E4BDA0
    java 编译器隐士地把 utf8 字符字面量转成 utf16 4F60 等于十进制 20320
    zhouyin
        38
    zhouyin  
    OP
       4 天前
    @codehz
    大佬 能不能发个具体能利用这样 enum 调试 c 的例子 不是 c 高手
    LanhuaMa
        39
    LanhuaMa  
       4 天前
    @theoriz #2 你有病,有病要去治,不治迟早会出事。
    cpstar
        40
    cpstar  
       3 天前   ❤️ 1
    OP 38# 编译器干的不就是这个,读取原始文件,然后进行语法识别和语义识别,判断到给本地变量 a 设置 char ,那就把等号后边的字符(以单引号包住的,前一步语法分析没有问题的)按照文件存储编码或者-encoding 选项进行识别,按数字处理,并根据不同的数字范围来使用不同的指令集,iconst_x bipush sipush ldc 等
    9LCRwvU14033RHJo
        41
    9LCRwvU14033RHJo  
       3 天前
    为什么很多人说 char 是 utf-16 编码呢? char 存的是 unicode 不是 utf-8 或者 utf-16 。它能存 65536 个基本多文种平面( BMP )的字符,如果超过这个范围(生僻字)就需要两个 char 才能存得下。

    char c = '\u0041';
    System.out.println(c); // 输出:我
    9LCRwvU14033RHJo
        42
    9LCRwvU14033RHJo  
       3 天前
    @user8341
    更正:
    char wo = '\u6211';
    System.out.println(wo); // 输出:我
    zhouyin
        43
    zhouyin  
    OP
       3 天前
    @user8341
    超过这个范围就必须要用 String
    没有两个 char 的表示法吧
    llej
        44
    llej  
       3 天前
    我超市了一下,在两个字节能够表示的是可以直接这样赋值的,但超出了就会报错。

    所以好像没啥问题,赋值中文确实可能出错,只是你的用例没到边界情况

    ```java
    class Main {
    public static void main(String[] args) {
    // 创建一个包含超出基本多文种平面( BMP )字符的字符串
    char str = '𠜎';

    System.out.println("字符串: " + str);
    }
    }

    ```
    llej
        45
    llej  
       3 天前
    @zhouyin 因为他本来就不是用的 utf8 ,java 用的就是 utf16 呀,这个和你代码文件的编码无关的,假设你使用 gbk2312 来保存你的代码,java 解析加载之后还是按他自己的规则走的。
    zhouyin
        46
    zhouyin  
    OP
       3 天前
    @llej
    你这样不行的 这种字符 赋值给 char 控制台输出乱码
    WorseIsBetter
        47
    WorseIsBetter  
       3 天前
    @codehz #30

    但这种写法按标准[^1]会得到一个「实现定义」的值。

    > The value of an integer character constant containing more than
    > one character (e.g., 'ab'), or containing a character or escape
    > sequence that does not map to a single-byte execution character,
    > is implementation-defined.

    考虑到可移植性,通常不建议使用。
    除非你写的代码只应用于特定实现,且该实现对此有明确定义。

    比如在 GCC[^2] 中:

    > The compiler evaluates a multi-character character constant
    > a character at a time, shifting the previous value left by the
    > number of bits per target character, and then or-ing in the
    > bit-pattern of the new character truncated to the width of a
    > target character.

    [^1]: ISO/IEC 9899:1999 §6.4.4.4/10
    [^2]: https://gcc.gnu.org/onlinedocs/cpp/Implementation-defined-behavior.html

    ---

    声明:本回答并非使用 LLM 生成。
    cpstar
        48
    cpstar  
       3 天前
    @llej 44#
    复制到 intelliJ 里直接就是两个\u ,在 uestudio 中可以显示“𠜎”,按照 UTF-8 保存,javac -encoding UTF8 编译报错。按照 UTF-16 保存,并且-encoding UTF16 ,同样。
    45#
    在编译阶段,可以指定代码来识别源代码文件格式。典型的问题就是 Windows 环境如果按照 utf-8 编辑文件,但是手工在 cmd 里编译的话,会按照 GBK 识别文件从而在中文字符上出问题。
    dandycheung
        49
    dandycheung  
       3 天前 via Android
    @sagaxu 你说的这是另外一件事。Windows 下,一个 Unicode 字符也是两个字节,你说是怎么做到的?当然是用更复杂的其它方法。
    zhouyin
        50
    zhouyin  
    OP
       3 天前
    @cpstar
    这种奇怪字符无法显示跟当前终端编码无关 gitbash 是 utf8 也无法显示 System.out.println("" + char )

    除非这个 char 是 65535 里面的 那种生僻字符就无法显示

    你如果能显示 是因为 jvm 实现由差别 因为 char 最多只能两个字节 那种生僻字符占 4 个字节
    sagaxu
        51
    sagaxu  
       3 天前
    @dandycheung 做不到的,10 万+字符携带的信息量,不可能编码进 2 字节中,Windows 一个 Unicode 字符也可能是 4 字节。UTF-16 对应的不是字符,可能是半个字符。
    dandycheung
        52
    dandycheung  
       3 天前 via Android
    @sagaxu 对,UTF-16 的方法叫“代理对”,surrogate pair ; Windows 的原生 Unicode 方案不是 UTF-16 ,而是叫 UCS2 ,虽然在大部分代码点上跟 UTF-16 重合,但并不完全一样。但是这些,都不影响楼主那个问题应该如何理解。
    iseki
        53
    iseki  
       3 天前 via Android   ❤️ 2
    Java 的 char 存储的是 UTF-16 的一个 code unit ,一个不在 BMP 的 code point 在 UTF-16 里是两个 code unit ,所以你没法把这部分字符塞进一个 char 。但是常用汉字都在这个范围。
    zhouyin
        54
    zhouyin  
    OP
       3 天前
    @sagaxu
    10 万个字符算啥

    utf-16 能用 4 个字节编码所有字符
    就是 4294967296 个字符 42 亿 9 千 4 百 9 十 6 万 7 千 2 百 9 十 6 个字符!
    moposx
        55
    moposx  
       3 天前
    @zhouyin 4 字节定长编码方案很早就有了。虽然简单,但由于空间占用和兼容性两方面的问题没能得到大规模应用。另外 Unicode 的编码空间范围是 0x0000 到 0x10FFFF ,远低于理论上限
    glcolof
        56
    glcolof  
       3 天前
    @zhouyin 这得看编辑器内部以什么字符编码工作的,比如在 Windows 平台,Windows 自带的文本输入框内部用的就是 ucs2/utf-16 ,所以输入法输入的也是 ucs2/utf-16 ,一个汉字两个字节。
    zhouyin
        57
    zhouyin  
    OP
       3 天前 via Android
    @glcolof
    还要看编辑器啊 输入法是跟编辑器编码对应的 不然为什么不同编码时输入的汉字 保存后字节不一样

    难道保存时临时转编码?
    glcolof
        58
    glcolof  
       3 天前
    @zhouyin 对,读取和保存时转编码。一般语言的运行时库会提供相应的功能,指定使用什么编码。
    seyoatda
        59
    seyoatda  
       3 天前
    @9LCRwvU14033RHJo #41 首先区分字符集和字符编码。Unicode 是字符集。要存到数据中是要指定编码的。准确的说:Java 中的 char 存放的是用 utf-16 编码的 Unicode 。
    Belmode
        60
    Belmode  
       3 天前
    我只能说,基础不牢,地动山摇......
    realJamespond
        61
    realJamespond  
       3 天前
    相当于 c++的 wchar ?
    Huelse
        62
    Huelse  
       3 天前
    https://docs.oracle.com/javase/8/docs/api/java/lang/Character.html

    char 是存储 unicode 字符而不是 utf-8 字符编码

    "which defined characters as fixed-width 16-bit entities"说明是 16 位,同时是 utf-16 编码

    大部分中文在 unicode 中都是 2 个字节,少数是 3 个字节,所以存在变长补位的说法
    sofm
        63
    sofm  
       3 天前
    java 中的 char ,能表示 unicode 中处于基本平面 BMP 的所有字符,从 0-65535 ,合计 65536 个字符,这 65536 个字符包括了 全世界范围内语言的 常用字符,自然也包括中文。 特殊生僻字 不在 char 范围内。

    char 的取值范围不能超过 65535 ,ide 会爆红提示错误。

    char 表示数字,中文在 unicoe 中也是一个数字编号。

    当想 print char 时,会将中文字符,转成 实际的 utf-8 的 3 个字节,如果 terminal 配置的时 utf-8 ,就会将这 3 个字节 整体显示为 一个 中文字符。
    lululau
        64
    lululau  
       2 天前
    哈哈,所以程序员也要多用用 Windows ,在 Windows 上摸爬滚打过的,自然对字符编码的问题了解得比较细致
    lff0305
        65
    lff0305  
       2 天前
    String 类的方法 codePointCount ,offsetByCodePoints 就是解决这个问题的( unicode 的 2/3/4 字节)
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5509 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 08:29 · PVG 16:29 · LAX 00:29 · JFK 03:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.