https://www.bilibili.com/video/BV1gZ4y1x7p7

字符集

字符集(Character set)是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、 GB18030字符集、Unicode字符集等。

码位:就是字符集中某个字符的唯一编号。
将每一个字符对应到一个专属的码位(code point),就可以表示这套字符集中的每一个字符。

ASCII字符集

字符编码 - 图1
ASCII 总共128个字符,给每个字符一个编码,这个编码就是码位,表示128个码位只要一个字节就可以了。

ISO-8859-1

ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。

扩展ASCII编码 28 = 256

GB2312

分区表示
整个字符集分成94个区,每区有94个位,共8836个码位。每个区位上只有一个字符(汉字或符号),因此可用所在的区和位来对汉字进行编码,称为区位码

01-09 区为特殊符号。除汉字以外的682个字符。
16-55 区为一级汉字,按拼音排序。
56-87 区为二级汉字,按部首/笔画排序。
10-15 区及88-94区则未有编码。
image.png
0389就是字符y的区位码。03 表示第3区,8和9确定具体位置。
image.png
image.png
image.png
image.png
image.png

image.png

  1. @Test
  2. public void test_3() throws UnsupportedEncodingException {
  3. byte[] gb2312s = "侃".getBytes("GB2312");
  4. System.out.println(Arrays.toString(gb2312s)); //[-39, -87]
  5. System.out.println(0xA0); // 160
  6. System.out.println(-39 - 0xA0); // -199 byte最小也只有-128
  7. System.out.println(-199 & 0xff); // 57
  8. System.out.println(-87 - 0xA0); // -247
  9. System.out.println(-247 & 0xff); // 9 -> 09
  10. }
  1. @Test
  2. public void test_6() throws UnsupportedEncodingException {
  3. byte[] b = "侃".getBytes("gb2312");
  4. String ret = "";
  5. for (int i = 0; i < b.length; i++) {
  6. String hex = Integer.toHexString(b[i] & 0xFF);
  7. if (hex.length() == 1) {
  8. hex = '0' + hex;
  9. }
  10. ret += hex.toUpperCase();
  11. }
  12. System.out.println(ret); // D9A9
  13. }

特别注意:Java中byte转换int时与0xff进行与运算

  1. @Test
  2. public void test_5() {
  3. byte b = -127;
  4. System.out.println(b); // -127 byte是一个字节 补码是 10000001
  5. int c = b & 0xff; // int 是4个字节 1111111111111111111111111 10000001 & 11111111 -> 00000000000000000000000 10000001 现在最高位是0,128+1 = 129
  6. System.out.println(c); // 129
  7. }
  1. @Test
  2. public void test_4() {
  3. byte[] a = new byte[10];
  4. // 当将-127赋值给a[0]时候,a[0]作为一个byte类型,其计算机存储的补码是10000001(8位)。
  5. a[0] = -127;
  6. // 将a[0] 作为int类型向控制台输出的时候,jvm作了一个补位的处理,
  7. // 因为int类型是32位所以补位后的补码就是1111111111111111111111111 10000001(32位),这个32位二进制补码表示的也是-127.
  8. // 注意: 补位是补1 还是补0,取决于byte的最高位是1还是0
  9. //虽然byte->int计算机背后存储的二进制补码由10000001(8位)转化成了 1111111111111111111111111 10000001(32位)很显然这两个补码表示的十进制数字依然是相同的。
  10. System.out.println(a[0]); // -127 这个是10进制的结果没变 但是二进制表示变了
  11. //因为byte占1个字节,所以0xff刚还是一个字节,保证这一个字节的补码不变
  12. int c = a[0] & 0xff; // 1111111111111111111111111 10000001 & 11111111 -> 00000000000000000000000 10000001 现在最高位是0,128+1 = 129
  13. System.out.println(c); // 129 十进制表示的结果变了,但是二进制的表示没有变
  14. }
  1. //获取传入字符串在gb2312字符集的区位码
  2. public static StringBuffer getQuweiCode(String str) throws Exception {
  3. StringBuffer sb = new StringBuffer();
  4. byte[] b = str.getBytes("gb2312");
  5. int[] quwei = new int[b.length / 2];
  6. for (int i = 0, k = b.length / 2; i < k; i++) {
  7. quwei[i] = (((b[2 * i] - 0xA0) & 0xff) * 100) + ((b[2 * i + 1] - 0xA0) & 0xff);
  8. }
  9. for (int i : quwei) {
  10. sb.append(i);
  11. }
  12. return sb;
  13. }

GBK

GBK是采用单双字节变长编码,英文使用单字节编码,完全兼容ASCII字符编码,中文部分采用双字节编码。
image.png

Unicode

image.png

UTF-8

UTF-8的特点是对不同范围的字符使用不同长度的编码。

简单来说:

  • Unicode 是「字符集」
  • UTF-8 是「编码规则」

字符集:为每一个「字符」分配一个唯一的 ID(学名为码位 / 码点 / Code Point)
编码规则:将「码位」转换为字节序列的规则(编码/解码 可以理解为 加密/解密 的过程)

广义的 Unicode 是一个标准,定义了一个字符集以及一系列的编码规则,即 Unicode 字符集和 UTF-8、UTF-16、UTF-32 等等编码。

Unicode 字符集为每一个字符分配一个码位,例如「知」的码位是 30693,记作 U+77E5(30693 的十六进制为 0x77E5)。

UTF-8 顾名思义,是一套以 8 位为一个编码单位的可变长编码。

将一个码位编码为 1 到 4 个字节:

  1. U+ 0000 ~ U+ 007F: 0XXXXXXX
  2. U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
  3. U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
  4. U+10000 ~ U+10FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

根据上表中的编码规则,之前的「知」字的码位 U+77E5 属于第三行的范围:

  1. 7 7 E 5
  2. 0111 0111 1110 0101 二进制的 77E5
  3. --------------------------
  4. 0111 011111 100101 二进制的 77E5
  5. 1110XXXX 10XXXXXX 10XXXXXX 模版(上表第三行)
  6. 11100111 10011111 10100101 代入模版
  7. E 7 9 F A 5
  1. import java.nio.charset.Charset;
  2. import java.util.Arrays;
  3. public class Test2 {
  4. public static void main(String[] args) {
  5. // 获取JVM默认字符集
  6. System.out.println(Charset.defaultCharset()); //UTF-8
  7. //16进制 77E5 转为 10进制为 30693
  8. Integer x = Integer.valueOf("77E5", 16);
  9. System.out.println(x); // 30693
  10. String b = Integer.toBinaryString(x);
  11. System.out.println(b); // 0111 0111 1110 0101 (最高位补零)
  12. byte x1 = (byte) Integer.parseUnsignedInt("11100111", 2);
  13. byte x2 = (byte) Integer.parseUnsignedInt("10011111", 2);
  14. byte x3 = (byte) Integer.parseUnsignedInt("10100101", 2);
  15. System.out.println(String.format("x1=%s, x2=%s, x3=%s", x1, x2, x3));
  16. byte[] bytes = {x1, x2, x3};
  17. System.out.println(new String(bytes));
  18. System.out.println(Arrays.toString("知".getBytes()));
  19. }
  20. }

UTF-8 30693 111011111100101 x1=-25, x2=-97, x3=-91

[-25, -97, -91]

String 字符串提供了一个public int codePointAt(int index) 返回指定索引处的字符(Unicode代码点)

  1. System.out.println("知".codePointAt(0)); //30693

字符编码 - 图11
比如『汉』这个字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001

比如【王】
image.png