• YAML 快速上手



    YAML(YAML Ain’t a Markup Language)是专门用来写配置文件的语言,简洁强大,相比于 JSON 和 XML,更加便于开发人员读写。

    YAML 配置文件后缀为.yml 或 .yaml。

    1.语法

    YAML 的基本语法规则如下:

    • 数据结构采用键值对的形式 key: value。
    • 键冒号后面要加空格(一般为 1 个空格)。
    • 字母大小写敏感。
    • 使用缩进表示层级关系。
    • 缩进只允许使用空格,不允许使用 Tab 键。
    • 缩进空格数可以任意,只要相同层级的元素左侧对齐即可。
    • 字符串值一般不使用引号,必要时可使用。使用双引号表示字符串时,会转义字符串中的特殊字符(例如\n)。使用单引号时不会转义字符串中的特殊字符。
    • 数组中的每个元素单独一行,并以 - 开头。或使用方括号,元素用逗号隔开。注意短横杆和逗号后面都要有空格。
    • 对象中的每个成员单独一行,使用键值对形式。或者使用大括号并用逗号分开。
    • 文档以三个连字符---表示开始,以三个点号...表示结束,二者都是可选的。
    • 文档前面可能会有指令,在这种情况下,需要使用---来表示指令的结束。指令是一个%后跟一个标识符和一些形参。
    • 目前只有两个指令:%YAML指定文档的 YAML 版本,%TAG用于 tag 简写。二者都很少使用。
    • #表示注释,从这个字符一直到行尾,都会被解析器忽略。

    2.历史版本

    版本发布日期
    YAML 1.029 January 2004
    YAML 1.118 January 2005
    YAML 1.2.021 July 2009
    YAML 1.2.11 October 2009
    YAML 1.2.21 October 2021

    3.数据结构

    YAML 支持的数据结构有三种:

    • 对象:键值对的集合,又称为映射(mapping)、散列(hashes)、字典(dictionary)。
    • 数组:一组按次序排列的值,又称为序列(sequence)、列表(list)。
    • 标量:单个不可再分的值

    下面分别介绍这三种数据结构。

    对象

    对象的一组键值对,使用冒号结构表示。

    name: Steve
    

    YAML 也允许另一种写法,将所有键值对写成一个行内对象。

    who: { name: Steve, age: 18 }
    

    当然,如果对象元素太多一行放不下,那么可以换行。

    who:
      name: Steve
      age: 18
    

    数组

    一组以连字符开头的行,构成一个数组。注意,连字符后需添加空格。

    animals:
      - Cat
      - Dog
      - Goldfish
    

    连字符前可以没有缩进,也就是说下面这种写法也是 OK 的,但是还是建议缩进,因为更加易读。

    animals:
    - Cat
    - Dog
    - Goldfish
    

    数组也可以采用行内表示法。

    animal: [Cat,Dog,Goldfish]
    

    如果数组元素是一个数组,则可以在连字符下面再缩进输入一个数组。

    animals:
      -
        - Cat
        - Dog
      -
        - Fish
        - Goldfish
    

    如果是行内表示,则为:

    animals: [[Cat,Dog],[Fish,Goldfish]]
    

    如果数组元素是一个对象,可以写作:

    animals:
      - species: dog
        name: foo
      - species: cat
        name: bar
    

    对应的 JSON 为:

    {
        "animals": [
            {
                "species": "dog",
                "name": "foo"
            },
            {
                "species": "cat",
                "name": "bar"
            }
        ]
    }
    

    复合结构

    对象和数组可以结合使用,形成复合结构。

    languages:
     - Ruby
     - Perl
     - Python 
    websites:
     YAML: yaml.org 
     Ruby: ruby-lang.org 
     Python: python.org 
     Perl: use.perl.org 
    

    对应的 JSON 表示如下:

    {
      "languages": [
        "Ruby",
        "Perl",
        "Python"
      ],
      "websites": {
        "YAML": "yaml.org",
        "Ruby": "ruby-lang.org",
        "Python": "python.org",
        "Perl": "use.perl.org"
      }
    }
    

    标量

    标量是最基本、不可再分的值。有以下 7 种:

    • 字符串
    • 布尔值
    • 整数
    • 浮点数
    • Null
    • 时间
    • 日期

    使用一个例子来快速了解标量的基本使用:

    boolean: 
        - TRUE	# true、True 都可以
        - FALSE	# false、False 都可以
    float:
        - 3.14			# 数值直接以字面量的形式表示
        - 6.8523015e+5	# 可以使用科学计数法
    int:
        - 123							# 数值直接以字面量的形式表示
        - 0b1010_0111_0100_1010_1110	# 二进制表示
    null:
        nodeName: 'node'
        parent: ~  		 # 使用~表示 null
    string:
        - hello			 # 字符串默认不使用引号
        - "Hello world"  # 使用双引号或单引号包裹含有空格或特殊字符(如冒号)的字符串
        - newline
          newline1		 # 字符串可以拆成多行,每一换行符会被转化成一个空格
    date:
        - 2018-02-17     # 日期必须使用 ISO 8601 格式,即 yyyy-MM-dd
    datetime:
        - 2018-02-17T15:02:31+08:00    # 时间使用 ISO 8601 格式,时间和日期之间使用 T 连接,+08:00 表示时区
    

    YAML 字符串有三种表示方式:

    • 无引号
    • 双引号
    • 单引号

    字符串默认不需要引号,但是如果字符串包含空格或特殊字符(如冒号),需要加引号。

    双引号字符串允许在字符串中使用转义序列来表示特殊字符,例如 \n 表示换行,\t 表示制表符,以及 \" 表示双引号。

    单引号字符串被视为纯粹的字面字符串,不支持转义序列。

    如果字符串含有单引号,可以使用双引号包裹,反之亦然。

    4.引用

    锚点 & 和别名 *,可以用来完成引用。

    defaults: &defaults
      adapter:  postgres
      host:     localhost
    
    development:
      database: myapp_development
      <<: *defaults
    

    等同于下面的配置。

    defaults:
      adapter:  postgres
      host:     localhost
    
    development:
      database: myapp_development
      adapter:  postgres
      host:     localhost
    

    & 用来建立锚点(defaults),<< 表示合并到当前数据,* 用来引用锚点。

    5.文本块

    如果想引入多行的文本块,可以使用 ||+|->>+>-

    • |

    当内容换行时,保留换行符。

    如果最后一行有多个换行符,只保留一个换行符。

    linefeed1: |
      some
      text
    linefeed2: |
      some
      text
      
    linefeed3: |
      some
    
      text
    

    对应的 JSON 为:

    {
        "linefeed1": "some\ntext\n",
        "linefeed2": "some\ntext\n",
        "linefeed3": "some\n\ntext\n"
    }
    
    • |+

    当内容换行时,保留换行符。

    与 | 的区别是,如果最后一行有多个换行符,则保留实际数目。

    linefeed1: |+
      some
      text
    linefeed2: |+
      some
      text
      
    linefeed3: |+
      some
    
      text
    

    对应的 JSON 为:

    {
        "linefeed1": "some\ntext\n",
        "linefeed2": "some\ntext\n\n",
        "linefeed3": "some\n\ntext\n"
    }
    
    • |-

    当内容换行时,保留换行符,但最后的换行符不保留。

    linefeed1: |-
      some
      text
    linefeed2: |-
      some
      text
      
    linefeed3: |-
      some
    
      text
    

    对应的 JSON 为:

    {
        "linefeed1": "some\ntext",
        "linefeed2": "some\ntext",
        "linefeed3": "some\n\ntext"
    }
    
    • >

    当内容换行时,替换为空格,但保留最后一行的换行符。

    如果最后一行有多个换行符,只保留一个换行符。

    linefeed1: >
      some
      text
    linefeed2: >
      some
      text
      
    linefeed3: >
      some
    
      text
    

    对应的 JSON 为:

    {
        "linefeed1": "some text\n",
        "linefeed2": "some text\n",
        "linefeed3": "some\ntext\n"
    }
    
    • >+

    当内容换行时,替换为空格,但保留最后一行的换行符。

    与 > 的区别是,如果最后一行有多个换行符,则保留实际数目。

    linefeed1: >+
      some
      text
    linefeed2: >+
      some
      text
      
    linefeed3: >+
      some
    
      text
    

    对应的 JSON 为:

    {
        "linefeed1": "some text\n",
        "linefeed2": "some text\n\n",
        "linefeed3": "some\ntext\n"
    }
    
    • >-(缺省行为)

    当内容换行时,替换为空格,不保留最后一行的换行符。

    linefeed1: >-
      some
      text
    linefeed2: >-
      some
      text
      
    linefeed3: >-
      some
    
      text
    

    对应的 JSON 为:

    {
        "linefeed1": "some text",
        "linefeed2": "some text",
        "linefeed3": "some\ntext"
    }
    

    注意:以上 6 个特殊字符,|->- 用得最多。

    6.显示指定类型

    有时需要显示指定某些值的类型,可以使用 !(感叹号)显式指定类型。

    ! 单叹号通常是自定义类型,!! 双叹号是内置类型。

    # !!str 指定为字符串
    string.value: !!str HelloWorld!
    # !!timestamp 指定为日期时间类型
    datetime.value: !!timestamp 2021-04-13T02:31:00+08:00
    

    内置的类型如下:

    !!int:整数类型
    !!float:浮点类型
    !!bool:布尔类型
    !!str:字符串类型
    !!binary:二进制类型
    !!timestamp:日期时间类型
    !!null:空值
    !!set:集合类型
    !!omap,!!pairs:键值列表或对象列表
    !!seq:序列
    !!map:散列表类型
    

    7.单文件多文档

    一个 yaml 文件可以包含多个 yaml 文档,使用三个连字符---分隔。

    a: 10
    b: 
    	- 1
    	- 2
    	- 3
    ---
    a: 20
    b: 
    	- 4
    	- 5 
    

    这种情况在 K8S 和 SpringBoot 中非常常见。

    比如 SpringBoot 在一个 application.yml 文件中,通过 — 分隔多个不同配置,根据 spring.profiles.active 的值来决定启用哪个配置。

    # 公共配置
    spring:
      profiles:
        active: prod #使用名为 prod 的配置,这里可以切换成 dev。
      datasource:
        url: jdbc:mysql://localhost:3306/test_db?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
        password: 123456
        username: root
    ---
    # 开发环境配置
    spring:
      profiles: dev #profiles属性代表配置的名称。
    
    server:
      port: 8080
    ---
    # 生产环境配置
    spring:
      profiles: prod
    
    server:
      port: 80
    

    8.解析

    下面以 YAML 表示一个简单的后台服务配置:

    name: UserProfileServer
    maxconns: 1000
    queuecap: 10000
    queuetimeout: 300
    loginfo:
      loglevel: ERROR
      logsize: 10M
      lognum: 10
      logpath: /usr/local/app/log
    

    下面以 Go 语言为例,解析上面的 YAML 配置。

    因为 Go 官方并没有提供解析 YAML 的标准库,所以这里基于第三方开源库 go-yaml 来完成对 YAML 文件的解析。

    第一步,将 YAML 配置文件的内容在 Convert YAML to Go struct 转换为 Go struct。

    type Server struct {
    	Name         string `yaml:"name"`
    	Maxconns     int    `yaml:"maxconns"`
    	Queuecap     int    `yaml:"queuecap"`
    	Queuetimeout int    `yaml:"queuetimeout"`
    	Loginfo      struct {
    		Loglevel string `yaml:"loglevel"`
    		Logsize  string `yaml:"logsize"`
    		Lognum   int    `yaml:"lognum"`
    		Logpath  string `yaml:"logpath"`
    	} `yaml:"loginfo"`
    }
    

    第二步,利用第三方开源库 go-yaml 来完成对 YAML 文件的解析。

    package main
    
    import(
    	"io/ioutil"
    	"fmt"
    	"os"	
    
    	yaml "gopkg.in/yaml.v3"
    )
    
    func main() {
      file, err := os.Open("server.yaml")
      if err != nil {
        fmt.Printf("error:%v\n", err)
        return
      }
      defer file.Close()
    
      data, err := ioutil.ReadAll(file)
      if err != nil {
        fmt.Printf("error:%v\n", err)
        return
      }
      v := Server{}
      err = yaml.Unmarshal(data, &v)
      if err != nil {
        fmt.Printf("error:%v\n", err)
        return
      }
      fmt.Printf("%+v\n", v)
    }
    

    运行输出:

    {Name:UserProfileServer Maxconns:1000 Queuecap:10000 Queuetimeout:300 Loginfo:{Loglevel:ERROR Logsize:10M Lognum:10 Logpath:/usr/local/app/log}}
    

    9.完整示例

    %YAML 1.2
    ---
    receipt:     Oz-Ware Purchase Invoice
    date:        2012-08-06
    customer:
        given:   Dorothy
        family:  Gale
       
    items:
        - part_no:   A4786
          descrip:   Water Bucket (Filled)
          price:     1.47
          quantity:  4
    
        - part_no:   E1628
          descrip:   High Heeled "Ruby" Slippers
          size:      8
          price:     133.7
          quantity:  1
    
    bill-to:  &id001
        street: | 
                123 Tornado Alley
                Suite 16
        city:   East Centerville
        state:  KS
    
    ship-to:  *id001   
    
    specialDelivery:  >
        Follow the Yellow Brick
        Road to the Emerald City.
        Pay no attention to the
        man behind the curtain.
    ...
    

    注意在 YAML 中,字符串不一定要用双引号标示。另外,在缩进中空白字符的数目并不是非常重要,只要相同层次结构的元素左侧对齐就可以了(不过不能使用 TAB 字符)。

    %YAML 1.2 表示版本。

    这个文件的顶层由七个键值组成:其中一个键值"items",是两个元素构成的数组(或称清单),这数组中的两个元素同时也是包含了四个键值的散列表。

    文件中重复的部分用这个方法处理:使用锚点(&)和引用(*)标签将"bill-to"散列表的内容复制到"ship-to"散列表。也可以在文件中加入选择性的空行,以增加可读性。

    在一个文件中,可同时包含多个文件,并用---分隔。选择性的符号...可以用来表示文件结尾(在流通信中,这非常有用,可以在不关闭流的情况下,发送结束信号)。


    参考文献

    The Official YAML Web Site
    Convert YAML to JSON
    go-yaml - Github
    Convert YAML to Go struct
    YAML 详解与实战-CSDN
    YAML - 维基百科,自由的百科全书
    Brief YAML reference — Camel 0.1.2 documentation

  • 相关阅读:
    公开的mqtt服务器如何获得等问题
    MySql语句
    区间内的真素数问题(C#)
    tomcat动静分离
    【Java】继承——成员变量的私有化(修饰符super进行访问)
    【架构设计】CAP理论、BASE理论
    git代码合并+解决冲突
    为什么我们在写java 代码的时候一定要public static关键字的作用?
    SystemV共享内存
    【课程作业】最优化理论与方法:第三次作业
  • 原文地址:https://blog.csdn.net/K346K346/article/details/139726919