一、什么是 YAML?—— 不止于「另一种配置文件」

YAML 全称 YAML Ain't Markup Language(YAML 不是标记语言),听着像绕口令,核心却是「反标记语言」的设计理念:用最简洁的语法描述数据结构,让人类一眼能看懂,机器也能轻松解析。

它诞生于 2001 年,初衷是替代 XML 的繁琐标签和 JSON 的大括号,如今已成为配置文件的「首选格式」—— 你在 Kubernetes、Docker Compose、Spring Boot、GitHub Actions 等场景中,随处可见它的身影。

核心定位:人类可读、机器可解析的数据序列化语言,专注于配置场景的简洁性和易用性。

二、为什么选择 YAML?—— 三大核心优势

对比 XML、JSON,YAML 的优势一目了然:

特性 XML(繁琐) JSON(简洁但局限) YAML(平衡之选)
语法简洁度 需闭合标签( 需大括号 / 引号,无注释 无多余符号,支持注释
可读性 低(标签冗余) 中(结构清晰但缺乏注释) 高(自然语言般的层级)
数据类型支持 需定义 schema 基础类型(字符串 / 数字等) 原生支持列表、字典、锚点等

举个直观对比:

JSON 写法(必须带引号和大括号):

1
2
3
4
5
6
7
8
9
{
"name": "张三",
"age": 28,
"hobbies": ["编程", "爬山"],
"address": {
"city": "北京",
"street": "中关村大街"
}
}

YAML 写法(无多余符号,更清爽):

1
2
3
4
5
6
7
8
name: 张三
age: 28
hobbies:
- 编程
- 爬山
address:
city: 北京
street: 中关村大街

除此之外,YAML 还支持 注释(# 这是注释)、锚点复用(避免重复代码)、多文档合并,这些都是配置场景中刚需的功能。

三、YAML 核心语法:5 分钟上手

语法原则:缩进敏感、大小写敏感、无多余分隔符,核心规则如下:

1. 基本键值对(字典 / 对象)

用 键: 值 表示,冒号后必须加 空格(关键!),支持多种数据类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 字符串(无需引号,特殊字符除外)
username: admin
nickname: "小杨" # 含中文也可加引号(可选)
email: user@example.com

# 数字(整数/浮点数)
port: 8080
timeout: 3.5 # 浮点数
count: 100 # 整数

# 布尔值(true/false 或 yes/no,大小写敏感)
enabled: true
debug_mode: no

# null 值(~ 或 直接留空)
empty_value: ~
unset_key: # 等价于 null

# 日期时间(原生支持 ISO 格式)
create_time: 2024-05-20T14:30:00
expire_date: 2024-12-31

2. 列表(数组)

用 - 元素 表示,短横线后加空格,支持单层、嵌套、混合类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1. 简单列表(同类型元素)
fruits:
- 苹果
- 香蕉
- 橙子
- 葡萄

# 2. 嵌套列表(多维数组)
menu:
- 首页
- 产品中心:
- 手机
- 电脑
- 配件:
- 耳机
- 充电器
- 关于我们
- 联系客服

# 3. 混合类型列表
mixed_list:
- 张三
- 25
- true
- { city: 上海, district: 浦东 } # 内嵌字典(紧凑写法)

3. 复合结构(字典 + 列表)—— 实际配置高频场景

这是 YAML 最核心的用法,以下是 3 个真实场景示例:

示例 1:Docker Compose 服务配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
version: "3.8"
services:
# Web 服务(Nginx)
web:
image: nginx:1.25.1
container_name: my-nginx
ports:
- "80:80" # 主机端口:容器端口
- "443:443"
volumes:
- ./nginx/conf:/etc/nginx/conf.d # 配置文件挂载
- ./nginx/html:/usr/share/nginx/html # 静态资源挂载
restart: always # 容器退出后自动重启
environment:
- TZ=Asia/Shanghai # 时区环境变量

# 数据库服务(MySQL)
db:
image: mysql:8.0.33
container_name: my-mysql
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql # 数据持久化
restart: always
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=test_db
- MYSQL_USER=test_user
- MYSQL_PASSWORD=test_pass
networks:
- app-network

# 自定义网络(隔离容器网络)
networks:
app-network:
driver: bridge

# 数据卷(独立于容器的存储)
volumes:
mysql-data:
示例 2:Spring Boot 应用配置(application.yml)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
spring:
# 数据库配置
datasource:
url: jdbc:mysql://localhost:3306/test_db?useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# Redis 配置
redis:
host: localhost
port: 6379
password:
database: 0
timeout: 3000ms

# 应用自定义配置
app:
name: user-service
version: 1.0.0
# 白名单列表
allowlist:
- 192.168.1.0/24
- 10.0.0.0/8
# 接口限流配置
rate-limit:
enabled: true
limit: 100 # 每秒最大请求数
burst: 20 # 突发请求允许数

# 日志配置
logging:
level:
root: INFO
com.example.user: DEBUG # 自定义包日志级别
file:
name: ./logs/user-service.log
示例 3:GitHub Actions CI/CD 流水线配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
name: 构建并部署应用
on:
# 触发条件:main 分支推送或 Pull Request
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
# 构建任务
build:
runs-on: ubuntu-latest # 运行环境
steps:
# 步骤 1:拉取代码
- name: 检出代码
uses: actions/checkout@v4

# 步骤 2:设置 JDK 17
- name: 设置 JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: maven

# 步骤 3:Maven 构建
- name: 构建应用
run: mvn -B package --file pom.xml

# 步骤 4:上传构建产物
- name: 上传 JAR 包
uses: actions/upload-artifact@v4
with:
name: app-jar
path: target/*.jar

# 部署任务(依赖 build 任务成功)
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- name: 下载构建产物
uses: actions/download-artifact@v4
with:
name: app-jar

# 步骤:部署到服务器(示例:通过 SSH 上传)
- name: 部署到生产服务器
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
cd /opt/app
mv ~/target/*.jar ./app.jar
systemctl restart app.service

4. 特殊场景处理 —— 覆盖 90% 实操需求

(1)字符串特殊处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 1. 含特殊字符(:、#、空格等)需用引号包裹
special_str1: 'He said: "YAML is easy!"' # 单引号:不解析转义字符
special_str2: "Line1\nLine2\tTab" # 双引号:解析转义字符(换行、制表符)
special_str3: "路径:C:\\Program Files" # 转义反斜杠

# 2. 多行字符串(保留换行 vs 折叠换行)
# 保留换行(| 符号,适合脚本、文本内容)
shell_script: |
#!/bin/bash
echo "开始部署..."
cd /opt/app
java -jar app.jar --spring.profiles.active=prod
echo "部署完成!"

# 折叠换行(> 符号,适合长文本描述,换行转为空格)
product_desc: >
这是一款基于 Spring Boot + Vue 的前后端分离项目,
支持用户管理、权限控制、数据统计等核心功能,
适用于中小型企业快速搭建业务系统。

# 3. 强制保留换行(|+)/ 强制删除末尾换行(|-)
keep_newline: |+
第一行
第二行
(末尾会保留两个换行)

trim_newline: |-
第一行
第二行
(末尾无换行)
(2)注释用法
1
2
3
4
5
6
7
# 单行注释(只能用 #,无多行注释)
server:
port: 8080 # 应用端口(开发环境用 8080,生产用 80)
servlet:
context-path: /api # 接口前缀,所有接口需加 /api
tomcat:
max-threads: 200 # 最大线程数(根据服务器配置调整)

5. 高级功能:锚点复用与多文档合并

(1)锚点复用 —— 避免重复配置

用 & 定义锚点,* 引用锚点,<< 合并字典(适合通用配置复用):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 定义通用配置锚点(& 后面跟锚点名称)
common_config: &common
timeout: 30s
retries: 3
connect_timeout: 5s
log_level: INFO

# 服务 A:引用并继承通用配置
service_a:
<<: *common # 合并 common 配置
name: 用户服务
port: 8081
# 覆盖通用配置中的 timeout
timeout: 60s

# 服务 B:引用通用配置,无覆盖
service_b:
<<: *common
name: 订单服务
port: 8082

# 服务 C:引用通用配置的部分字段(* 直接引用单个值)
service_c:
name: 支付服务
port: 8083
timeout: *common.timeout # 直接引用 common 的 timeout
log_level: WARN # 覆盖日志级别
(2)多文档合并 —— 一个文件多个配置

用 --- 分隔多个文档,适合环境区分(开发 / 测试 / 生产):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 开发环境配置(文档 1)
---
spring:
profiles: dev
datasource:
url: jdbc:mysql://localhost:3306/dev_db
username: root
password: 123456
server:
port: 8080

# 测试环境配置(文档 2)
---
spring:
profiles: test
datasource:
url: jdbc:mysql://test-db:3306/test_db
username: test_user
password: test_pass
server:
port: 8081

# 生产环境配置(文档 3)
---
spring:
profiles: prod
datasource:
url: jdbc:mysql://prod-db:3306/prod_db
username: prod_user
password: ${DB_PASSWORD} # 引用环境变量
server:
port: 80

四、C++ 库 yaml-cpp

在 C++ 项目中处理 YAML 文件,yaml-cpp是开发者的首选库。它提供了直观的 API 接口,允许开发者以面向对象的方式解析、修改和生成 YAML 文档。通过yaml-cpp,你可以轻松地将 YAML 数据映射到 C++ 类对象,或反之将对象序列化为 YAML 格式。

安装yaml-cpp非常便捷,在 Debian/Ubuntu 系统下,使用apt-get install libyaml-cpp-dev即可完成安装;在 CentOS/RHEL 系统中,可通过yum install yaml-cpp-devel进行安装。对于使用 CMake 构建的项目,只需在CMakeLists.txt中添加find_package(yaml-cpp REQUIRED),即可将yaml-cpp集成到项目中。

以下是一个简单的使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <yaml-cpp/yaml.h>

int main() {
try {
YAML::Node config = YAML::LoadFile("config.yaml");
std::string server = config["server"].as<std::string>();
int port = config["port"].as<int>();
std::cout << "Server: " << server << ", Port: " << port << std::endl;
} catch (const YAML::Exception& e) {
std::cerr << "Error parsing YAML: " << e.what() << std::endl;
return 1;
}
return 0;
}

上述代码展示了如何使用yaml-cpp从config.yaml文件中读取server和port字段的值。yaml-cpp还支持复杂数据结构的处理,如列表、嵌套映射等,能够满足各类项目对 YAML 配置解析的需求,是 C++ 开发者处理 YAML 文件不可或缺的工具。

五、常见坑与避坑指南

缩进引发的血案:YAML 严格依赖缩进表示层级关系,务必使用两个或四个空格缩进,禁止使用 Tab 键。例如,以下错误写法会导致解析失败:

1
2
3
4
# 错误示范
parent:
- child1
- child2 # 缩进与上一行不一致

修正后:

1
2
3
parent:
- child1
- child2

字符串的引号陷阱:YAML 支持单引号、双引号和无引号字符串。无引号字符串会自动解析特殊字符(如\n转义),双引号支持变量插值(如${env.VAR}),单引号则按字面处理。例如:

1
2
3
4
5
6
# 无引号字符串自动解析转义
unquoted: Hello\nWorld
# 双引号支持变量插值
double_quoted: "当前时间: ${NOW}"
# 单引号保留原始内容
single_quoted: 'Hello\nWorld'

列表与字典混用错误:列表项只能是单一数据类型或结构,避免以下错误嵌套:

1
2
3
4
# 错误:列表项同时包含字符串和字典
mixed_list:
- apple
{name: banana, price: 2} # 此处应缩进并修正为字典格式

正确写法:

1
2
3
mixed_list:
- apple
- {name: banana, price: 2}

注释穿透问题:YAML 注释以#开头,但多行注释需格外注意。例如,注释掉字典键值对时,确保缩进对齐,否则可能影响后续解析:

1
2
3
4
# 错误:注释未对齐导致解析异常
# user:
# name: John
password: secret # 此密码字段可能被误解析为user的子项

修正后:

1
2
3
user:
# name: John
password: secret

版本兼容性风险:不同语言的 YAML 解析器对规范支持存在差异,建议优先使用官方推荐库。例如,Python 的PyYAML库默认开启unsafe_load,存在安全隐患,推荐使用safe_load方法:

1
2
3
import yaml
with open('config.yaml', 'r') as f:
data = yaml.safe_load(f) # 避免执行恶意YAML内容