Java 为什么能给 char 类型赋值中文字符 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zhouyin
V2EX    Java

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

  •  
  •   zhouyin 244 天前 6254 次点击
    这是一个创建于 244 天前的主题,其中的信息可能已经有所发展或是发生改变。
    java 文档里有写 一个 char 由两个自己组成 但一个 utf8 汉字由三个字节组成
    为什么这样赋值没事?

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

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

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

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

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

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

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

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

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

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

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

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

    必须赋值给 String 才能处理

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

    其实它在 utf8 下

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

    是 6 个字节
    zhouyin
        28
    zhouyin  
    OP
       244 天前
    @wuyiccc
    对应 utf8 编码 fa a0 ae b7 能在编辑器中正常显示
    可能网站 mytju 给出的 utf8 不准确
    w568w
        29
    w568w  
       244 天前   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  
       244 天前 via Android
    稍微偏个题
    其实你 c 语言里也可以这么写,而且有实际用处(不过一般不是中文,而是四个英文字母组成的字面量,类似 enum State { stop = 'stop' }这样的用法,然后就可以在内存里见到这个字面量了,简易调试的时候很有用(不过有字节序的问题,所以现在也不常用)
    w568w
        31
    w568w  
       244 天前
    @w568w #29 手快发出去了。

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

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

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

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

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

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

    ```
    llej
        45
    llej  
       244 天前
    @zhouyin 因为他本来就不是用的 utf8 ,java 用的就是 utf16 呀,这个和你代码文件的编码无关的,假设你使用 gbk2312 来保存你的代码,java 解析加载之后还是按他自己的规则走的。
    zhouyin
        46
    zhouyin  
    OP
       244 天前
    @llej
    你这样不行的 这种字符 赋值给 char 控制台输出乱码
    WorseIsBetter
        47
    WorseIsBetter  
       244 天前
    @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  
       244 天前
    @llej 44#
    复制到 intelliJ 里直接就是两个\u ,在 uestudio 中可以显示“”,按照 UTF-8 保存,javac -encoding UTF8 编译报错。按照 UTF-16 保存,并且-encoding UTF16 ,同样。
    45#
    在编译阶段,可以指定代码来识别源代码文件格式。典型的问题就是 Windows 环境如果按照 utf-8 编辑文件,但是手工在 cmd 里编译的话,会按照 GBK 识别文件从而在中文字符上出问题。
    dandycheung
        49
    dandycheung  
       244 天前 via Android
    @sagaxu 你说的这是另外一件事。Windows 下,一个 Unicode 字符也是两个字节,你说是怎么做到的?当然是用更复杂的其它方法。
    zhouyin
        50
    zhouyin  
    OP
       244 天前
    @cpstar
    这种奇怪字符无法显示跟当前终端编码无关 gitbash 是 utf8 也无法显示 System.out.println("" + char )

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

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

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

    难道保存时临时转编码?
    glcolof
        58
    glcolof  
       243 天前
    @zhouyin 对,读取和保存时转编码。一般语言的运行时库会提供相应的功能,指定使用什么编码。
    seyoatda
        59
    seyoatda  
       243 天前
    @9LCRwvU14033RHJo #41 首先区分字符集和字符编码。Unicode 是字符集。要存到数据中是要指定编码的。准确的说:Java 中的 char 存放的是用 utf-16 编码的 Unicode 。
    Belmode
        60
    Belmode  
       243 天前
    我只能说,基础不牢,地动山摇......
    realJamespond
        61
    realJamespond  
       243 天前
    相当于 c++的 wchar ?
    Huelse
        62
    Huelse  
       243 天前
    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  
       243 天前
    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  
       243 天前
    哈哈,所以程序员也要多用用 Windows ,在 Windows 上摸爬滚打过的,自然对字符编码的问题了解得比较细致
    lff0305
        65
    lff0305  
       242 天前
    String 类的方法 codePointCount ,offsetByCodePoints 就是解决这个问题的( unicode 的 2/3/4 字节)
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     928 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 88ms UTC 19:12 PVG 03:12 LAX 12:12 JFK 15:12
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86