Unicode
Unicode编码定义了这个世界上几乎所有字符(就是你眼睛看的字符比如ABC,汉字等)的数字表示,而且Unicode还兼容了很多老版本的编码规范,例如你熟悉的 ASCII码。
码点
我们国家的每一个人都对应唯一的一个身份证号,而Unicode也为了每个字符发了一张身份证,这张“身份证”上有一串唯一的数字ID确定了这个字符。
这串数字在整个计算机的世界具有唯一性,Unicode给这串数字ID起了个名字叫[码点]。
码点是如何表示?
U+XXXXXX 是码点的表示形式,X 代表一个十六制数字,可以有 4-6 位,不足 4 位前补 0 补足 4 位,超过则按是几位就是几位。
字符A的ASCII码是众所周知是65吧,将65转换成16进制就是41(16×4+(16^0)×1 = 65),按照规则前面补0,那么字符A的码点表示就是U+0041,依次类推B的码点表示就是U+0042...等等,汉字"你"的字符表示是“U+4F60”...
注意
-
unicode是一个码表,描述的是字符和码的对应关系,整个unicode码表是很大的一个返回,不只是两个字节,三个字节,它可以有任意的长度,只是一个映射,相当于一个字符到数字的大map
image.png
image.png
https://www.qqxiuzi.cn/zh/hanzi-unicode-bianma.php 至于存储的时候占用几个字节,就是UTF-8或者UFT-16的事儿了。比如汉字 你 的unicode是\u4f60 存储的时候,utf-8需要三个字节,而并不是按码表用两个字节存。
汉字转unicode 首先java内存中所有的汉字都是unicode的,并用utf-16存储的。所以String的toByte方法,得到数字,就是utf-16编码。 注意注意,这里和utf-8没任何关系。所以
Integer.toHexString('你')
得到的结果就是\u4f60。再次强调,这个是utf-16编码,和utf8没有任何关系。
-
对于生僻字,比如“𠀃”,在unicode码表中是属于20000-2A6D6这个段的。但是java中一个char是两个字节的,表示不了这个码了? 所以idea中会报错:
image.png 但是String可以保存,所以String a = “𠀃” , a.length() ==2 不是1 。这时候utf-16就是用两个字节表示这个生僻的字符。
所以下面这个程序输出两个字节,注意这里不是unicode码表中的数字,而是utf-16存储的unicode字符。我们所谓的汉字转unicode,其实就是汉字转utf-16.
-
char='恁',输出他的unicode是\u6041,两个字节,utf-16没问题。但是String a =“恁”
a.getBytes().length却等于3个字节? 这是为啥? 因为getByte相当于做了一次编码,用的是操作系统默认编码,如果是utf-8,那么就是三个字节了。看下面:
image.png
默认utf-8的getByte输出字符长度是3,因为占用3个字节。
改成utf-16变成4了? 而且最后两个字节79,96. 正好就是他的unicode码\u4f60。
但是前两个字节是什么?前面的feff是什么东西呢?
- 在wiki上我们可以看到:
UTF-16的大尾序和小尾序存储形式都在用。一般来说,以Macintosh制作或存储的文字使用大尾序格式,以Microsoft或Linux制作或存储的文字使用小尾序格式。
为了弄清楚UTF-16文件的大小尾序,在UTF-16文件的开首,都会放置一个U+FEFF字符作为Byte Order Mark(UTF-16LE以FF FE代表,UTF-16BE以FE FF代表),以显示这个文本文件是以UTF-16编码,其中U+FEFF字符在UNICODE中代表的意义是ZERO WIDTH NO-BREAK SPACE,顾名思义,它是个没有宽度也没有断字的空白。
原来FE FF代表 UTF-16BE ,就是大尾序格式,显示的是0061
可以看到我们换成
byte[] bb= a.getBytes("UTF-16BE"); 得到的结果就是0061了
反之
byte[] bb= a.getBytes("UTF-16LE"); 得到的结果就是6100了
结论:getBytes("UTF-16")的byte长度会比我们预期的多2,就是两个byte开头要指定是大尾格式,还是小尾格式
- 总上,char中存储的才是即使内存中utf-16的unicode。 如果用string来转成byte,就有一个重新编码的过程。如果从string中首先取出来char,这个char的两个字节就是真正的utf-16字符编码
unicode的三种编码方式
(换句话就是码点如何转换为utf-8或者utf-16或者utf-32)
UTF-16 是一种变长的 2 或 4 字节编码模式。对于 BMP 内的字符使用 2 字节编码,其它的则使用 4 字节组成所谓的代理对来编码。
UTF-32 采用的定长四字节则是 32 位,所以它表示所有的码点不但毫无压力,反而绰绰有余
UTF-8 是变长的编码方案,可以有 1,2,3,4 四种字节组合。UTF-8 采用了高位保留方式来区别不同变长
Java中 内码和外码
- 内码:char或String在内存里使用的编码方式。
- 外码:除了内码都可以认为是“外码”。(包括class文件的编码)
java内码:
unicode(utf-16)
jvm默认外码:
windows——gbk
Linux——utf-8
Java字符和字符串存在于以下几个地方:
1.Java源码文件,.java,可以是任意字符编码,如GBK,UTF-8
2.Class文件,.class,采用的是一种改进的UTF-8编码(Modified UTF-8)
3.JVM,内存中使用UTF-16编码
Java中的编码
上边说了一些概念,那么java中的字符编码是怎么设计的呢?
源文件,也就是.java文件,在你的操作系统中新建.java文件,编写代码,保存,保存的时候注意保存的编码格式,可能是asiic,gbk,utf-8等等,根据不同的编辑器,编码方式也可能不同,如果使用idea开发,setting中有个配置,就是源文件的编码方式,这里,我改成UTF-8。总之,第一步,是写,写一个utf-8编码的文件。
源文件有了,下一步编译,也就是javac,这个时候要读取源文件,用什么编码方式读呢? 默认是操作系统提供
System.getProperty("file.encoding")
。还可以加参数,-Dfile.encoding=UTF-8。 这样就保证读出来的内容是正确的。这一步,是,读。读取完之后,字符串需要在jvm的堆空间存储,这时候,就用什么编码的呢? java中字符都是unicode,存储方式是UTF-16。这时候,一个字符可能占用2-4个字节。这一步,是写,写入编码方式utf-16
假设这个源代码实现的功能是把一堆汉字写入一个新文件,或者通过网络传输给对端。那么程序执行,从内存中读取字符,这时候读取方式肯定也是utf-16,读取出来也是unicode,也就是说这个utf-16的处理根本无需关心,只要知道java内存中,所有的字符都是unicode即可。注意这里不是程序源码中指定编码方式,而是jvm自动处理的,也就是说在jvm中,会自动处理utf-16的转换,只要class文件中的字符编码是正确的,就可以认为没有乱码的产生。到这里,内存中报错了unicode编码的所有字符串。
读取完成之后,这时候字符的真正编码又回到了最原始的源文件的编码方式,也就是unicode 。如果网络对端的操作系统只有一种汉字编码,GBK。这种情况,是无法在对端显示出正确的汉字的,因为只有支持unicode字符集的系统,java中的汉字才能够显示出来,比如一个英文系统,只支持asiic字符,那么汉字是无论如何也都无法显示出来的。
要传给网络对端,或者写文件,对方接受到的,文件里写入的,也就是unicode,至于对端和文件怎么保存,那就是另一套逻辑了。
getBytes(String charsetName)
new String (iso8859,Charset.forName("GBK"));
这两个方法又是怎么回事呢? 其实就是相当于给字符串加密解密,原本是个内存中的unicode,getByte之后,就是用charsetName重新编码了的byte数组,如果想要还原成原样,那么new String的时候,也要对应的使用编码方式进行解码,才能拿到正确的unicode字符。System.out.println('\u61D2');
会输出汉字。因为之前所述,java中所有的字符都是unicode编码,jvm中用utf-16存储,所以,我们再写代码的时候,可以完全只使用unicode来写代码,这样javac的时候,无论设置了什么参数,这些unicode都不会转换,会原样写入到class文件当中。至于print的时候,只要你的操作系统支持unicode,那么就能显示出正确的结果。我们可以写下面这种代码(可能ide会报错,但是可以正常运行):
double π = Math.PI;
System.out.println(\u03C0);
\u0053ystem.out.println('\u61D2');
u03C0就是π
\u0053就是S
浏览器中的编码
- 两次编码
- 很早之前,一个老工程师告诉我,在js中进行页面跳转,或者jsp的forward跳转,如果url中涉及到中文参数的话,要进行两次编码。然后再后台一次解码,这样,就不会乱了。
- 但是原理是啥样的呢? 因为request.getParamter()方法,会进行一次解码,这个是根据系统属性,或者tomcat的配置,进行解码。在这个编码方式不确定的情况下。前台两次编码,第一次编码后中文变成了utf8.第二次编码后因为全是assic字符,所以无论request.getParamter()是用什么解码方式,都会得到正确的字符,因为assic码在所有的编码方式中,都是一致的。这样就屏蔽了request.getParamter()的编码差异。只要保证第一次编码的字符集和后台我们那一次解码的字符集是相同的,就ok了。
- 那如果直接在浏览器输入中文呢? 这个就控制不了两次编码了,只有一次编码,编码方式,就看浏览器的规定了。 然后如果和后台request.getParamter()解码的字符集不同,那么就必然会出现乱码了。
问题:
- 变量名称,首字母:英文字母或者美元符或者下划线。非首字母:美元符或者字母或者数字或者下划线组成。那么首字母的范围是不是比非首字母少10个? 也是除了就0到9,首字母和非首字母范围一致?如何验证?
2.char多大?用几个字节表示?能否表示中文?能否表示全部中文?
3.String nin=“恁” ,nin.getBytes().length 为什么等于3,在你同事电脑里可能等于2?是不是这个就是char的真实大小?
4.截图里的代码,能运行么?会输出什么?
5.在idea或者eclipse里,用gbk或者utf-8保存源文件,编译出来的class文件有区别么?
jvm中是用的编码是和源文件一致么?
6.char s = ‘再' , s+'见' 会得到什么值 ? char st =‘再'+ '见'+ '见' 呢?
7.char uy = '\377'; char uys = '\378'; 这两种定义方式有什么起源?都能成功执行么?
8.unicode和utf-8,utf-16到底什么关系? 什么叫code point?ucs2和ucs4是什么? ucs2能保存全世界所有的字符么?
char a=111 用整数来定义,整数的范围是多少? char和short占用字节大小是否一样? 能否直接转换?
UTF-16的大尾序和小尾序储存格式是什么样子的?