在计算机内存上操作数据一般操作系统都通过了一些系统调用接口,直接使用这些接口就可以;相比在磁盘里面操作要是简单的多,程序运行的时候大部分都是从外存加载二进制文件到主存上执行,主存的操作相当简单,在编程语言实现上只需要分配和管理就可以,操作系统提供了虚拟内存来控制。而在磁盘上访问内容我们就需要通过偏移量来访问了,数据是序列化二进制存储在磁盘上的,如何把主存中数据结构存储到磁盘上有相应的存储布局呢?这时本文要介绍一些实现磁盘存储和数据编码原理。
在磁盘上的数据布局远比内存上重要,内存速度虽然比磁盘读写数据要快,但是容量会受到限制并且容量可能是磁盘容量的千分之一,并且断电数据会丢失,这时我们就需要通过能持久化的磁盘设备来保存数据,但磁盘的速度又比内存操作慢,这时存储数据我们就要设计一个高效并且合理的数据结构做磁盘的数据布局。这也是之前前面几篇文章要介绍的LSM-Tree系列的数据持久化组织管理方式,磁盘能存储更多的数据但是怎么存储?怎么编码?如何快速获取某部分的数据?设计什么样的数据结构能提供CPU缓存行利用率?怎么能高速正反序列化数据内容?
字节序
日常开发的时候都使用的着编程语言所通过的类型系统进行开发的,我不需要关系怎么存储的,但是如果需要把数据在网络上进行传输和磁盘上持久化存储这时就需要将其序列化成特定编码的格式。数据记录有字符串、整型、浮点型、布尔型之类的原始类型以及它们的组合构成的,但是要在网络和磁盘上使用的话,就需要转成字节序列了,网络发送数据要进行传输就需要序列化数据,磁盘写入也要序列化数据,然后读取需要反序列化数据。字节序编码方式决定了一组字节的先后顺序是怎么样子的,计算机字节序编码方式有两种分别为Big-Endian
和Little-Endian
进行的,具体差异如下图:
大端字节序: 是从最高有效字节开始,从高位到低位依次排列,换句话说高位字节在前,低位字节在后,这是人类读写数值的方法。
小端字节序: 从最底有效字节开始,从低位到高位依次排序,低位字节在前,高位字节在后,这时符合计算机底层电路运作的方法。
为什么要有字节序?这就设计到计算机底层一些电路原理了,我们人类大部分阅读一篇文章都是从左往右阅读,当然也有少数国家的人阅读是从右往左阅读;而换到计算机上,计算机处理数据电路先处理低位字节,效率比较高,因为计算都是从低位开始的。所以,计算机的内部处理都是小端字节序;但是人类还是习惯读写大端字节序,因为符合人类阅读习惯,如果想让计算机运行够快还是推荐使用小端字节序,但是注意网络数据流采用的大端序。
数据布局
所有的原始类型都是固定大小的,然后怎么用原始数据类型组建一个高级的数据类型,可能也是相应类型的数组。这样组成的类型怎么序列化?通过我们可以将其内存计算字节序的大小,然后将大小放到某指定的位置,下次读取时可以根据这个位置读取对应数据反序列化处理;很多语言对应内存对齐概念,保存Java也尝试往原生的数据类型转型,可以参考这篇文章Project Java Valhalla。
像布尔类型和一些特殊标记位可以使用0和1直接代替,没有必要使用用很长的数据类型进行编码,例如枚举类型可以使用一个简单是整数类型,标志位典型应用就是TCP包头的特殊的标志位,可以表示一个数据包的类型用途是什么,当数据到达接受端可以提取对应的标志位判断数据包类型做出相应的动作,具体查看这篇文章When to use TCP。
在开发者过程中我们可能需要定义一些数据结构或者Java中的class
,对应数据库中可能就是一张表结构,但是这个表结构如何存储在磁盘上,某些数据是在运行时产生长度不确定,但是知道其类型,例如文档类型NOSQL类型种类很多,如何将这些数据存储磁盘上持久化?设计一个Schema对应结构进行编码存储,具体实例可以查看前面我的一篇文章Bitcask记录编码组织方式。
字节序只是计算机运算的时候需要的地址访问组织方式,具体也看CPU的设计,网络是一个例外使用的大端的方式,至于用什么用方式没有那么重要,主要是在读取数据时要使用正确的方式进行访问还原其值。