非常好的编码文章:https://www.jianshu.com/p/1b00ca07b003
The JLS says that the translation of Unicode
Escapes to the corresponding Unicode character
is processed before lexical analysis.
首先来看一下常见的码表,如下:
ASCII: | 美国标准信息交换码。用一个字节的7位可以表示,总共128个,0~31是控制字符如换行、回车、删除等,32~126是打印字符,可以通过多键盘输入并且能够显示出来。 |
ISO8859-1: | 拉丁码表。欧洲码表用一个字节的8位表示,共能表示256个字符 |
GB2312: | 中国的中文编码表。双字节编码,总的编码范围是A1~F7,其中A1~A9是符号区,B0~F7是汉字区 |
GBK: | 中国的中文编码表升级,融合了更多的中文文字符号 |
Unicode: | 国际标准码,融合了多种文字。所有文字都用两个字节来表示,Java语言就是unicode |
UTF-8: | 最多用三个字节来表示一个字符,采用了一种变长技术,每个编码区域有不同的字码长度。不同类型的字符可以由1~6个字节组成。 |
UTF-16 | ISO试图创建一个全新的语言字典来容纳世界上所有的语言(实际上收录不下)。用两个字节来表示,也就是每两个字节表示一个字符,这就大大简化了字符串操作,也是Java以UTF-16作为内存的字符存储格式的一个很重要原因。 |
ASCII的几个控制字符如下:
(1)回车 \r 本义是光标重新回到本行开头,r的英文return,控制字符可以写成CR,即Carriage Return
(2)换行 \n 本义是光标往下一行(不一定到下一行行首),n的英文newline,控制字符可以写成LF,即Line Feed
在不同的操作系统这几个字符表现不同,比如在WIN系统下,这两个字符就是表现的本义,在UNIX类系统,换行\n就表现为光标下一行并回到行首,在MAC上,\r就表现为回到本行开头并往下一行,至于ENTER键的定义是与操作系统有关的。通常用的Enter是两个加起来
(3)\t作为一个转义字符,代表需要实际输出一个tab键输入的效果
Java中转义字符:
1、八进制转义序列:\ + 1到3位5数字;范围'\000'~'\377' \0:空字符
2、Unicode转义字符:\u + 四个十六进制数字;0~65535 \u0000:空字符
3、特殊字符:就3个
\":双引号
\':单引号
\\:反斜线
4、控制字符:5个
\r 回车
\n 换行
\f 走纸换页
\t 横向跳格
\b 退格
关于如上几个控制字符可以参考:
关于 LF CR 参考https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.4
关于FF 或者\t 等参考https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.6
Java中的字符存放:https://www.cnblogs.com/extjs4/p/8486738.html
在Java程序运行过程中,字符串对象始终以Unicode编码方式保存在内存中.
1、Unicode编码
(1)编码字符集
编码字符集是一个字符集,它为每一个字符分配一个唯一数字。Unicode 标准的核心是一个编码字符集,字母“A”的编码为0041和字符“€”的编码为20AC。Unicode标准始终使用十六进制数字,而且在书写时在前面加上前缀“U+”,所以“A”的编码书写为“U+0041”。
(2)代码点code point和代码单元
代码点是指可用于编码字符集的数字。编码字符集定义一个有效的代码点范围,但是并不一定将字符分配给所有这些代码点。有效的 Unicode代码点范围是 U+0000 至 U+10FFFF。
代码单元可以理解为字符编码的一个基本单元,最常用的代码单元是字节(即8位),但是16位和32位整数也可以用于内部处理。
(3)增补字符
16 位编码的所有 65536 个字符并不能完全表示全世界所有正在使用或曾经使用的字符。于是,Unicode 标准已扩展到包含多达 1,112,064 个字符。那些超出原来的16 位限制的字符被称作增补字符。
Java的char类型是固定16bits的。代码点在U+0000 — U+FFFF之内到是可以用一个char完整的表示出一个字符。但代码点在U+FFFF之外的,一个char无论如何无法表示一个完整字符。这样用char类型来获取字符串中的那些代码点在U+FFFF之外的字符就会出现问题。
因此,Java 平台不仅需要支持增补字符,而且必须使应用程序能够方便地做到这一点。Java Community Process 召集了一个专家组,以期找到一个适当的解决方案。该小组被称为JSR-204专家组,使用Unicode 增补字符支持的Java技术规范请求的编号。
增补字符是代码点在 U+10000 至 U+10FFFF 范围之间的字符,也就是那些使用原始的 Unicode 的 16 位设计无法表示的字符。从 U+0000 至 U+FFFF 之间的字符集有时候被称为基本多语言面 (BMP UBasic Multilingual Plane )。因此,每一个 Unicode 字符要么属于 BMP,要么属于增补字符。
编码方式是指规定以何种方式将逻辑上的码位值以字节或位的方式表示出来。虽然很可能某两种编码方式的目标字符集相同,但因为编码方式和字符在字符集中的顺序不同,因此在内存上的表现形式也不同。
2、UTF-16
使用一个或两个未分配的16位代码单元的序列对 Unicode 代码点进行编码。假设U是一个代码点,也就是Unicode编码表中一个字符所对应的Unicode值。
(1)如果在BMP级别中,那么16bits(一个代码单元)就足够表示出字符的Unicode值。
(2)如果U+10FFFF>U>=U+10000,也就是处于增补字符级别中。UTF-16用2个16位来表示,并且正好将每个16位都控制在替代区域U+D800-U+DFFF(其中\uD800-\uDBFF为高代理项范围,\uDC00- \uDFFF为低代理项范围) 中。
下面来看一下Java是如何处理这些增补字符的。
分别初始化2个16位无符号的整数 —— W1和W2
W1 = 110110yyyyyyyyyy(0xD800-0xDBFF)
W2 = 110111xxxxxxxxxx(0xDC00-OxDFFF)
然后,将Unicode的高10位分配给W1的低10位,将Unicode 的低10位分配给W2的低10位。这样就可以将20bits的代码点U拆成两个16bits的代码单元。而且这两个代码点正好落在替代区域U+D800-U+DFFF中。
UTF-16不兼容与ASCII码,而UTF-8兼容。
举个例子:代码点U+1D56B(使用4个字节表示的代码点)
0x1D56B = 0001 1101 01-01 0110 1011
将0x1D56B的高10位0001 1101 01分配给W1的低10位组合成110110 0001 1101 01=0xD875
将0x1D56B的低10位01 0110 1011分配给W2的低10位组合成110111 01 0110 1011=0xDD6B
这样代码点U+1D56B采用UTF-16编码方式,用2个连续的代码单元U+D875和U+DD68表示出了。
3、UTF-8
使用一至四个字节的序列对编码 Unicode 代码点进行编码。
U+0000 至 U+007F 使用一个字节编码
U+0080 至 U+07FF 使用两个字节
U+0800 至 U+FFFF 使用三个字节
U+10000 至 U+10FFFF 使用四个字节
UTF-8 设计原理为:字节值 0x00 至 0x7F 始终表示代码点 U+0000 至 U+007F(Basic Latin 字符子集,它对应 ASCII 字符集)。这些字节值永远不会表示其他代码点,这一特性使 UTF-8 可以很方便地在软件中将特殊的含义赋予某些 ASCII 字符。
以下是Unicode和UTF-8之间的转换关系表:
U-00000000 - U-0000007F: 0xxxxxxx U-00000080 - U-000007FF: 110xxxxx 10xxxxxx U-00000800 - U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx U-00010000 - U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx U-00200000 - U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxx x 10xxxxxx U-04000000 - U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
可以看到:
(1)如果一个字节以10开头,一定不是首字节,需要向前查找。
(2)在一个首字节中,如果以0开头,表示是一个ASCII字符,而开头的连续的1的个数也表示了这个字符的字节数。如1110xxxx表示这个字符由三个字节组成。
下面来看一个使用各种编码对字符进行编码的例子,如下:
缺点就是用UTF-16两个字节可表示出来的字符有时需要用更多的字节来表示。
4、Java应用Unicode转义字符
理论上,Unicode转义字符可以代表任何字符(我们不考虑那些不在Unicode码表内的字符),因此我们很容易想到:所有字符都应该可以使用Unicode转义字符的形式。为了了解Unicode转义字符带来的危险,我们看如下程序:
public class StringLengh {
publicstatic void main(String[] args) {
System.out.println("abc\u000a".length());
}
}
public class StringLengh{
publicstatic void main(String[] args){
System.out.println("abc
".length());
}
}
\u0070u\u0062lic \u0063l\u0061ss Hello {
publicstatic void main(String[] args){
System.out.println("HelloWorld!");
}
}
总结:
(1)java是允许在注释以及代码中使用\u开头的Unicode转义字符的,但是要求转义必须有效,否则编译器会报告错误
(2)Java 对在字符串字面常量中的 Unicode 转义字符没有提供任何特殊处理。编译器在将程序解析成各种符号之前,先将Unicode转义字符转换成为它们所表示的字符
参考:
(1)https://blog.csdn.net/tin0x/article/details/6064665