关于编码和解码,下面举一个例子
(1)查找码点并转换为二进制
比如'我'这个中文字符,在Unicode字符集中的码点是25105,将其转换为二进制是110,0010,0001,0001。
(2)编码
其二进制并不能直接存储到内存中,而是需要按照UTF-8的编码方式使用3个字节进行存储,格式为1110xxxx 10xxxxxx 10xxxxxx,可以看到余下16位二进制需要填充,所以将110,0011,1110,0001的高位补0,补成16bit,即0110,0010,0001,0001,填充到3个字节中去就是:11100110 10001000 10010001。这就是实际存储在计算机中的数据。
通过getBytes()方法对字符进行编码,也可以指定编码方式,下面是测试代码:
public class test1 {
public static void main(String[] args) throws IOException {
byte[] bytes = "我".getBytes("UTF-8");
for (byte b : bytes) {
System.out.println(b);
}
}
}
输出结果:
分析:getBytes()方法的返回值是字节数组。
-26的补码是11100110,可以看到与编码的第一个字节对应,后面一一验证也都是正确的。
(3)解码
首先读取3个字节的数据,将红色的bit位剔除,0110,0011,1110,0001就是实际的数据。
(4)转换为十进制
0110,0011,1110,0001转换为十进制就是25105。
终这个字符在GBK字符集中的码点为32456,转换为二进制后的结果为111,1110,1100,1000。
高位补1。
思考了N多问题。
问题1:read()方法的返回值一定是某个字符在字符集中的码点吗?
思路:①先把'我'这个字符按照UTF-8的编码方式写到文件中;
②再使用read()方法读取,并指定解码方式为UTF-8。
下面是代码实现:
public class test2 {
public static void main(String[] args) throws IOException {
byte[] bytes = "我".getBytes("UTF-8");
FileOutputStream fos = new FileOutputStream("demo.txt");
fos.write(bytes);
fos.close();
FileReader fr = new FileReader("demo.txt");
int i = fr.read();
System.out.println(i);
fr.close();
}
}
输出结果为25105,Unicode字符集中的码点相同,所以read()方法的返回值一定是字符集中的码点。
问题2:'我'这个字符在GBK中的码点?
由于上面的结论,即read()方法的返回值一定是字符集中的码点,所以下面依然利用上述代码试图给出'我'这个字符在GBK中的码点。
public class test2 {
public static void main(String[] args) throws IOException {
byte[] bytes = "我".getBytes("GBK");
FileOutputStream fos = new FileOutputStream("demo.txt");
fos.write(bytes);
fos.close();
FileReader fr = new FileReader("demo.txt", Charset.forName("GBK"));
int i = fr.read();
System.out.println(i);
fr.close();
}
}
输出结果是25105,就以为'我'这个字符在GBK字符集中的码点也是25105,和Unicode字符集中的码点一致。
在知道了'我'这个字符在GBK中的码点之后,我就试图利用GBK的编码方式自行计算出应存储在底层中的二进制,然后和通过getBytes()方法得到的字节进行对比,以为结果应该是一样的。
问题3:利用GBK的编码方式自行计算出应存储在底层中的二进制
25105的二进制是110,0010,0001,0001,按照GBK的两个字节中第一个字节的首位为1的规则,在最高位补1,结果是1110,0010,0001,0001。
问题4:通过getBytes()方法得到的字节
public class test1 {
public static void main(String[] args) throws IOException {
byte[] bytes = "我".getBytes("GBK");
for (byte b : bytes) {
System.out.print(b + " ");
}
}
}
输出结果为-50和-46:
下面来分析一下这两个数:
计算机存的是补码,但我们打印出来的是十进制。这个十进制是补码所对应的十进制。
那么补码所对应的十进制是如何计算的呢?
拿补码1111 1111来进行举例。
补码:1111 1111 --> 反码:1111 1110 --> 原码:1000 0001,转换为十进制是-1。
所以要计算补码所对应的十进制,首先把补码转成反码,再把反码转换为原码,最后把原码转成十进制数。
一般来说,补码只是原码的另一种表现形式。补码和原码所表示的十进制的值是一样的。
那么整数所对应的补码又如何计算呢?
-50的原码是1011 0010,-46 的原码是1010 1110。
步骤如下:
原码:1011 0010 --> 反码:1100 1101 --> 补码:1100 1110
原码:1010 1110 --> 反码:1101 0001 --> 补码:1101 0010
将两部分拼在一起之后为1100 1110 1101 0010,这就是'我'这个字符使用GBK编码方式实际存储在计算机中的二进制,转换为十进制是52946,这就是'我'这个字符在GBK字符集中所对应的码点。
-50和-46的补码是1100 1110 1101 0010,这是在计算机中实际存储的,和我计算出来的1110 0010 0001 0001并不一样啊。百思不得其解,为什么不一样?因为25105并不是正确的码点,所以编码的二进制1110 0010 0001 0001自然也就是错的。
至此可以得出read方法返回的都是字符对应的Unicode码点。
问题5:已经知道了'我'这个字符在GBK中的码点是52946,想验证一下对不对呢?
52496转换为十六进制是CED2,通过在线GBK编码表-GBK中文字符集-在线GBK汉字编码字符集对照表 (jsons.cn)
在这个网址中进行查询(快捷键crtl+F)得到CED2对应的字符就是'我',下面是图片。
现在可以回答这个问题了:
问题6:如何知道某个字符在某个字符集中的码点?
有两种方式:①查字符集;
②通过getBytes()方法得到存储在计算机中的数据,然后解码再转换为十进制,就是对应的码点了。这种方式一定要了解编码方式,要不然不会解码。
问题7:GBK的两个字节中第一个字节的首位为1,是符号位还是数值位?
-50和-46的补码是是1100 1110 1101 0010,这是实际存储在计算机中的数据。
(1)首先把最高位的1看作数值位,转换为十进制是52946;
(2)如果看作是符号位,那肯定是负数呀,但是字符集码点怎么会是负数呢?
所以结论是GBK的两个字节中第一个字节的首位为1,是数值位。
问题8:GBK编码方式为什么第一个字节的首位是1?
我觉得是因为中文字符的码点都很大,都超过1000 0000 0000 0000(其十进制是32,768),又搜了GBK的码点范围,给出的十六进制范围是0x8140~0xFEFE,转换为十进制是33088~65278。
再次给出一个建议,不要再使用GPT进行进制转换了,都是错的。
可以看到GBK的码点的开始值33088是大于32768的,所以这也就是为什么最高位为1的原因,并不是人为设置的。
问题9:关于1100 1110 1101 0010转换为十进制
(1)刚开始使用GPT进行进制转换,下面这个将1100 1110 1101 0010最高位的1解释为了符号位。
就不能不解释为符号位吗?于是又用GPT指定首位不是符号位,得到的结果是50792,我就一直以为1100 1110 1101 0010转换为十进制是50792。
补充:好像GPT还给出过47100,真是离谱。
但是我又多想了一步,用网址验证一下50792转换为二进制的结果,下图可以看到和实际存储在计算机中的二进制1100 1110 1101 0010并不一样。为啥总是对不上啊,不知道哪个有问题,怀疑是GPT有问题,烦死。
到底是谁有问题? 答案就是GPT,总是瞎给答案。
(2)问题10:二进制转换为正确的十进制是如何得到的?
因为后面GPT又指出可以使用Java编程语言进行转换,代码如下,我为什么那么相信编程的结果,因为我相信程序是不会骗人的。
public class test3 {
public static void main(String[] args) throws IOException {
String binaryString = "1100111011010010";
int decimal = Integer.parseInt(binaryString, 2);
System.out.println("十进制数是: " + decimal);
}
}
输出结果就是52946。