彻底弄懂Java里的Unicode、UTF8、UTF16

1. 概述

Java在内存中默认使用Unicode存储字符(Java 8使用Unicode 6.2.0),

Unicode编码范围是0x00000 ~ 0x10FFFF, 其中0x0000 ~ 0xFFFF称为基础多语言编码,0x10000 ~ 0x10FFFF称为扩展多语言编码,总共能表示111万(1114112)字符,0x10FFFF转为二进制,最多21位。

Unicode里的一个字符,对应一个编码,这个编码我们成为代码点(CodePoint)。

UTF-8和UTF-16只是对Unicode的不同组织方式,Unicode的一个代码点,可以组织成UTF-8里多个字节,或组织成UTF-16里的多个字符(char)。

Java里的char就是对UTF-16的一种表现方式,Java内部的字符都是使用UTF-16展示的,对于一个特殊字符(如’𠀸’),无法用一个char表示,这个时候在Java内部只会显示为"\uD840\uDC58"。

2. 编码方式UTF-8

UTF-8的目的是为了日常存储字符串的时候减少字节数的占用,代码点(0~127)直接的字符用一个字节就能表示。 四个字节就能完整的表示21位的Unicode。

字节数格式
10x0xxxxxxx
20x110xxxxx 0x10xxxxxx
30x1110xxxx 0x10xxxxxx 0x10xxxxxx
40x11110xxx 0x10xxxxxx 0x10xxxxxx 0x10xxxxxx
50x111110xx 0x10xxxxxx 0x10xxxxxx 0x10xxxxxx 0x10xxxxxx

比如"汉"这个字,对应的Unicode的CodePoint的是27721,转成二进制为:0b0110110001001001,从右往左按6位截取,分别是:

0b10_001001
0b10_110001
0b1110_0110

组成数组,打印UTF-8编码对应的汉字:

System.out.println("ShowText:" + new String(new byte[]{(byte) 0b11100110, (byte) 0b10110001, (byte) 0b10001001}, "utf-8"));
# 输出
ShowText:汉
3. 编码方式UTF-16

UTF-16将0xD800 ~ 0xDFFF保留做4字节的UTF-16使用, 0x0000 ~ 0xD7FF和0xE000 ~ 0xFFFF的UTF-16可以用两个字节表示。
对于0x10000 ~ 0x10FFFF之间的Unicode用4个字节表示,现用Unicode减去0x10000,最大值0x10FFFF - 0x10000,结果为0xFFFFF,需要20位表示。

0xD800~0xDFFF用于指示当前代码点需要4个字节表示, 又被拆成了两部分, 0xD8~0xDB用于表示第一个UTF-16,前6位二进制(0b110_110) , 0xDC~0xDF表示第二个UTF-16,前6位二进制位(0b110_111)。

还是以一个例子来说,'𠀐’对应unicode:0x20010,大于0xFFFF,所以需要两个UTF-16表示,

首先Unicode减去0x10000,为

0x20010 - 0x10000 = 0x10010 

转为二进制

0x0001 0000 0000 0001 0000

拆分为两个10位的二进制

0b0001 0000 00
0b00 0001 0000

拼接前缀后:

0x110110_0001 0000 00, 0x110111_00 0001 0000

格式化后:

0x1101 1000 0100 0000, 0x1101 1100 0001 0000

对应十六进制

0xD840 0xDC10

通过如下方式验证

String text = "\uD840\uDC10"
System.out.println(text);
"\uD840\uDC10".codePointAt(0) == 0x20010
4. Tomcat对Path和QueryString、RequestBody的编码处理

Path和QueryString、RequestBody采用了不同的编码,如IE下: PathInfo 是 UTF-8 编码而 QueryString 是经过 GBK 编码

Tomcat对URI的Path默认是使用UTF-8解码的,而参数默认使用iso-8859-1解码,常常到站获取参数了的中文乱码,代码逻辑见org.apache.catalina.connector.CoyoteAdapter的convertURI方法。

protected void convertURI(MessageBytes uri, Request request) throws IOException {

    ByteChunk bc = uri.getByteChunk();
    int length = bc.getLength();
    CharChunk cc = uri.getCharChunk();
    cc.allocate(length, -1);

    Charset charset = connector.getURICharset(); // 这里的getURICharset()默认返回的是UTF-8

    B2CConverter conv = request.getURIConverter();
    if (conv == null) {
        conv = new B2CConverter(charset, true);
        request.setURIConverter(conv);
    } else {
        conv.recycle();
    }
}
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值