# Java - 字符编码

## 字符集

字符集是一系列字符的集合，将每个收录的字符和数字进行映射。最早的字符集是ASCII，使用一个字节进行存储字符，8位一共可以表示256个字符，而ASCII只使用了其中的128位，即0～127位，这128位里面包括了常用的英文字符以及标点符号。
<!--more-->
一部分不使用英文的字符可以用后面的128位来表示，即128～256位，后面的128位也被称为扩展字符集。但是它表示的字符有限，对于汉字来说，用单字节无法表示完整，因此国标码采用了双字节来表示汉字（即GBK等编码）。

由于不同国家的语言编码不同，互相之间如何通过ASCII进行交流？于是就有了万国码Unicode。Unicode是一个几乎包括了世上所有字符的字符集，每个字符都有一个对应的独一无二的Unicode码，比如聊天时使用的emoji表情字符，GitHub上也可以通过 <span>:</span>grin: 这种写法来输出emoji字符，这个对应的是:grin:。

因为Unicode使用四个字节来存储，虽然编码效率高，但是会极大浪费存储空间，因此就有了对Unicode字符集进行编码解码的存储方式，如UTF-8等字符编码。字符编码其实就是对Unicode字符集的实现方式，用以约定如何用1~4个字节来存储字符。

## 字符编码

### UTF-8

UTF-8是可变长编码，即多字节编码，在存储不同的字符时使用的字节数量是不同的。比如存储英文字母时只使用1个字节，而存储汉字时则使用3个字节。

UTF-8分为有BOM（Byte Order Marker）和无BOM的两种编码方式，现代文本编辑器在改变文件的编码时应该都看到过这两种编码。

UTF-8的好处是节省了空间，但编码效率降低了，即时间换空间。

### UTF-16

UTF-16是双字节编码，属于定长编码。由于使用两个字节为一组来表示一个字符，那么就涉及到了字节顺序的问题，即大端小端的问题。这个是Mac机和PC机对字节顺序的理解不一致导致的历史遗留问题，机器在读取字节顺序时，会从低地址读取，将两个字节中的高位字节放在低地址表示，这就是大端BE，即big-endian。反之就是小端LE，即little-endian。

因此UTF-16存在三种编码：

* UTF-16BE，使用两个字节进行存储的大端编码。**Java就是使用的这个编码来存储字符，中文和英文都是两个字节（即char这个基础数据类型），所以Java是双字节编码。**
* UTF-16LE，使用两个字节进行存储的小端编码。
* UTF-16，没有指定后缀，即不知道其是大端还是小端，所以需要用四个字节来存储，开始的两个字节表示该字节数组是大端还是小端。即`FE FF`表示大端，`FF FE`表示小端。

使用UTF-16的好处是编码效率较高，但浪费存储空间，属于用空间换时间。

## 不同字符集编码的英文字母和中文汉字的字节数

英文字母：

```html
编码：GB2312；字节数 : 1

编码：GBK；字节数 : 1

编码：GB18030；字节数 : 1

编码：ISO-8859-1；字节数 : 1

编码：UTF-8；字节数 : 1

编码：UTF-16；字节数 : 4

编码：UTF-16BE；字节数 : 2

编码：UTF-16LE；字节数 : 2
```

中文汉字：

```html
编码：GB2312；字节数 : 2

编码：GBK；字节数 : 2

编码：GB18030；字节数 : 2

编码：ISO-8859-1；字节数 : 1

编码：UTF-8；字节数 : 3

编码：UTF-16；字节数 : 4

编码：UTF-16BE；字节数 : 2

编码：UTF-16LE；字节数 : 2
```

## 参考链接

* [UTF-16BE、UTF-16LE、UTF-16 三者之间的区别](https://blog.csdn.net/QQxiaoqiang1573/article/details/84937863)
* [英文字母和中文汉字在不同字符集编码下的字节数](https://www.cnblogs.com/lancidie/archive/2013/04/12/3017241.html)
* [为什么UTF-16要有LE和BE之分](https://zhidao.baidu.com/question/460283131772995725.html)
* [你真的了解 Unicode 和 UTF-8 吗？](https://www.cnblogs.com/reycg-blog/p/10021658.html)