各种配置文件方案的比较

概述

在应用程序的配置文件方面,长期以来我们研发团队内部都是根据各个项目的情况进行制定,也有的时候因为客户方并没有要求,可能是按照开发人员自己的经验、习惯和偏好制定,并没有统一的标准、格式以及代码只对应的接口方便其他人调用。本技巧则对目前存在的一些配置文件方法进行总结和比较。

配置文件的定义

要想总结和比较各种配置文件的方法,首先我们要知道什么是配置?总的来说配置是独立于程序的一些只读变量。配置独立于程序,同一个程序在不同的配置下可以有不同的行为方式;对于客户程序来说,配置应该是只读的,并在程序初始化阶段进行读取。常见的配置方式为:硬代码、启动参数、环境变量、注册表和配置文件。因为配置通常是程序中一些使用的值,所以配置的表现形式一般为键——值模式。需要将配置与数据进行区分,数据一般是程序进行处理的对象,并不会改变程序原来的行为模式,并且数据来源于用户。典型的配置为:各种路径、服务器的地址、程序模式和功能的切换和开关、预定值等;对于NX二次开发还包括一些系统的公差值、特征创建的默认值等等。

文本文件

即一般的文本格式,根据某种特定的配置格式标准或者自定的标准写成的配置文件。事实上现阶段我们研发团队内部使用的基本上都属于这一分类。需要注意的是我们不应该通过文件的后缀来区分文本配置文件的标准,因为像我们团队内部常用的配置文件拓展名“.cfg”文件、".dat"文件并不指定特定的标准格式文本,而仅仅是常常用来表示配置或数据文件的拓展名而已。

NX的dat文件格式

image-20211031011948787

NX的各种环境变量在UGII目录下的ugii_env_ug.dat文件中定义,用户也可以通过ugii_env.dat文件覆盖并修改原来的设置。从文件的注释中我们可以得知格式规定:以”#“为注释标记,#开头的句子将被忽略;定义变量的格式为:”名称=值“。由于这里设置的其实就是环境变量,所以其表现形式也和环境变量一样。

CSV样式文件

image-20211031014031815

CSV(逗号分割值)格式是一种存在广泛认可和通用性的数据格式,虽然不存在标准,但是在RFC 4180中有基础性的描述。它最简单的定义就是数个用逗号分割的值组成的行,并由多个同样格式的行组成的表。值虽然可以为空,但是分割符不能省略,因为需要分割符来定位内容属于哪一列。它可以在第一行设置列名,若值中存在分割符或特殊字符可以使用英文双引号将值包含起来。由于CSV的定义如此简单,因此很多程序在定义配置和数据文件时都会自然地符合CSV的格式,区别可能只是使用了不同的分割符或添加了一些表示注释的符号等等。由于CSV具有记录和表的格式,因此它更适用于作为数据格式而不是配置。事实上大部分数据库也支持通过CSV导入导出数据。

ini文件

image-20211031024029766

ini文件是Initialization File的缩写,即初始化文件,是Windows系统中广泛应用的系统配置文件格式。由于是系统使用的格式,因此有更有公认性,WinAPI中也有相应的函数进行读写,不少软件也借用了这个格式。标准中包括:使用”[Name]“来定义节,可以对不同的配置进行分组;使用行首分号作为行注释;配置使用”键=值“的格式。

其他格式

如本研发团队内使用的cfg配置文件类,其中定义了作为注释行的标记”//“,定义了用两个行分割的配置内容区域,但其他方面则并没有做限定。开始和结束标识虽然建议为XX_START,XX_END字样,但是其实可以输入任意字符串。内容提供了作为CSV样式的解析方法,但是开发人员也可以自行拟定解析方法。

此类文本配置文件的优点是修改方便,只需要稍加说明,用户就可以自行用文本编辑器对配置文件进行修改。但是由于其本质只是按小范围内定义的或者开发人员自己定义的格式写成的文本,会造成标准混乱,需要自行编写文件的读写代码。另外配置文件也难以包含较多的配置或复杂的层级关系,否则会增加用户浏览和修改的难度。

Excel格式

对于我们开发人员来说,我们定制配置文件的时候可能会选择使用文本格式,或者一些更标准的数据格式如xml或者json。但是这些格式对于客户来说或者需要约定规则,或者太过专业难懂,需要掌握一定的开发基础才能手动进行编写和修改。因此在很多客户会选择使用Excel格式作为配置文件的格式。

优点和缺点

正如上文所说,客户更倾向于使用Excel作为他们的首选配置文件的格式,因为相比过于简单、无规则的文本配置文件、有一定学习成本的xml和json文件,Excel文件可以使用Office Excel、WPS Excel等流行的软件进行编辑,而且客户通常对这些软件都比较熟练。

Excel格式的优点很大程度上来源于Excel的编辑软件,通过软件用户可以直观地查看各个配置的值,可以实现快速编辑、批量编辑或更复杂的编辑操作。可以设置单元格和表格的样式,使表格更加清晰明了。还可以实现排序、筛选、批注等操作。

对于Excel格式本身,它在一个Excel文件里可以设置不同的工作表,对于配置文件可以将不同的工作表定义为不同分组的配置,集成在一个文件中。

Excel中的单元格可以指定为不同类型的数据格式,如文本、数字、日期等。如果规范的使用,这点可以成为配置文件格式的优点。但在多数情况下客户并不会在意填写单元格的数据类型,因此我们需要兼容每种情况,反而会使解析数据更为复杂。

对于应用程序的配置文件来说,Excel文件的很多功能都是不必要的,有时客户设置的一些和配置的数据无关的东西却可能会使得程序读取的值有偏差甚至报错。换句话说,Excel格式作为配置文件的格式较为累赘。

定义内容范围

Excel在形式上符合表格的行、列的形式,这使得它在使用上和数据库中的表很接近。但是不同的是Excel表格并没有很严格地限定行和列,这使得用户可以随便地编辑任意的单元格。具体到我们的配置文件的情况就是客户经常会在规范的表格范围外任意地方添加其他的内容,例如备注、注释等。实际上这样会破坏我们程序中解析表格的流程,因为我们很难区分这个单元格是不是我们配置的内容。这时我们就需要一些特定的约束规则让我们程序知道内容的边界。比较典型的方法有:

添加END作为行和列的结束,END标记需要在特意的添加,在表格中也显得比较突兀:

另外一种是规定若遇到空行和空列就结束读取。那么客户如果要为表格添加备注内容就必须要在一行空行外进行,但是相对END标记来说这种规定不太明了,容易被忽略。另外也不排除可能出现无数据行的情况。

Excel的读取方法

对于在.NET和C#环境下读取Excel文件,常用的方法有:Microsoft Office的COM组件、NPOI和Spire.Xls for .NET。Office COM组件要求客户机上必须安装Office,且对Office的版本也有要求,效率也相对较低。Spire.Xls是较为优秀的Excel读写类库,但由于是商业类库,因此有一定的限制。相比较下NPOI是开源的类库,而且现在也依然在维护,效率对于一般的配置表和中小规模的数据表的读取也可以胜任,因此是我们读取方法的首选。

XML格式

XML指可拓展标记语言(Extensible Markup Language),类似于HTML,XML是一种标记语言,但与HTML不同的是XML的设计宗旨是用于传输和存储数据,而非用于显示数据。

优点和缺点

XML文件本质上是一个文本文件,因此任何具有文本编辑软件都可以对XML文件进行编辑,用户只要熟悉XML语言语法和编辑XML的数据结构就可以正确地手动编辑XML。由于是文本文件,XML也能获得更好的通用性,被大部分的系统以及平台支持。

XML被广泛地用于各种应用程序和软件中作为基础创建新的数据格式,例如微软Office文件中带有x后缀名的文件都是基于XML技术。再例如我们的NX中也定义了不少的XML格式,如用于导入导出材料库数据的MatML,用于模板定义的pax文件,定义块UI的dlx文件等,它们都属于XML的应用标准。

对应开发人员,基本上所有流行的编程语言都有提供XML相关的应用程序接口。以我们.NET为例,使用System.Xml和System.Xml.Linq命名空间下的接口就可以完成大部分的Xml读写工作。.NET还支持XPath、XQuery、XSD架构等Xml相关的技术,这些都是.NET框架中原生支持的,不需要使用任何第三方库。

XML的标签必须要成对出现,这导致了大量的数据冗余,需要更多的字符才能表达同样的数据,虽然理论上XML有更好的可读性,但是阅读起来也比较繁琐;在数据类型上,XML并不支持不同的数据类型,只能记录为文本,不支持数组类型,必须用更多的元素表示数组,这也增加了文本的大小;XML格式中可以通过元素和属性记录数据,这在使用上也会引起分歧,有的开发者偏向于用元素,有的偏向于用属性。

元素和属性

如前文所述,XML中可以使用元素和属性两种不同的方式储存数据。

XML 元素指的是从(且包括)开始标签直到(且包括)结束标签的部分。元素中可以包含文本、属性,也可以嵌套其他的元素,因此元素也是XML文档中最基本的单位,一个完整的XML文档由声明和一个根元素组成。

以下是一个简单Name元素的写法:

若元素不包含文本或其他属性也可以将开始和结束标签合并:

XML中可以在元素的开始标签添加属性,类似于HTML:

属性的值必须用引号括起来,可以使用双引号或单引号。与元素不同的是,属性的值只能是简单的文本也无法表达数组。

但对于单层的数据结构,完全可以用属性表达出和元素一样的效果:

虽然XML标准并没有规定什么时候使用元素或属性,但是有一个约定俗成的理念:使用元素储存数据,使用属性储存元数据(描述数据的数据)。

使用设置文件

为了对不同的应用程序创建储存配置数据的XML文档,并创建对应的类以便我们在程序中调用,比较有效和方便的方法是使用.NET中类序列化和反序列化为XML的方法。但是即使是这样我们依然需要自己构建XML的配置文件架构,幸运的是.NET中早已存在完善的应用程序配置文件架构,并且是以XML为基础,在Visual Studio中提供了专门的编辑器,可以更方便地创建和编辑设置。

在Visual Studio中添加设置文件,只需右键需要创建应用程序的工程->添加->新建项:

找到并选择新建设置文件:

创建后可以在解决方案资源管理器中看到有两个新的项,分别为一个cs文件,用于开发人员对生成的设置类添加自定义代码,和一个代表设置文件的项,其中包含XML文件和设计器自动生成的代码:

双击Settings项可以打开设置编辑器:

其中提供了一个表格,开发人员可以方便地添加和删除设置项,更改设置的名称、类型、应用范围和值。类型支持大部分常用的类型,并且可以预览,所选的类型必须支持序列化。范围选项可以现在用户是否能在运行时对设置项进行更改。

在程序中读取或修改特定属性的值只需要调用设置类的Default设置,并通过特定名称的类属性就可以完成:

对应的XML文档如下:

程序生成的同时此XML文档将一起负责到程序的目录中,程序运行时将读取此文档中定义的值。因此用户要手动修改设置只需要修改此文档中对应设置的值即可。

JSON格式

JSON(JavaScript Object Notation, JS 对象简谱) 是一种轻量级的数据交换格式。它基于 ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。

优点和缺点

与XML一样,JSON也是一种文本格式,因此也可以轻松地使用文本编辑工具进行手动编辑。JSON和XML都是树结构,但和XML由元素和属性构成不同,JSON由对象、数组、字符串、数值、布尔值和空值组成。JSON的组成和面向对象编程语言中的对象类似,甚至在Javascript中可以直接使用JSON的语法定义一个对象。对于表示对象中的一个简单的值属性,在XML中可以用元素或属性的方式表示,这时就存在一个选择的问题。但在JSON中表示的方式是唯一的,而且和它原来的结构非常相似。对于简单的值,JSON提供不同的类型,特别是可以直接获取数值类型,而XML则需要将字符串再次解析为数值。JSON对于数组的支持也是相对XML的一个很大的优点,虽然XML也可以通过树结构表达集合,但因为不是语法直接支持会增加很多的文本量,读起来也更困难。

XML通常认为是一种比较“重型”的格式,大量的冗余提供了更强大的功能。相比下轻型的JSON也可以实现XML的大部分功能,而实现同样功能的情况下因为文本较小因此数度更快。可读性方面,XML和JSON都需要人员对语法有一定的知识,相比下JSON较短读起来也更简洁明了。

JSON中使用了更多的括号和特殊符号,这对开发人员来说较为自然,但是对于一般人员想手动对JSON文本进行编辑的时候可能会由于忘记了符号而造成语法错误,无法解析的情况。JSON标准中没有对于注释的支持也是一个问题,虽然在一些更新版本的标准中提供了注释的规范。

作为配置文件格式

JSON的基本结构其实是一种”键——值“结构,因此同样非常适合作为简单配置文件的格式:

另外JSON对数组的支持也方便了多个值的配置项:

.NET中要到5.0后才原生支持JSON文件的读写和序列化,而我们NX二次开发一般都达不到这么高的.NET版本。但是.NET中可以使用Newtonsoft.Json,它是.NET中使用最广泛的第三方库,开源而且完全免费,具有强大、易用的JSON读写和序列化功能。

为了将JSON作为配置文件,我们可以将配置项设置为JSON中的键和值,如上示例,然后在C#编写一个对应的类:

从JSON文件中使用Newtonsoft.Json反序列化,获取Settings的实例并在程序中使用:

通用JSON配置文件基类

因为使用JSON配置文件类的功能都类似,我们可以设置一个配置基类,让程序的其他配置类都由这个基类派生,并在基类中添加读写方法和默认配置静态实例。静态实例可以让下游程序直接调用配置值而不需要先调用读取方法:

由此,第二节的例子可以简化为(需要将JSON文件命名为程序名.Setting.json,并放到程序同一目录下):

YAML

YAML(YAML Ain't Markup Language)是一种可读性高,用来表示数据序列化的格式,它实际上是一种通用的数据串行化格式。

优点和缺点

与XML、JSON等相比,YAML是通过缩进区分数据层级的,并且使用空格作为缩进符、大量依赖于文本的外观,使它具有更高的可读性,适合用于表达和编辑数据结构,制造各种配置文件,倾印调试内容、文件大纲。

YAML语法与JSON相近,是JSON的一个超集,但相对于JSON,YAML不依赖于使用各种括号区分数据层级,使人更容易阅读,与此同时也支持通过括号表示一个数值或一个对象。YAML同样支持值类型,但不对字符串数据进行强制加引号的要求。与JSON不同YAML原生支持通过“#”添加注释。

但是相比XML和JSON,YAML是比较小众的格式,应用上相对前两者没有那么的广泛,在实现上也较少。尽管如此YAML凭借它的可读性的优点也有越来越多的应用。由于YAML依赖于换行和缩进,对于具有大数组或复杂对象的数据,YAML可能需要一个很长的文本。对此可以尝试结合使用单行格式去表达。

作为配置文件格式

作为配置文件YAML和JSON非常相似,同样提供数值、字符串、布尔、空值这些基础类型(标量),也支持数组和对象或更复杂的对象。以下为一简单例子:

由此可见YAML格式看上去确实像一个大纲一样把数据罗列出来,方便阅读。

C#中的实现

在.NET和C#中读取和写入YAML文档可以使用YamlDotNet库。

以上文的配置结构为例,新建一个配置类Configuration:

将上文的yaml示例保存到configuration.yaml文件中,通过YamlDotNet的反序列化读取yaml文本并返回Configuration对象:

输出结果: