系列文章目录
文章目录
2.二进制
2.1整数的二进制表示
对于一个整数,我们日常使用的都是十进制,将十进制转换成二进制,就称作是机器数,但是在转换成二进制的过程出现了一个问题,负数怎么表示?
一般对于有符号整数,我们会在最高位置一个符号位,0表示正数,1表示负数,故据此推理1的二进制表示(假设为byte)0000 0001,-1的二进制表示 1000 0001,而这种编码方式就成为原码
这里就发现了又一个问题,1 + (-1)= 00000001 + 10000001 != 0,在计算上这种直接变动符号位的做法显然有问题,故而我们提出了一种新的编码方式来表示,也就是补码
对于补码的计算,其实就是一个公式的事情,但是这里为了更深刻的理解,我们可以直观的来推导以下,现在无非想要一种既能区分正负,又能保证计算正确的编码方式,即使我说今天 -1 表示成111111111这种样式,只要能保证既能区分开每一个数,又能计算正确那也是可以这么规定的,但是显然这样规定两个要求都不能符合
那么我们就来找怎样符合?(-1)本质上可以看成 0 - 1,这样就能保证计算正确,那么 0 - 1又怎么表示?
变成二进制 0 - 1 = 0000 0000 - 0000 0001,然后我们可以将0000 0000拆开,0000 0000 = 1111 1111 + 0000 0001,高位的1溢出,那么-1 就可以表示成
1111 1111 - 0000 0001 + 0000 0001,用 1111 1111减去正数本身,就是对这个数取反,而最后加上0000 0001,就是取反之后再+1,那么至此其实就可以推导出一个负数的补码其实就是它的原码取反再加1,因此-1的补码表示就是 1111 1111
那么在理解了补码之后,其实就可以理解对于java中计算结果出现正数相加或相乘等于负数的情况了,如两个byte相加 127 + 1:
127 -> 0 1111 1111
1 -> 0 0000 0001
+ ------------------
-128 -> 1 0000 0000
这就是超过了计数的范围,符号位改变了
2.2整数的十六进制表示
一句话概括就是,二进制写起来太长,因此我们需要通过更高级别的进制用以方便表示和书写,但是又要很快捷的同二进制做转换,故而我们选择了十六进制
因为二进制 与 十六进制的转换非常简单
如 1010 0001 1111 0101 转换成十六进制就是A1F5,每四个二进制位转换成一个十六进制位,最后拼起来就可以,不需要复杂的计算
在java里,如果我们需要直接赋值的方式表示二进制或十六进制的数可以通过0x和0b的标识,如
int a = 0x7B;// 表示a为十六进制下的7B
int b = 0b11001;// 表示b为二进制下的11001
同时,我们也可以通过包装类直接查看整数的二进制和十六进制表示,如:
System.out.println(Integer.toBinaryString(a));
System.out.println(Integer.toHexString(a));
System.out.println(Long.toBinaryString(a));
System.out.println(Long.toHexString(a));
2.3浮点数的二进制表示
首先引入一个问题
float f = 0.1f * 0.1f;
System.out.println(f);
这个计算机结果,很显然应该等于0.01,但是实际运算结果为0.010000001,显然计算”出错了“
- 为什么会出错?
一句话概括,表达不精确,计算过程都是定好的不会出错,问题其实出在计算机无法精确的表示很多浮点数,比如0.1,计算机只能保存一个尽可能接近0.1但是又不等于0.1的一个数,这又是为什么?
其实很好理解,我们觉得0.1很容易表示,是因为我们站在了十进制的角度,十进制里依然有不少不能精确表示的数啊,如1/3,在我们在十进制里表示数依然只能表示10 ^ n类似的数的组合,如 12.345其实是1 * 10^1 + 2 * 10^0 + 3 * 10^-1 + 4 * 10^-2 + 5 * 10^-3 的组合,因此在二进制的世界里,也只能准确表示由2^n 表示的数的组合,显然我们没法由一个准确的组合表示0.1这个数
具体的浮点数的二进制表示其实也很好理解,无非是计算机中的”科学计数法“,一般会用 m x (2 ^ e)这种方式来进行表示,而计算机则会用一种特殊的规则存储尾码m和阶码e以及这个浮点数的符号位,而这个标准就是国际通用的IEEE 754标准,其定义了两种格式,一种是32位浮点数,对应java的float;一种是64位浮点数,对应java的double,其具体规则可以自行查证,计算机组成原理这门专业课将会涉及
2.4字符的编码与乱码
2.4.0什么是编码
对于字符这种符号化的表示,计算机显然只能通过数字与字符对应的方式来记录字符,而从字符到计算机数字存储的过程就是编码,从计算机存储的数字到字符的过程就是解码,而这中间的对应关系,或者说映射表则是编码集,编码集是由人自行定义规定的,因此就会存在不同编码集的问题,而当你试图用A编码集的方式去解码B编码集编码的字符时,就会发现牛头不对马嘴,这就出现了乱码。故而,在碰到乱码时,关键点就是找到这里的A与B,要么你试图用B编码集的方式去解码,要么你试图对之前B编码集编码好的字符重新用A编码,钥匙打不开锁,要么换锁要么换钥匙,很简单的想法
2.4.1常见非Unicode编码
- ASCII码
这个编码集是一个利好英文字符的编码集,全称是American Standard Code for Information Interchange,美国信息互换标准代码
我们使用byte表示了127个包含英文字母和一些常用符号的集合,具体对应可以自行查阅
- GB2312
这个是第一个出现的中文编码集,很显然中文汉字的数量要远超英文字母,因此中文编码集需要用两个字节来表示,这个字符集中大约包含了7000个常用汉字和一些繁体字,不仅对于GB2312,几乎其他所有编码集为了和最开始出现的ASCII编码集作区分,会置最高位为1(ASCII码最高位为0)
- GBK
GBK是建立在GB2312的基础上,向下兼容GB2312的一个编码集,GBK新增了14000多个汉字
- GB18030
同样是建立在GBK之上,又新增了55000多个字符,其中就包含了很多少数民族的字符以及中日韩统一字符,对于GB18030,我们采用变长编码,有的字符是两个字节,有的是四个,具体方式可以自行查阅
- Big5
这个编码集是针对繁体中文的,广泛用于我国台湾香港等地区
2.4.2Unicode编码
对于上面介绍的非unicode编码,一个很明显的问题就是各个字符集之间尤其多个国家的字符集之间兼容性几乎为0,因此Unicode就做了一件事,它将世界上所有的字符都分配了一个独属于这个字符的编号,请注意unicode只是分配了一个编号,而编号究竟怎么对应上二进制,就此产生了以下的不同方案
- UTF-32
这个最简单,就是字符编号的二进制形式表示,4个字节,缺点明显,每个字符都使用4个字节,浪费空间
- UTF-16
UTF-16采用变长字节
1)编号在U+0000 - U+FFFF的字符(常用字符集),直接使用两个字节表示
2)字符值在U+10000 - U+10FFFF的字符(增补字符集),用4个字节表示
缺点仍然是浪费空间,对于美国和西欧国家来说,4个字节太奢侈了
- UTF-8
这也是coding里最常用的编码方式,utf8依然是使用变长字节,只是灭个字符使用的字节个数与Unicode编号的大小有关,编号小使用字节少,编号大使用字节多,使用的字节个数为1~4不等
UTF8不同于UTF-32和UTF16的点是,UTF-8兼容ASCII码,对于大部分中文而言,中文字符一般占用3个字节