大概在06年,我看到了一本书,叫做<<深入Java虚拟机>>。

在周志明那本神书《深入理解Java虚拟机》出来之前,这本书应该是唯一一本讲JVM的书, 对Java class文件格式,执行引擎讲得特别详细。

我看了一遍看完了以后就“热血沸腾”:原来Java 的class 文件格式是这样的啊!也许我也可以写一个JVM了!

于是我就开始琢磨, 先写程序去解析.class文件, 然后写一个小的执行引擎,去执行那些字节码。 

作为第一步,我要写个小程序去读取硬盘上的.class文件,然后看看它的头四个字节是不是著名的魔数:“CAFE BABE

第一个字节实际的输出结果让我大跌眼镜: -54 !

这是怎么回事呢?说好的CA呢?

这个问题让我想了很久,后来终于想明白了,CA 是16进制字符,变成二进制就是 1100 1010 , Java内部使用补码表示数字的,把1100 1010看作二进制补码,它对应的十进制可不就是-54嘛!

原来都是补码惹的祸!

那计算机为什么用补码呢?一个重要的原因就是简化电路的设计:把整数的加减法统统变成加法来运算。

比如一个4位的计算机,能表示的数字是 0 ~ 15 

在做加法的时候非常简单: 

8+3(十进制) = 1000 + 0011 = 1011 = 11 (十进制)

我们可以设计一套简单的数字电路(与门,或门等)来实现这个加法运算。 

但是减法怎么办呢?难道再设计另外一套电路?这就浪费了。

于是人们就引入一个‘补数 '的概念, 例如 3的补数 是 13, 4的补数是12,  5 的补数是11......

当你计算7减去3 的时候, 可以变成 7加上3 的补数, 即 7 + 13 : 

7-3 = 0111- 0011 (二进制3) = 0111 + 1101(二进制13) = 10100  

10101已经是5位了,溢出了, 去掉最高位是 0100 ,就是十进制4 了。 

那二进制的“补数”怎么得出呢?人们想出了一个异常简单, 又特别适合计算机电路的算法, 对二进制数的所有位取反, 然后加1

3 -> 0011 -> 1100 (取反) -> 1101 (加1)

这种方法是不是很神奇?只用一套加法电路和补码电路就可以高效地实现加法和减法了。 

等等,负数怎么办?我们手写的时候,可以在一个数前加个负号, 就可以表示,但是对计算机来说,它必须得用某一位来表达正负,比如用这种方法:

最高位的0 表示整数,1 表示负数。 

真正有效的数字只剩下3位了, 正数的范围是从1 到7 ,负数的范围从 -1到-7 ,不过这里出现了两个零!一个正0 , 一个负0 , 这肯定不好!

改进一下,把那个负0 认为是-8吧,这样还能多表示一个数字!

这样,数字的范围变成了从[-8 ,7]  。 

推广一下,在编程语言中,对于n位整数,它的取值范围是[-2^(n-1) ~ 2^(n-1) - 1]  ,正数要比负数少一个。 

但是之前的减法变加法的规则还能用吗?我们试试:

7-4  = 7+(-4) = 0111 +1100 = 10011 -> 0011 (舍掉最高位) = 3, 正确!

4-7 = 4+(-7) = 0100 + 1001 = 1101 = -3  ,正确!

注意,用这种办法,连符号位都参与了运算。 

总结一下, 在计算机内部,是使用补码来表示二进制数, 如果是一个正数, 补码就是它本身,  如果是个负数, 需要把除了符号位之外的二进制数进行取反加一的操作。 

回到我们开头的问题, 如何正确地把第一个字节变成16进制字符串“CA”,然后展示出来呢?用这个函数就可以了:

后记:本文首发于我的知识星球“码农翻身”的“硬技能”专栏,在那里我主要讲“组成原理”,“编程语言”,“操作系统”,“计算机网络”,“数据库”,这些都是让你起飞的计算机基础知识

不仅如此,我也邀请好友海飞开了一个“软技能”专栏,叫做“非说不可”,海飞是IBM资深经理,有15年以上工作经验。他也是极客时间《面试现场》专栏作者,对于“面试”,“领导力”,“学习成长”,“个人管理”等软技能方面可以说是信手拈来。

给大家看看已经发过的文章:

你的综合能力 =(硬技能) X (软技能)

欢迎加入我们的知识星球“码农翻身”!你可以向我和海飞任意提问, 我们知无不言,言无不尽,这两个工作经验都在15年+的老家伙,分享宝贵经验,不敢说让你有多大的提升,一定会让你少走几年弯路。 

星球原价149元, 使用下面的优惠券,仅需99元即可加入,超值!

优惠券仅限100人,需要的要抓紧了!

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐