彻底理解从二进制到序列化跨平台

Posted 乐斯

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了彻底理解从二进制到序列化跨平台相关的知识,希望对你有一定的参考价值。

首先我们先来看下b(英文:bit,中文:比特/位)、B(英文:Byte,中文:字节) 和字长的定义和换算。

换算 :

1B (Byte) = 8b (bit)

定义:

位 :二进制位简称“位”,是二进制记数系统中表示小于2的整数的符号,一般用1或 0表示,是具有相等概率的两种状态中的一种,二进制位的位数可表示一个机器字的字长,一个二进制位包含的信息量称为一比特(BIT,Binary digit)。

字节 :在计算机中,把 8 位聚在一起的二进制数称为一个字节(byte),即 1字节(byte)= 8 位(bit)。

字 :现代计算机的机器字长一般都是8位的整数倍,如16位、32位、64位和128位等,即字长分别为2个字节、4个字节、16个字节和16个字节,所以也可以用“字节”来表示机器字长;对于64位操作系统的CPU来说,字长就是64位,而字节是固定8位,所以在64位计算机中,一个字长所占的字节数为8。

首先大家对于一个字节8位有没有疑惑?为什么不是6位、7位或10位呢?可能大部分的人都觉得这不是理所当然的么,就和一日三餐一样,还需要怀疑吗?那些看起来理所当然的事情,其实远不是我们想象的那样简单,经常思考和探究这些看似常识性的问题,有助于我们养成"批判性思维"。

大家在大学期间应该都学习过高等数学中的极限、微积分,单单从概念的角度去理解极限、微积分,想必大多数同学都感觉很头疼,那么当我们将极限、微积分与伽利略、牛顿-莱布尼茨等重要人物联系起来,引入了历史这一重要因素,一切将会变得更加灵性,鲜活;再对极限、微积分的理解将会更加深刻。我们一定要承认,任何知识、应用的产生一定是有需求、有场景、有客观因素存在的,不可能无中生有就蹦出来这样一个概念。

了解历史就会有全局观,那么位、机器码、字节、字的理解我们同样以历史的角度出发来为大家解惑,以下我引用了他人的一个回答再加上自己的看法。

---------------------------------------引用开始--------------------------------------------

“所谓字节,原意就是用来表示一个完整的字符的。最初的计算机性能和存储容量都比较差,所以普遍采用4位BCD编码(这个编码出现比计算机还早,最早是用在打孔卡上的)。BCD编码表示数字还可以,但表示字母或符号就很不好用,需要用多个编码来表示。后来又演变出6位的BCD编码(BCDIC),以及至今仍在广泛使用的7位ASCII编码。不过最终决定字节大小的,是大名鼎鼎的System/360。当时IBM为System/360设计了一套8位EBCDIC编码,涵盖了数字、大小写字母和大部分常用符号,同时又兼容广泛用于打孔卡的6位BCDIC编码。System/360很成功,也奠定了字符存储单位采用8位长度的基础,这就是1字节=8位的由来。”

各位看官看到这里是否觉得IBM System/360很牛,是否想起了大牛弗雷德里克·布鲁克斯和他的《人月神话》,那么继续往下追寻,

弗雷德里克·布鲁克斯在哈佛大学取得博士学位以后,进入IBM公司设立在纽约波凯普茜(Poughkeepsie,NewYork)的实验室当工程师。这个实验室从20世纪50年代到80年代一直是IBM公司开发计算机的中心。布鲁克斯在这里参加了Harvest和STRETCH计算机的开发,任体系结构设计师。这两个型号的计算机都引入了一些新技术,在20世纪50年代后期至60年代初期有很大影响,尤其是STRETCH计算机,当前已成标准的8个二进制位的“字节”(byte)就是由布赫霍尔兹(WernerBuchholz)提出,在STRETCH上首次采用的。

原文链接:https://blog.csdn.net/bookasw...

什么是机器码

https://www.cnblogs.com/qiumi...

机器码

机器码(machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑的CPU可直接解读的数据。

通常意义上来理解的话,机器码就是计算机可以直接执行,并且执行速度最快的代码。

用机器语言编写程序,编程人员要首先熟记所用计算机的全部指令代码和代码的涵义。手编程序时,程序员得自己处理每条指令和每一数据的存储分配和输入输出,还得记住编程过程中每步所使用的工作单元处在何种状态。这是一件十分繁琐的工作,编写程序花费的时间往往是实际运行时间的几十倍或几百倍。而且,编出的程序全是些0和1的指令代码,直观性差,还容易出错。现在,除了计算机生产厂家的专业人员外,绝大多数的程序员已经不再去学习机器语言了。

机器语言是微处理器理解和使用的,用于控制它的操作二进制代码。
8086到Pentium的机器语言指令长度可以从1字节到13字节。
尽管机器语言好像是很复杂的,然而它是有规律的。
存在着多至100000种机器语言的指令。这意味着不能把这些种类全部列出来。
总结:机器码是电脑CPU直接读取运行的机器指令,运行速度最快,但是非常晦涩难懂,也比较难编写,一般从业人员接触不到。

什么是字节码

字节码

字节码(Bytecode)是一种包含执行程序、由一序列 op 代码/数据对 组成的二进制文件。字节码是一种中间码,它比机器码更抽象,需要直译器转译后才能成为机器码的中间代码。

这里可以类比我们中学学的鸡兔同笼,这个问题是有一百多种解法的,每一种具体的解决就如同特定机器上的机器码,但是当换个问题,比如蜈蚣、鸡,具体的某一个解法可能就不生效了,而当我们学了方程后,它比具体的解法更抽象,而且它的适用更加广了,无论你怎么换,它都可以简单的套公式得出结果。

通常情况下它是已经经过编译,但与特定机器码无关。字节码通常不像源码一样可以让人阅读,而是编码后的数值常量、引用、指令等构成的序列。

字节码主要为了实现特定软件运行和软件环境、与硬件环境无关。字节码的实现方式是通过编译器和虚拟机器。编译器将源码编译成字节码,特定平台上的虚拟机器将字节码转译为可以直接执行的指令。字节码的典型应用为Java bytecode。

字节码在运行时通过JVM(JAVA虚拟机)做一次转换生成机器指令,因此能够更好的跨平台运行。

理论上说计算机运行的程序都可以被反汇编的。但java这类程序就不一样了~~~java程序运行在java虚拟机(jvm)里由java虚拟机和操作系统进行交互操作。java源文件在编译成*.class时,java源代码被编译成类似计算机汇编代码的java汇编代码(也就是 Java字节码),这时java虚拟机就像cpu一样可以“运行java汇编代码”所以java的类并不是计算机的机器指令而是由jvm解译成机器指令运行的。

本质上,字节码定义了虚拟处理器的操作码,而汇编则由物理处理器的操作码组成。

java字节码和汇编指令_汇编代码和字节码有什么区别?
https://blog.csdn.net/weixin_...

Java字节码跟真正汇编的比较
https://link.zhihu.com/?targe...

CPU 执行程序的秘密
https://baijiahao.baidu.com/s...

Java虚拟机是如何运行字节码的
https://lilu.org.cn/2020/07/2...

java 虚拟机源码_Java虚拟机:源码到机器码
https://blog.csdn.net/weixin_...

JVM虚拟机种类_weishuai528的博客-CSDN博客_jvm类型
https://blog.csdn.net/weishua...

JDK、JRE、JVM和javac的关系_程序员进阶之路-CSDN博客_javac在jdk还是jre中
https://blog.csdn.net/qq_3658...

字节码和汇编语言是一回事吗?
http://www.voidcn.com/article...

总结:机器码是用二进制代码编成的,但二进制代码不一定就是机器码,字节码是一种中间状态(中间码)的二进制代码(文件)。需要直译器转译后才能成为机器码。

java编译器要将java代码转为Unicode字节码(byte)主要原因是为了实现跨平台,因为在不同的机器字长的机器上表示相同的数据类型数据需要的字节数是不同的,而一个字节是固定由8位二进制数表示,如果我们直接编译转为bit,等于是根据编译程序的机器环境已经确定了表示该数据类型需要的字节数,这样的后果是可能会造成所给的字节数小于程序目标使用机器实际上表示该数据类型的需要而缩小该数据类型的数值表示范围,从而导致数值越界造成程序出现Bug。因为不同机器可识别的机器语言是不同的,而JVM就像Java程序和机器硬件之间的一座桥梁,前面得到的Unicode字节码(byte)组成的.class文件根据机器硬件环境来进行解释,从而使应用程序能在各种平台上运行一致,但这是以牺牲部分效率来实现的。

---------------------------------------引用结束--------------------------------------------

我们做任何一件事情,当然是在能满足需求的前提下,花费的代价越小越好,大家应该要达成这样一个共识。那么从上面最直接的我们就可以知道,最初使用这些概念的是美国人,那么只需要满足美国人的需求就行,"需要存储的信息越来越多"意思着需要更高效的存储方式,也就是在精度不低于要求的情况下,用更少的数据单元存储更多的数据,而8位是刚刚好能满足的。

1、0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10 和13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。

2、32~126(共95个)是字符(32是空格),其中48~57为0到9十个阿拉伯数字。

3、65~90为26个大写英文字母,97~122号为26个小写英文字母,其余为一些标点符号、运算符号等。

那么在英语中,用128(2^7)个符号编码便可以表示所有,即7位就能编码美国人会用到的所有字符。

到这时就出现了字节的概念,那为什么字节要用8位表示呢?因为除了英文字母,世界上还有很多的语言字符,光汉字就有10万多个。聪明的美国人就在前面加了一个bit作为扩充位。当这一最高符号位为1时,表示扩展字符集,此时系统会将该字节和其下一字节合并解释,并根据当前使用的字符集显示正确的文字。

而当最高位为0时,就是我们经常说的ASCII编码,ASCII 编码是最简单的西文编码方案。GB2312、GBK、GB18030 是汉字字符编码方案的国家标准,这些标准下,一个汉字要占用两个字节。

到这里我们可以肯定的是最少要用8位来表示一个字节了。那有些人可能会问了,设置这么多编码集多麻烦,直接16位表示一个字节不就简单多了。这是因为计算机发展初期内存十分宝贵,能少用就少用,如果用16bit定义字节的话,一个英语字符就要占用两倍内存,这会造成巨大的浪费。而后来出现的UTF-8编码,一个汉字占3个字节。为什么要出现三个字节的编码呢?两个字节最多编码65535个字符,我们前面说了,光汉字就有10万个,还有其他国家的文字,这两个字节完全不够用啊。

理解了以上的知识点后,我们还需要了解到底什么是Java序列化、反序列化。

一、什么是序列化和反序列化?
序列化:将对象的状态信息转换成可以存储或者传输的二进制格式的过程,在这个过程中二进制格式通常以文件形式来体现。

反序列化:反之,将二级制存储形式转换成对象的过程,就是反序列化了。

二、 JAVA中为什么byte而不是bit?

  1. JAVA序列化将对象转为字节码(byte),为什么不可以直接转为位(bit)?
  2. 为什么java编译器要将java代码转为字节码(byte)?为什么不可以直接转为位(bit)?那样的话计算机不就可以直接读取了么?为什么还需要虚拟机呢?这么做的原因是什么?

1、2这两个问题其实很相似。

大家在刚接触JAVA的时候,应该就已经知道JAVA是可以跨平台(依靠JVM)的编程语言,那我们首先得知道什么是平台,我们把CPU处理器与操作系统的整体叫平台(平台 = CPU+OS,又因为现在主流的操作系统都支持主流的CPU,所以有时也把操作系统称为平台)。

另外我们要知道,编译器负责把Java程序转成class文件(字节码),方便JVM来读取它,JVM是java虚拟机,它是解释器,把class文件中的命令转成某种平台的命令,比如把java命令转成Windows下的命令,直白一点就是,java源文件在不同的操作系统下通过前端编译器生成的class字节码文件是一样的,要不然怎么是“一次编译,到处运行”呢。字节码的设计就是为了充当中间人的角色,javac将源码编译成字节码,然后不同操作系统版本的jvm识别字节码 ,然后翻译成相应机器码(二进制),字节码文件提供了跨平台运行的特性。

机器码(machine code),学名机器语言指令,有时也被称为原生码(Native Code),是电脑的CPU可直接解读的数据。

class字节码文件在不同的操作系统下运行时,被JVM解释器翻译成操作系统的机器码(二进制)是不一样的。

字节码和机器码是两回事,不同架构的机器的机器码(二进制)是不同的。

比如32位处理器就是一次只能处理32位,也就是4个字节的数据,而64位处理器一次就能处理64位,即8个字节的数据。如果我们将总长128位的指令分别按照16位、32位、64位为单位进行编辑的话:旧的16位处理器,比如Intel 80286 CPU需要8个指令,32位的处理器需要4个指令,而64位处理器则只要两个指令,显然,在工作频率相同的情况下,64位处理器的处理速度会比16位、32位的更快。而且除了运算能力之外,与32位处理器相比,64位处理器的优势还体现在系统对内存的控制上。由于地址使用的是特殊的整数,而64位处理器的一个ALU(算术逻辑运算器)和寄存器可以处理更大的整数,也就是更大的地址。传统32位处理器的寻址空间最大为4GB,使得很多需要大容量内存的数据处理程序在这时都会显得捉襟见肘,形成了运行效率的瓶颈。而64位的处理器在理论上则可以达到1800万个TB,1TB等于1024GB,1GB等于1024MB,所以64位的处理器能够彻底解决32位计算系统所遇到的瓶颈现象,速度快人一等,对于那些要求多处理器可扩展性、更大的可寻址内存、视频/音频/三维处理或较高计算准确性的应用程序而言,AMD 64处理器可提供卓越的性能。

以redis为例子来解释序列化,都知道java序列化对象到redis的时候都是转换成byte类型的,数据都是持久化在dump.rdb,比如dump.rdb是在windows上的,那么我们直接将其复制到linux上,一样可以进行数据的完美迁移,因为class字节码对于任何操作系统都是一样的,redis存储字节码,当要进行反序列化的时候不受不同操作系统的影响。

现在我们对1、2这两个问题提出反问,如果直接转为bit的话,那么将会发生什么呢?

假如序列化的时候我们直接序列化成windows的机器码(二进制),比如就序列化完存储到windows上的redis,这时候redis存储的是windows的机器码(二进制),当我们将dump.rdb迁移到linux的时候,由于取出来的时候是windows的机器码,这时候linux压根就不理解windows的机器码(二进制),无法逆推到字节码,更别说反序列化了。

假如我们将java程序直接编译成windows的机器码(二进制),这时候在windows系统上运行起来是没问题的,那当我们想在linux上运行程序的时候,只能再指定编译成linux的机器码(二进制),在MAC系统上,还需要再次指定编译成MAC的机器码(二进制),想象一下有多麻烦。

三、为什么要序列化和反序列化?
在很多应用场景中都需要用到序列化和反序列化,以保存当前对象的状态信息或者用于传输,例如,在常见的web服务中的持久化session就是一个很好的例子,一般session都是入驻内存的,当服务器异常宕机,内存里的session因为掉电而搽除,当我们设置了session持久化特性时,就会把session保存在硬盘上,这就是序列化,等服务器重启后有可以读取硬盘上这个session文件,还原session对象,这就是反序列化。

另一个需要序列化的场景就是进程间的远程通信,远程通信是以二进制字节流传输的,当需要传输对象的时候,首先在发送端需要将对象序列化为二进制形式方便网络传输,然后在接收端将二进制形式反序列化为java对象。比如早期的RMI,甚至现在流行的RPC通信都用到了序列化和反序列化。

四、如何序列化和反序列化一个对象?
Java提供了一套API方便用户序列化和反序列化。其中需要用到以下几个接口

java.io.Serializable
java.io.Externalizable
ObjectOutputStream
ObjectInputStream

以上是关于彻底理解从二进制到序列化跨平台的主要内容,如果未能解决你的问题,请参考以下文章

如何从二进制文件中读取int型序列

深入理解JAVA I/O系列五:对象序列化

PlatformNotSupportedException:此平台不支持安全二进制序列化

Protobuf Net 和从二进制文件存储/检索数据块

Java IO流--对象流及对象序列化机制的理解

Azure AD Graph:此平台不支持安全二进制序列化