wangjie_fourth

may the force be with you

0%

对字符的理解

一直以来,字符串、字符集、编码、解码、乱码这些概念把我弄的很迷糊。今天看了一些资料,准备捋一捋。

发展历史

我们知道计算机中的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),即0x00000x10FFFF,每个平面有 65536 个码点。其中我们常称为平面0为BMP[Basic Multilingual Plane]。
Unicode早期并没有流行起来,原因之一就是它占用字节太多。比如说:原本只用一个字节就可以表示A,现在非要用俩个字节。如果涉及到非0平面的字符,就需要四个字节了。

UTF-8字符级编码表

这里是字符级编码表发展的第四阶段。由于互联网的出现,我们需要同一套字符级编码表,但是又因为Unicode太过于浪费空间。所以Unicode慢慢发展成标准,其他字符级编码表只要能跟Unicode一一对应就可以了。
UTF-8就是实现Unicode规范的一个字符级编码表。它的特点就是采用变长字节来缩减字节大小,在UTF-8中,一个字符大小是1~4个字节。

1
2
3
4
5
6
7
Unicode符号范围     |        UTF-8编码方式
(十六进制) | (二进制)
----------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8只要能识别出哪几个字节表示一个字符,就可以截取出这些字节来对应出Unicode字符集编码表,找出对应的字符。计算机看什么时候读到第一个0,前面有几个1就表示要再读多少个字节。
这样的好处不仅仅是能节约空间,而且还可以完美兼容ASCII字符集编码表,因为ASCII的所有码点第一位都是0

Java中的char

其实上面写了那么多,都是因为之前看到了一个问题:char表示的是一个字符,如果某个字符占3个字节的话,char还能表示吗?
这个问题一下子就愣住了我。char是采用Unicode字符集编码表的,那Unicode其他平面是需要大于2个字节来存储的,即使是UTF-8,其中某些字符也还需要三个字节来存储。那这样的话,char还能存储吗?
答案是不能,很直接,只要超过俩个字节表示的码点,char就展示不出来。比如说:

1
2
3
4
public static void main(String[] args) {
char demo = '\uffff';
System.out.println("demo = " + demo);
}

如果你写成\u10000,程序就直接编译报错了。
那为什么java要把char设计成俩个字节呢?这很明显不能表示一个字符呀?
因为早期Unicode设计的时候,认为2个字节足以存储世界各个国家的字符。后来Java设计的时候,也就按照Unicode的俩个字节来了,也就是上面说的BMPBMP中的字符基本上涵盖了我们日常所使用的字符,很少会设计到要占用超过俩个字节的字符。

Java中的String

既然写到这,顺手再写了String
关于String有个比较常问的面试题:Java中的String是不可变的吗?为什么要把String设计成不可变的呢?
首先,String是不可变的,问为什么String设计成不可变的,其实就是问String设计成不可变的优点。
1、保证字符串的hashCode不变
字符串的hashCode是经常用的方法,比如说在HashMap中。如果一个字符串是可变的话,每一次字符串变化,可能就需要重新hash。
2、充分利用字符串常量池
字符串不可变的话,那么很多字符串就可以使用同一个变量,这也就是字符串常量池的存在意义。

其实这一点说是优点,不如说是为了解决字符串不可变而带来大量对象创建缺点的解决方法。

3、线程安全性
不可变对象拥有天生的线程安全性,这样字符串就可以在多线程中安全使用。

还有一个问题就是下面这种:

1
2
3
4
5
6
7
8
9
10
11
// 直接在字符串常量池创建
String constantString = "Baeldung";
// 在堆中新建一个字符串对象,其内部的char[]存储的值是从字符串常量池获取
String newString = new String("Baeldung");
String newString2 = new String("Baeldung");

System.out.println(newString == newString2);
System.out.println("newString = " + newString);

String intern = newString.intern();
System.out.println("intern = " + intern);

答案都写出来了,其实就是问字符串常量池、字符串对象、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