一直以来,字符串、字符集、编码、解码、乱码这些概念把我弄的很迷糊。今天看了一些资料,准备捋一捋。
发展历史
我们知道计算机中的CPU是通过高、低电压来识别0、1。如果要计算机识别更多的字符,我们就需要使用0、1组合在一起来表示其他字符。比如说:0100-0001
表示字符A
。我们将这种0、1组合的数字与对应字符的映射关系称之为字符集。还有一些其他概念:
- 将字符转换字节的过程,称为编码;
- 将字节转换字符的过程,称为解码;
- 字符集编码中的数字,称为码点
code print
;
ASCII
字符集编码表
ASCII
是计算机早期的字符集编码。它使用一个字节来存储128
种字符,所以ASCII
的码点第一位全是0。ASCII
的缺点就是它只表示了英文字符,其他国家的字符都没有对应的表示。
GBK
字符集编码表
这里其实表示字符级编码表发展的第二阶段。由于ASCII
不包含其他国家字符,所以每个国家都开始弄自己国家的字符编码表。GBK
就是中国针对汉字建立的。
这个时期的字符级编码表的缺点就是没有统一规范,大家各用各的。这也是乱码的原因,同一个字符如果使用不同的字符级进行编码、解码就很容易乱码。
Unicode
字符集编码表
这里是字符级编码表发展的第三阶段。Unicode
是一个包含世界上所有国家字符的字符集编码表,目的是解决第二阶段不同标准的字符级编码。Unicode
目前规划的总空间是17个平面(平面0至16),即0x0000
至0x10FFFF
,每个平面有 65536 个码点。其中我们常称为平面0为BMP
[Basic Multilingual Plane
]。Unicode
早期并没有流行起来,原因之一就是它占用字节太多。比如说:原本只用一个字节就可以表示A
,现在非要用俩个字节。如果涉及到非0平面的字符,就需要四个字节了。
UTF-8
字符级编码表
这里是字符级编码表发展的第四阶段。由于互联网的出现,我们需要同一套字符级编码表,但是又因为Unicode
太过于浪费空间。所以Unicode
慢慢发展成标准,其他字符级编码表只要能跟Unicode
一一对应就可以了。UTF-8
就是实现Unicode
规范的一个字符级编码表。它的特点就是采用变长字节来缩减字节大小,在UTF-8
中,一个字符大小是1~4个字节。
1 | Unicode符号范围 | UTF-8编码方式 |
UTF-8
只要能识别出哪几个字节表示一个字符,就可以截取出这些字节来对应出Unicode
字符集编码表,找出对应的字符。计算机看什么时候读到第一个0,前面有几个1就表示要再读多少个字节。
这样的好处不仅仅是能节约空间,而且还可以完美兼容ASCII
字符集编码表,因为ASCII
的所有码点第一位都是0
。
Java
中的char
其实上面写了那么多,都是因为之前看到了一个问题:char
表示的是一个字符,如果某个字符占3个字节的话,char
还能表示吗?
这个问题一下子就愣住了我。char
是采用Unicode
字符集编码表的,那Unicode
其他平面是需要大于2个字节来存储的,即使是UTF-8
,其中某些字符也还需要三个字节来存储。那这样的话,char
还能存储吗?
答案是不能,很直接,只要超过俩个字节表示的码点,char
就展示不出来。比如说:
1 | public static void main(String[] args) { |
如果你写成\u10000
,程序就直接编译报错了。
那为什么java
要把char
设计成俩个字节呢?这很明显不能表示一个字符呀?
因为早期Unicode
设计的时候,认为2个字节足以存储世界各个国家的字符。后来Java
设计的时候,也就按照Unicode
的俩个字节来了,也就是上面说的BMP
。BMP
中的字符基本上涵盖了我们日常所使用的字符,很少会设计到要占用超过俩个字节的字符。
Java
中的String
既然写到这,顺手再写了String
。
关于String
有个比较常问的面试题:Java
中的String
是不可变的吗?为什么要把String
设计成不可变的呢?
首先,String
是不可变的,问为什么String
设计成不可变的,其实就是问String
设计成不可变的优点。
1、保证字符串的hashCode
不变
字符串的hashCode
是经常用的方法,比如说在HashMap
中。如果一个字符串是可变的话,每一次字符串变化,可能就需要重新hash。
2、充分利用字符串常量池
字符串不可变的话,那么很多字符串就可以使用同一个变量,这也就是字符串常量池的存在意义。
其实这一点说是优点,不如说是为了解决字符串不可变而带来大量对象创建缺点的解决方法。
3、线程安全性
不可变对象拥有天生的线程安全性,这样字符串就可以在多线程中安全使用。
还有一个问题就是下面这种:
1 | // 直接在字符串常量池创建 |
答案都写出来了,其实就是问字符串常量池、字符串对象、intern
一些问题。
再来引申一点,字符串常量池是什么?它位于JVM
哪块地方?
参考intern
方法上面的注释,能大概明白字符串常量池是由String
类维护的,位于运行时常量池中的,一块包含字符串常量的内存空间。
在jvm8
中,字符串常量池位于的运行时常量池,是在方法区上。
https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
https://medium.com/@joffrey.bion/charset-encoding-encryption-same-thing-6242c3f9da0c
https://www.cnblogs.com/hongdada/p/9901246.html
https://dzone.com/articles/why-string-immutable-java
https://www.baeldung.com/java-string-pool