看王争专栏字符章节总结
目录
4,另外unicode的四字节编码表式,以及证明了char存的是utf-16字节编码
一,字符,字符集和字符编码
1)字符
字符可以理解为我们平时的符号,包括汉字,数字,字母,标点,图形符号,控制符号(换行)等
2)字符集
字符集是一组字符的集合,比较常用的有ASCII, GBK,DB2312,GB18030,Unicode,比如GB2312主要是汉字的字符集,里面包括了6000多常用汉字和一些符号等。
字符集不仅包含字符,还包含字符的编号,这个编号只是方便索引与字符编码不是一回事。个人理解字符的编号是以0123方式的编号,而字符编码则指的是二进制编码,像java的char存的是字符编码(UTF-16)。
3)字符编码
字符编码是指计算机存储字符编号的一种格式,一般一种字符集会对应一种字符编码,比如GBK,GBK2312,ASCII,但也有例外,比如Unicode有UTF-8,UTF-16,UTF-32。
二,常见字符集和字符编码
1)ASCII
全称叫美国信息交换标准代码,
是最早的一种字符集,只有128个字符,所以使ASCII的字符编码很简单,刚好用一个字节,使用一个字节中的低7位来存储值,最高位默认为0
0-31是不可显示字符
32-127可显示字符
可显示字符好表式,不可显示字符的表示一般使用\xxx,其中xxx用八进制表示,当然可显示字符也可以。
比如\012 是换行,ascii的10
a,可以表示为97,也可以表示为\141
也有部份可用转义字符来表示
\r,表示回车
\n换行
\t 表示tab
也可双使用ascii码或码值(a = 97)来直接表示字符
如char a = 97,与char a = 'a';是一样的效果
char类型的比较操作
2)GB*系列字符集和字符编码
ACSII只有128个字符,对于英文可能够用了,但是对于中文,日文等就不行了,因此计算机在传播中出现了不同的字符集以及字符编码
其中GBK2312是1980年发布的,收录了6000个常用汉字,符号等,以2字节存储,虽然99%都有了,但还是不全
GBK也是两个字节,在GB2312的基础上增加了两万多个汉字和符号,
GBK18030,兼容了GBK和GB2312,收录的汉字也更多,达到7万多,但是占用字节是变动的,有些占1字节,占2字节,有些占4字节
3)Unicode字符集和UTF*系列字符编码
各语言都有了各自的字符集与字符编码,同一串二进制在不同的字符集和字符编码中有不同的意思,这使得我们没法在同一个文档中使用两种及以上的语言。
这时大一统的Unicode出现了,他收集了100多万个字符,每个字符有他自己的编号。
对于Unicode习惯使用前缀 U+ 与16进制对编号来进行表式,范围为U+ 0 ~~ U+10FFFF。
unicode虽然收集的字符很多,但是常用的终归是少数,所以把常用的放在前面,同时也可以使占用的空间更少。unicode分部如下:
U+0000-- U+FFFF(FFFF刚好2字节) 存储常用的字符,其中U+D800 -- U+DFFF 不使用(即这些编号没有unicode字符),
U+ 10000 -- U + 10FFFF,存储其他的字符
其中U+0000 -- U+ 007F 与ASCII的编号一样。
Unicode有三种字符编码,分别为UTF-32,UTF-16,UTF-8
此处注意Unicode的字符编号使用U+方式表示
而字符编码是\uXXXX这样的方式,都是16进制
3-1)UTF-32
utf-32使用4节字定长存储,直接将unicode的编号存入即可,好处是编码简单,读取解码时也简单,坏处就是占用空间较多。
3-2)UTF-16
U+的表示方式是,unicode的字符的编号。
untf-16使用2,4两种字节不定长存储,其中 U+0 ~ U+FFFF(不包括U+D800 -- U+DFFF),直接以两字节存储
对于U+10000及以上的编号如下:
1,将U+10000~U+10FFFF 减去10000,得到U+0000 ~ U+FFFFF的新的范围,同时得到每个字符对应的新的编号,总共20位
2,将20位的高10位取出,放入一个新的二进制的后十位,前面缺失的用1101 10补上,这样得到一个新的二进制位,值的范围为D800 -- DBFF ;这就是四字节的前2字节。
3,将20位的后10位取出,放入一个新的二进制的后十倍,前面缺失的用1101 11补上
这样得到一个新的二进制位,值的范围为DC00 -- DFFF;这就是四字节的后2字节。
上面高二位与低二位的值的范围合一起刚好是D800 -- DFFF,也就是unicode没有字符的编号,同时也是为什么这些编号没有字符(用区来分二进制编码,以有四进制编码的高16位与低16位)。
通过上面步骤得到了一个四字节的数据,因为char是2进制的,如何判断是二字节编码还是四字编码呢:
1,如果值的范围是U+0~ U+FFFF,同时不属于D800 -- DFFF,那么读取的这两个字节就是二字节编码
2,如果值的范围是U+ D800 - U+DBFF(前缀1101 10 允许的值范围),那么读出来的就是四字节编码的高16位
3,如果值的范围是U+DCFF -- U+DFFF(前缀1101 11 允许的值范围),代表的就四字节编码的低16位,其实读出高16位,正常编码后面肯定就是低16位
3-3)UTF-8
utf-8存储也是不定长的,有1,2,3,4四种字节编码,不同范围内的编号使用不同的编码:
U0000 -- U007F 使用1字节存储,与ASCII对应
U0080 -- U07FF 使用2字节存储
U0800 -- UFFFF 使用3字节存储,包括中文,日文等
U10000 -- U10FFFF 使用4字节存储
使用类似于utf-16的前缀补位法,如下:
注意2字节编码第一个为110,3字节编码为1110,四字节编码11110,后面的非第一个字节全为10.
根据这个补位规则可以反推数值范围,如2进制位存储的范围:
110+10占去5位,剩下11位,最大值U07FF,开始位为U0080
三字节同上,余16位,得到最大值为UFFFF。
四字节特殊,收录的不够上限
三,Java的字符编码
1)默认问题
c\c++时ASCii是主流所以是1字节
而当java发起时,unicode已经流行,因此设计了两个字节的char来存储unicode的常用字符,U0000 -- UFFFF。unicode字符会通过utf-16编码后存在于char变量中(UTF-32是四字节,UTF-8是三字节,UTF16是2字节)。
四字节的以两个char存储。
2)java汉字更编码占多少字节
gbk:2字节
gb2312:2字节
gb18030:1,2,4字节
utf-8:3字节
utf-16:2字节
utf-32:4字节
3)unicode与ASCII的表示差异
1,ASCII
ASCII和字符编码也叫ASCII字符编码,且字符的编码与字符编码是一样的都是0-127
直接使用编号数字可以,char a = 97;
直接使用字符也可以 char a = 'a';
使用\xxx格式,xxx是8进制表式,如char a = '\141'(注意单引号)
以上表式的都是 'a'
对于0-31的无显示符号,\xxx方式,有些特殊的有转换符如: \r, \n等
2,Unicode对应的字符编码
Unicode编号为U+ 如U+D800 -- U+DFFF是不用的
而字符编码为\uXXXX模式
一般使用16进制表式, char a = 0x0041;
直接使用符号表式 char a = 'a'
使用\uxxxx方式表式,xxxx为16进制,注意前缀u '\u0041'(注意单引号)
以上都是表式的'a'
4,另外unicode的四字节编码表式,以及证明了char存的是utf-16字节编码
一, String a = "\uD83D\uDF01"
二,char[] a = new char[2]; a[0] = '\uD83D'; a[1] = '\uDF01';
三,char[] a = Charactor.toChars(0x1F701)
以上证明了char存的是utf-16的字符编码(unicode编号经过utf-16编码后存入char变量中)。在UTF-16下超过FFFF以上的就会出现字符编码与unicode不一样了。
一些getByte的方法
static void getStringBytes (String s) {
byte[] s1Bytes = s.getBytes(StandardCharsets.UTF_8);
for (int i = 0; i < s1Bytes.length; i++) {
System.out.println(("中的utf-8第" + (i + 1) + "位编码为:" + Integer.toBinaryString((int)s1Bytes[i])));
}
}
static void getHexStringBytes (String s) {
StringBuilder sb = new StringBuilder();
byte[] s1Bytes = s.getBytes(StandardCharsets.UTF_8);
for (int i = 0; i < s1Bytes.length; i++) {
System.out.println(Integer.toHexString(s1Bytes[i]));
sb.append(Integer.toHexString(s1Bytes[i]), 6, 8);
//只有后两位是真正的二进制编码,前面全是补的11111
}
System.out.println(sb);
}
5)为什么char是两个字节,能存汉字
1,java的char默认是使用utf-16编码后存入到char变量中。
而汉子基本在0000 - FFFF(排除掉D800 - DFFF)这个范围内,这个范围内的UTF16不需要特殊处理,可以直接存入(也就是字符的编号,与字符编码一致)。
2,参考别人的
看看就好