本意是介绍Windows系统用到的一种数据存储结构,可以方便扩展和支持不同软件版本之间的兼容。数据存储结构本身很简单,当然不局限于Windows平台使用,所有平台,所有编程语言都可以用。查阅到的相关信息,我也不想放弃,所以就按下面的内容进行了组织:第一点,相关概念,特别是数据存储结构的概念;第二点,数据库便于扩展和兼容吗;第三点,XML便于扩展和兼容吗;第四点,JSON便于扩展和兼容吗;最后,主角Windows系统中用到的数据存储结构,及与TLV对比。
数据存储结构
计算机程序的一个主要功能是处理数据。数据是客观存在的信息,数据本身有自己的规律,人们为了便于理解和处理,提取/获取数据并进行一定的抽象,形成数据的逻辑结构(或者叫业务数据),用计算机进行处理,就要转换成计算机程序能够识别和处理的形式,在计算机中表现为数据存储结构, 在此基础上进行数据运算。
计算机中数据的存储结构,即多个数据元素在计算机中如何存储和表示。按照数据元素之间的关系,有顺序存储、链式存储、索引存储、哈希存储四种表示方式。不管存储到内存空间,还是flash空间,或者文件形式存于硬盘(文件系统屏蔽了存放细节),本质都一样。
注:下面的“结点”可以理解为逻辑上的数据“元素”在计算机中的表示,“结点”和“元素”可以互换,不一般不做区分。
顺序存储,数据元素顺序存放,每个结点只有一个元素,储位置反映数据元素间的逻辑关系,逻辑上相邻的结点存储在物理位置上相邻的存储单元里。顺序存储的典型例子,就是代码中无数次出现的数组;代码中struct和类对象等定义的数据也算;栈上的数据也算。
链式存储,每个结点除了包含数据元素信息外还包含一组指针字段,指针反映数据元素间的逻辑关系,该方法不要求逻辑上相邻的结点在物理位置上亦相邻。单链表,双链表等都是这类用法,实际使用到的例子也很多,见使用自定义堆,代替系统缺省堆?中链表使用。
索引存储,除了存储数据元素,还需要针对数据元素建立一个索引表,索引项一般为(关键字、结点地址),索引表一般为顺序存储,我理解数据本身可以连续存储,也可以非连续存储。索引概念在数据库和文件系统大量用到,但这种思想是必须要掌握的。
哈希(散列)存储,基本思想是根据结点的关键字直接计算出该结点的存储地址,即通过哈希函数解决冲突的方法,将关键字散列在连续的有限的地址空间内,并将哈希函数的值作为该数据元素的存储地址。
注意,索引存储和哈希存储中,“关键字”引入了数据元素的内容,索引存储和哈希存储可以理解为本质上是同一个东西,即关键字和结点间的数学映射;关键字这一数据元素内容,在顺序存储和链式存储中没有的。
数据库便于扩展和兼容吗?
数据库便于扩展和兼容吗?凭我的个人经验,回答不了。综合考虑业务需求、产品性能、性价比等因素,人们会选择不同类型的数据库。总体来说,数据库本身有点太重量级了,要做扩展和兼容,需要做的工作比较多,相关的工作应该交给专业人员去评估和实施。
我用过的主要是关系型数据库,oracle ERP系统中的数据库,MySql数据库;SQL Server的数据库本身没怎么用,但做过SQLServer数据库调优的项目(即根据一些最佳实践的规则,判断SQL Server是否满足,给出调优建议)。
数据库分为传统关系型数据库和众多非关系型NoSQL数据库。关系型数据库的特点是“标准统一”:使用统一的SQL语言,坚持统一的ACID标准,可以兼容和支持统一的开发模式。ACID,是指数据库管理系统(DBMS)在写入或更新资料的过程中,为保证事务(transaction)是正确可靠的,所必须具备的四个特性:原子性(atomicity,或称不可分割性)、一致性(consistency)、隔离性(isolation,又称独立性)、持久性(durability)。
NoSQL非关系型数据库,它弥补了SQL数据库在一些场景下的不足,便于存储非结构化数据 同时具有高性能、高可用性、高伸缩性的特点 :不支持ACID,仅保证“最终一致性”,没有通用的查询语言,细分类型众多,彼此间无法兼容。
MySql底层数据结构就是用的B+树,详情参考:一文吃透MySql的底层数据结构(满满都是干货)
WBEM在windows上的实现WMI(Windows Management Instrumentation)也用到了类似的思想,也支持类似于SQL的查询语句。
XML便于扩展和兼容吗
XML便于扩展和兼容吗?The short answer is yes。
使用XML的例子很多,VisualStudio相关的.sln解决方案文件,.proj项目文件,manifest文件等等,同一XML文件可以被不同VisualStudio加载;基于Eclipse的集成环境里面也大量用到XML。
XML就是字符串,有很强的跨平台可移植性,并且数据无需转换, XML还有一大优点就是它非常类似对象数据结构。 XML的基本语法如下:
xml文档由元素组成, XML 元素使用 XML标签进行定义,所有 XML元素都须有关闭标签。
下面是来自VS项目文件的xml元素示例(其中<CharacterSet>为标签,xml标签大小写敏感):
<CharacterSet>Unicode</CharacterSet>
XML元素可以有属性(属性名称和属性值成对),属性值必须加引号,下面是来自VS项目文件的示例(属性名为Label,属性值为“Shared”):
<ImportGroup Label="Shared"></ImportGroup>
XML元素可以嵌套,即存在父子关系,同样来自VS的示例:
<PropertyGroup>
<ConfigurationType>Application</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
XML文档必须有一个元素是所有其他元素的父元素。该元素称为根元素。
基于XML的SOAP协议,SOAP 是用于访问网络服务的协议,可使应用程序在 HTTP 之上进行信息交换。SOAP 是微软 .net 架构的关键元素。SOAP 提供了一种标准的方法,使得运行在不同的操作系统并使用不同的技术和编程语言的应用程序可以互相进行通信。我是这样理解SOAP:定义了消息格式(可以理解为函数调用,使用外部提供的服务),以XML方式表示该消息,在Intelnet上(HTTP/HTTPS)传输,完成应用程序间的通信。因为基于XML,所以是独立于平台、独立于语言,简单且便于扩展。
JSON便于扩展和兼容吗
JSON(JavaScript Object Notation,JavaScript 对象标记法) 是一种轻量级的数据交换格式,JSON采用完全独立于语言的文本格式,易于人阅读和编写,同时也易于机器解析和生成;JSON更接近面向对象的概念,本来名字就含有“对象”两个字嘛。
同XML一样,json本质上就是一个字符串。JSON的基本语法如下:
JSON是一个字符串表示的对象,JSON中的"名称:值"二元组中的名称为双引号表示的字符串,值(value)可以是双引号括起来的字符串(string)、数值(number)、true、false、 null、对象(object)或者数组(array),且可以嵌套。
对象,用花括号{}表示,对象是一个无序的“名称/值对”的集合,一个对象以“{”开始,以“}”结束。
数组,使用方括号[]表示,数组是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。
字符串类型,必须使用双引号。
整形、浮点型、布尔类型还有null类型。
JSON的示例:
{"name":"thomas","age":18}
JSON格式,以前IA64的服务器上用到过,服务器上的设备资源和状态信息,以json格式通过http供外部访问(RESTfull)。
Windows用到的一种数据结构,VS. TLV
其实主角是下面这个数据结构,windows DDK中头文件winbio_types.h中有如下结构:
//The following gives thelocation and size of a block
//in a BIR. The offset is measured from the beginning of
//the WINBIO_BIR structure
typedef struct _WINBIO_BIR_DATA
{
ULONG Size;
ULONG Offset;
}WINBIO_BIR_DATA;
typedef struct _WINBIO_BIR
{
WINBIO_BIR_DATA HeaderBlock;
WINBIO_BIR_DATA StandardDataBlock;
WINBIO_BIR_DATA VendorDataBlock;
WINBIO_BIR_DATA SignatureDataBlock;
}WINBIO_BIR;
注意:这里WINBIO_BIR结构中的4个数据块的偏移位置Offset都是相对于整个WINBIO_BIR结构的起始地址;其中Block的size可以为0,即没有实际的数据;紧接着WINBIO_BIR对象的连续地址空间依次存放了block的数据。这样不同软件版本之间解析数据的方式相同,每个版本对不同block的使用方式可能有区别。
基于上面的内容,我们可以定义下面的数据结构:
typedef struct _MY_DATA_BLOCK_
{
uint32_t size;
uint32_t offset;
}MY_DATA_BLOCK;
typedef struct _MY_DATA_
{
uint32_t total_size; //size of this struct and all the apending data
//all the block offset could be from the beginning of MY_DATA, from the
//ending of MY_DATA, or else like beginning of the first block data1
MY_DATA_BLOCK data1; //data for business,
MY_DATA_BLOCK data2; //data for business
MY_DATA_BLOCK data3; //data for business
MY_DATA_BLOCK reserved1; //reserved for future use
MY_DATA_BLOCK reserved2;//reserved for future use
MY_DATA_BLOCK reserved3;//reserved for future use
}MY_DATA;
不同软件版本兼容的场景:
软件版本1使用上述结构,用到data1/data2/data3前3个block,并将数据存到文件系统。软件版本2增加了一个功能,把reserved1也用上了,更新数据并存到文件系统。更新前的数据,软件版本1和2都可以访问(软件版本2本来就要向下兼容)。更新后的数据,软件版本1和2都可以正常访问。
这种方法的好处,解析的代码简单(相对后面的TLV),适用于业务数据相对比较固定,数据种类不是特别多的情况。
TLV(Type-Length-Value),个人觉得解析起来会麻烦一点(如果要考虑TLV嵌套的话,如果说前面那个结构是一维的,嵌套的TLV这是3D的),适合于很多数据种类的场景,当然最好对是否嵌套,嵌套的层级情况实现有一个清晰的定义。
所谓嵌套,即TLV中的V,又是一个TLV。TLV结构体定义如下:
typedef struct _ST_TLV_
{
uint32_t type;
uint32_t len;
uint8_t value[0];
}ST_TLV;
用到过的场景,网络数据中,产线工具向驱动发送的数据中,FIDO(一种安全认证认证体系)协议规定中(后续介绍一点这里的C++编码工作)。
【2022年5月7日 成都】