别急着翻教科书,先跟着我回到一个真实场景。有次带新人做数据整理,我给了他一摞成绩单的Excel表格,让他统计一下平均分,他反问我:“斌哥,你说的数据在哪个文件夹?还有结构是啥?搞这么复杂干嘛?”这个问题问得特别诚恳,我却一时没答上来。因为“数据”和“结构”这两个词,我们天天挂在嘴边,可真要掰开讲清楚,反而比写几段代码还难。正好借着这个“第一章”的标题,我想认真把这堂课补上——它适合刚接触编程的自学者,也适合那些天天跟数据打交道、却总被各种“结构”绕晕的工程新人。这篇文章不打算复述教材定义,而是用大白话、例子和几段可运行的代码,把“数据”和“结构”这两个地基概念彻底捋一遍。
1. 数据到底是什么:不要把数据和信息混在一起
1.1 体温测量里藏着的“数据”和“信息”的区别
先说一个最常见的例子。早上你摸着额头感觉发烫,“发烫”是你获得的信息;但医生并不会处理“烫”这个字,他需要体温计显示出的“38.5℃”这个数字。这个“38.5”就是数据。信息是人对世界的感知和判断,而数据是把这种感知符号化、量化、固定下来的结果。换句话说,数据是信息的“快递包装”,信息是数据“拆包”之后被你理解的意义。
这个区别听起来像咬文嚼字,但到了程序里就非常要命。如果你告诉程序“这个病人发烫”,程序没法计算;如果你告诉程序“体温=38.5”,程序才能做判断是否超过阈值、是否需要预警。所以你会看到,凡是能进入计算机系统的东西,必须先被编码成数据,而不是“信息”本身。同一个世界,可以被不同设备编码成不同数据:一杯热水,温度计记录的是38℃,体重秤记录的是0.3kg,pH试纸记录的是酸碱度数值。数据从来不是事实的全部,它只是事实的某种切片和编码。你选择的“切片方式”决定了后续所有分析的边界。
这也是我工作中特别强调的一点:精度和编码方式会限制你的天花板。你录入员工工资时如果只保留整数,那每个月几块钱的零头就永远从报表里消失了;你用字符串去存年龄而非整数,后续做“年龄大于30”的筛选项可能就会得到一堆啼笑皆非的结果。数据采集那一刻的编码决策,往往比后续任何算法都更影响结论。
1.2 只有确认了类型,数据才有参与计算的意义
光有“38.5”还不够,计算机并不知道它是体温、是路标里程,还是股票代码。所以必须给数据贴上“类型”标签。编程语言里最基础的概念就是整数、浮点数、字符串、布尔值,它们本质上是“数据的使用说明书”。整数可以做取余和加减,字符串可以拼接和匹配,布尔值可以参与逻辑判断。同样是数字“120”,在车速场景是浮点数,在血压仪里也是浮点数,但在“紧急电话”语境下它就是个特殊的字符串或枚举值。数据的含义不是自己长出来的,而是靠“数据+类型+上下文”共同决定。
举个我见过很多次的低级错误:从某个外部接口拿到返回值,里面有个字段叫“phone”,明明应该是字符串,结果因为全是数字,被某些中间件自动转换成了整数。遇到“010-88888888”这种带区号的就直接报错,或者手机号字段变成科学计数法,后四位全变零。这就是典型的“有数据、没结构”导致的事故。类型是结构的第一步,也是最容易被忽略的一步。
1.3 数据的常见形态:从小表格到传感器数据流
日常工作中我们见到的数据形态大致可以分成三类,别看它们长得不一样,本质都是“被记录下来的、等待被解释的符号”。
- 结构化数据:最规整,就是二维表。Excel表、MySQL表、CSV文件都是例子。每一行是一条记录,每一列是一个字段,表头定义了字段名和类型。
- 半结构化数据:有一定组织方式,但又不像二维表那么死板。最常见的就是JSON、XML,还有日志文件。嵌套、数组、动态增减字段都能容忍。
- 非结构化数据:图片、音频、视频、纯文本正文。它们也有内部规律,但不是“字段+记录”的形态,需要用额外的方法去抽取特征。
如果你接触过数据采集卡、传感器、PLC这类工业设备,你会发现它们最常产出的是“数据流”——一帧一帧的二进制字节,或者连续的时间序列。这种数据不像表格那么直观,必须在接收端按照协议手册去“翻译”。后面第4.1节我还会专门展开讲,这里先记住一个结论就好:数据形态决定了你处理它的第一套动作,而动作的起点永远是搞清楚结构。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 结构从哪里来:从“一堆数”到“一张表”
2.1 没有结构,数据只是一串噪音
你试试看下面这组数:“35, 80, 12.6, 3”。这四个数字单独扔给你,你能猜出是什么意思吗?可以是年龄、体重、油价、数量;也可以是坐标、温度、楼层、序号。任何解读都可能是对的,也都可能是错的。但如果我告诉你,这是一张“病人病案表”里的某个横向切片:年龄35,体重80,血红蛋白浓度12.6,吸烟史3(年),你立刻就明白了。再进一步,如果我还告诉你这个表是从医院HIS系统导出的,那么“诊断”“用药”“医嘱”这些字段的排列规则、长度限制、是否允许空值,也都跟着定下来了。
结构本质是一份“约定”。它约定了有哪些字段、每个字段什么类型、按照什么顺序排列、每个值允许的取值范围。计算机之所以能高效处理数据,不是因为它多聪明,而是因为它死守着这套约定,按图索骥。结构化数据之所以叫“结构化”,就是因为它已经被明确地定义了。工程上管这层定义叫“元数据”,也就是“描述数据的数据”。表头、字段类型、注释、文档里的字段说明,全部是元数据。
2.2 二维表:最容易理解的结构模型
我一直觉得,想给新手讲清楚“结构”这个概念,不用绕远路,就盯着一张Excel表看就够。横着看,是一行一行的记录;竖着看,是一列一列的字段。我们把表头那一行单独抽出来,它就是这套数据的结构的全部清单。比如“学号、姓名、班级、语文、数学、英语”,这就是成绩表的六列结构。任何一个学生的具体成绩,只是往这个“空壳结构”里填充的一份实例。
数据库领域的二维表把这件事做成了工业标准:每个字段要声明类型,Nullable是否允许为空,主键唯一标识,外键关联另一个表。为什么数据库这么深受欢迎?因为二维表这个结构模型足够简单、统一、好检索。你在SQL里写的 SELECT * FROM students WHERE score > 90,本质就是“保持结构不变,按条件框选行”。你会发现,只要结构稳定,查询逻辑就可以被抽出来做成固定的套路。
这里想多提醒一句:字段的约束也是结构的一部分,而且是最容易漏掉的部分。做了多年数据治理,我遇到不知道多少张表,看起来列齐了,实际上里面什么妖魔鬼怪都有:明明该填数字的列混进来一个“#N/A”,该唯一的编号重复了七八遍,日期格式一列是“2024-01-01”,另一列是“20240101”。这些其实都算是结构被破坏——约束不到位,再好的结构也会慢慢腐坏。
2.3 代码里的结构:从C语言结构体到JSON
表格能表达结构,代码也能。而且代码里表达结构的方式往往更严谨,因为它要交给编译器去检查。C语言的结构体就是最经典的例子,你定义好一个结构体,就相当于在代码里画好了一张“表头”:
c复制typedef struct Student {
char name[32]; // 姓名,最多32字节
int age; // 年龄,整数
float score; // 成绩,浮点数
} Student;
结构体变量定义之后,你创建一个Student变量,就是在“创建一行空记录”:
c复制Student stu1;
strcpy(stu1.name, "张三");
stu1.age = 18;
stu1.score = 92.5;
“结构体变量的定义”“结构体初始化”“怎么引出结构体成员”这些问题,说穿了就是“怎么按照表头去填格子”。点号运算符就是在访问某一行里的某个字段。
再看现在前后端接口里最常用的JSON,它的表达方式更自由,但仍然是在约定结构。
json复制{
"name": "张三",
"age": 18,
"score": 92.5
}
JSON不要求你提前编译,字段增删也灵活,所以特别适合一些变化频繁、松耦合的系统对接场景。但灵活性是双刃剑,同一批接口文档如果字段命名一会驼峰一会下划线、嵌套层级各不相同,对接双方的所谓“结构约定”就形同虚设。Python里的dict也是类似道理——键名就是结构,值就是数据。Pandas里的DataFrame更直接,df.columns那一串列名就是结构,df.dtypes就是每个字段的类型定义。
3. 再看“数据结构”:教材里的结构是另一层意思
3.1 逻辑结构与物理结构:一张表在内存里怎么摆
前面讲的二维表、结构体、JSON,其实都在说“逻辑结构”——就是人在思维层面如何组织和描述数据。但计算机真正干活时,数据总要落到内存的物理地址上。这里就出现一个经典的岔路:同样的逻辑顺序,可以用不同的物理方式实现。
数组是一种连续排列的结构,就像电影院连座的一排,座位号越大,位置越靠后,地址也是连续的。链表则灵活得多,每个节点里保存着数据和指向下一个节点的指针,物理上东一个西一个完全没关系,但靠指针把逻辑顺序串起来。你研究“数据结构”课程时碰到的第一个大分化,往往就是数组和链表的对比。
用排队来解释最直观。食堂窗口前大家站成一条直线,这是“数组式排队”,谁排第几号一目了然,但有人插队进来,后面所有人都得挪位置。另一种方式是大家散开站着,每人拿一张字条,写着下一个是谁的位置,你要想找到队尾,只能顺着字条一个个找下去,这叫“链表式排队”。你说哪种更好?答案是没有绝对好坏,只看你的操作场景:
- 频繁按位置随机访问 → 数组快,因为它算一下地址偏移就能定位。
- 频繁插入、删除、不知道总长度 → 链表更灵活,改一下指针就行。
这就是“数据结构”这门课真正的第一课:不是你背下多少种结构,而是你要理解逻辑上的组织方式不同,物理上的存储代价就不同,最终操作的时间复杂度也不同。 C语言结构体加上指针,恰好就是用来亲手搭建链表、树、图这些物理结构的基础工具,它在教材里地位这么高,不是没有原因的。
3.2 栈、队列、树、图:它们解决什么问题
很多非科班的人一听“栈”“队列”“树”“图”就头大,觉得是考试才用的概念。我用生活场景给你串一遍,你会发现全是你天天在用的东西。
- 栈:后进先出。函数调用、撤销操作、浏览器回退按钮,全靠它。你写的程序嵌套一层层调用函数,底层系统就是靠栈保存现场,一层层往上弹。
- 队列:先进先出。打印机任务、食堂排队、消息队列、请求限流,全是它。谁先来谁先被处理,公平且有序。
- 树:一对多的层级关系。文件夹目录、公司组织架构、网页的DOM结构,都是树。查找速度比链表快得多,所以很多索引底层都带树的思想。
- 图:多对多的网络关系。地图导航最优路径、社交网络好友推荐、设备连接拓扑,全是图。计算最短路径就是图的经典操作。
这些结构不是凭空发明的,它们每一种都对应着“真实世界中某种关系的建模”。教材里还会有“双端队列”这种进阶变体,表面看是队列,实际上头和尾都能进能出,为的就是在特定场景下兼顾栈和队列两种特性,牺牲一点简单性,换来操作灵活性。这些概念只有放到“关系”里去理解,才不会死记硬背。
3.3 工程中还有“项目结构”“目录结构”这类结构
学完教科书,你再回到真实工程里,会发现“结构”这个词的含义又被拓宽了。它不再只是内存布局,还包括代码的组织方式、文件之间的依赖关系、甚至团队协作的规范。最常见的诉求就是“项目目录结构怎么组织”。FastAPI项目的目录为什么会有 routers/、models/、schemas/、services/ 这种分层?因为代码也是一种需要被维护的数据,良好的目录结构等于给未来的自己和同事画了一张地图,降低寻找和修改的认知成本。
前端WPF里的数据绑定,把界面上一个文本框直接绑定到底层对象的某个属性上,本质上也是一种“结构映射约定”。界面控件有它的层级结构,数据对象有它的属性结构,绑定就是在这两者之间建立对应关系。你改了后台数据,界面自动刷新,省掉的“手动给控件赋值”那堆代码,全部是结构约定换来的便利。
还有近期特别热的知识库领域,三种“结构”经常容易被新人混淆:KG知识图谱是显式的实体关系网络,RAG知识库是向量化索引加原文检索的结合,而最传统的结构化知识库就是SQL表。选哪种,不取决于哪个更高级,而取决于你的业务到底需要精准的实体关系,还是灵活的语义匹配,还是稳定的字段查询。结构选择永远跟着业务语义走,而不是相反。
4. 为什么要同时讲数据和结构:几个真实场景
4.1 工业数据采集:数据是流的,结构是协议
如果你接触过数据采集卡、PLC、传感器、数控机床这些设备,你会理解“数据和结构”不是课本概念,而是每天在生死线上徘徊的事。Modbus协议和OPC UA协议是工业自动化的两个典型代表,它们做的事情本质上是一致的:把一堆设备的运行状态数据,按照约定好的格式放进报文里发送出来。数据采集端拿到的是纯字节流,但如果不知道寄存器地址、数据长度、数据类型(是16位整数还是32位浮点)、字节序(是大端还是小端),那字节流就是一堆乱码。
这就像有人给你寄了一封用摩斯密码写的信,你光拿到信纸没用,还得有那张对照表。协议就是数据的对照表。我做项目时曾经排查过一个温度读数异常的问题,现场同事反复检查PLC程序没发现毛病,后来才发现采集端把16位无符号整数按16位有符号整数去解析了,一旦温度超过某阈值就变成负数。这不是硬件故障,是结构的假设被打脸了。
所以采集系统里最重要的不是先怀疑设备坏了,而是先确认帧结构、字段偏移、精度换算这三个环节。结构上错一位,后面所有报警、大屏、报表全是错的,而且会错得很稳定、很隐蔽。
4.2 数据绑定、Excel统计与API:结构让工具替你干活
很多非程序员的办公场景,其实也在悄无声息地使用“结构”。Excel统计里,你要“在同一列中统计含某个关键词的对应数据求和”,用SUMIF/SUMIFS就能做到。这个公式为什么成立?因为这个范围、条件、求和区域,全部是基于二维表的结构约定。你告诉Excel“A列是产品名,B列是销售额”,它才能按条件把B列的数加起来。如果表头乱写、行列错位,公式再对也出不了正确结果。
再比如Web开发里常见的FastAPI项目,后端接口返回一段JSON给前端,前端拿到JSON绑定到表格组件上。这个过程能自动完成,靠的还是结构:字段名和组件列名一一对应。你要调用某财经网站或电商平台的股票价格/商品数据接口,第一步永远是读它的接口文档,看返回体里到底是 data 字段里嵌着列表,还是 list 字段里嵌着字典。所谓“对接”,就是让自己的代码服从对方的结构约定。
我经常打一个比方:数据像水,结构像管道。水人人都有,但只有修好了管道,水才能流到该去的地方。你堆积了大量数据却不对它做结构设计,等于挖了个蓄水池却忘了装阀门和水管,要用的时候取不出来,更送不出去。
4.3 大数据与知识库:结构化的程度决定了沉淀的深度
企业积累数据的速度越来越快,但能不能把数据“养”起来,关键还是看结构。数据备份与恢复这件事,听着是个运维活,本质上也是在保卫结构——备份文件时连表结构、索引定义、约束条件一起备份,恢复出来的数据才能继续被应用正确解读。如果只倒数据不背结构,恢复完的程序可能连启动都起不来。
最近几年火的RAG知识库、KG知识图谱,本质上也是在解决“非结构化数据怎么变成可检索的资产”这个铁问题。你好几年前的公司内部资料库可能只是几千个PDF、Word文档,内容都在,但搜索引擎一搜就搜出一堆噪音,因为它不理解文档之间的结构关系。RAG知识库会把文档切片、向量化、建立索引,这是给非结构化数据重新披上一层“检索结构”;KG知识图谱则更进一步,把实体和关系显式地连点成网。没有这些结构加工,再多的文档也只是安静的硬盘垃圾——数据量越大,反而越难用。
你可以观察到一个规律:越强调“数据资产化”“可复用”的场景,越依赖结构设计;越强调“先记下来再说”的场景,结构越容易欠费。 这也是很多数据仓库和指标平台建设失败的通病,大家只忙着灌数据,却忘了在设计表模型、维度建模上花力气。
5. 动手环节:从零定义一段“带结构的数据”
5.1 先画表,再写结构体,再做建表语句
理论讲了一堆,不如动手做一遍。我就以“记录某车队一个月内每次加油的数据”为例,带你完整走一遍“从现实到结构”的过程。
第一步,先在纸上或者Excel里画出这五列表头:日期、车牌号、加油量(升)、单价(元/升)、里程数(公里)。然后填上真实数据:
| 日期 | 车牌号 | 加油量(升) | 单价(元/升) | 里程数(km) |
|---|---|---|---|---|
| 2025-01-05 | 京A12345 | 40.5 | 7.68 | 12600 |
| 2025-01-18 | 京A12345 | 42.0 | 7.52 | 12980 |
| 2025-01-30 | 京B67890 | 38.8 | 7.55 | 85400 |
第二步,把这张枚举表翻译成C语言结构体:
c复制typedef struct RefuelRecord {
char date[11]; // 2025-01-05
char plate[16]; // 京A12345
float liters; // 加油量
float unit_price; // 单价
float mileage; // 里程数
} RefuelRecord;
第三步,再用SQL建表表达式表达一遍:
sql复制CREATE TABLE refuel_records (
id INT PRIMARY KEY AUTO_INCREMENT,
date DATE NOT NULL,
plate VARCHAR(16) NOT NULL,
liters DECIMAL(8,2) NOT NULL,
unit_price DECIMAL(8,2) NOT NULL,
mileage DECIMAL(10,1) NOT NULL
);
你会发现,Excel表头、结构体成员、SQL列定义,三者长得不一样,但对应的完全是同一个东西。这也是我希望你建立的第一个核心认知:不管用什么载体,“定义结构”都是同一件事——把字段名、类型、约束说清楚。 有了这个认知,你学任何新语言时,只需要找到它的“定义结构”语法,立刻就能上手存数据。
5.2 用Pandas创建一份结构化数据
如果你是做数据分析的,大概率会更常用到Python。来看Pandas怎么实现上面这张表,它把“表头”和“类型”直接隐藏在DataFrame对象里:
python复制import pandas as pd
df = pd.DataFrame({
"date": ["2025-01-05", "2025-01-18", "2025-01-30"],
"plate": ["京A12345", "京A12345", "京B67890"],
"liters": [40.5, 42.0, 38.8],
"unit_price": [7.68, 7.52, 7.55],
"mileage": [12600, 12980, 85400],
})
print(df.dtypes)
# date object
# plate object
# liters float64
# unit_price float64
# mileage int64
df.dtypes 输出每个字段的类型,这就是Pandas里的“结构说明书”。后续你选择某一列求和、按车牌分组平均、筛选里程数大于1万的行,全都依赖这一套结构。
python复制# 统计每辆车的总加油量
print(df.groupby("plate")["liters"].sum())
# 筛选里程数大于1万的所有记录
print(df[df["mileage"] > 10000])
踩坑提醒:如果你用 pd.read_csv() 读一个Excel导出文件,日期列经常被读成字符串(object类型),而不是datetime类型,这样你想按月份筛选就死活做不对。解决方案就是你得显式声明类型:
python复制df["date"] = pd.to_datetime(df["date"])
这一步其实就是“修复结构”。很多数据分析跑出来的结果不对,不是算错了,而是结构的类型不对。 比如把含空格的字符串当成数值参与计算,NaN满天飞;把本应保留两位小数的价格读成浮点数,最后一步求和差了0.1元。数据类型是一切计算的立足点,检查数据的第一件事永远是 dtypes。
5.3 一个最小演示:把无结构文本变成结构
最后给你看一个“从无结构到有结构”的过程,这也是很多数据工程师每天在做的事情。假设系统日志里每条长这样:
code复制2025-02-01 10:23:45 ERROR 用户ID=10086 支付超时
2025-02-01 10:25:12 WARN 用户ID=10087 库存不足
2025-02-01 10:30:01 ERROR 用户ID=10088 网络断开
它看起来有规律,但没有明确的分隔符和字段定义,属于半结构化文本。你可以用字符串分割或正则提取,把它拆成时间、级别、用户ID、消息四列,得到一个干净的结构化DataFrame,之后再做按时段统计告警量、按用户聚合失败次数,就十分顺畅了。
这件事给我们的启发是:结构不是天生就有的,而是人赋予的。 你拿到任何一批乱糟糟的数据,先别急着清洗,先定义目标结构:我要几列?每列什么含义?什么类型?唯一性怎么保证?确定完了,清洗规则就跟着出来了。否则你刚把A列的格式清理好,B列又冒出一堆空值,东一榔头西一棒子,永远清不干净。
5.4 当结构假设被打破:一个注册表读取失败的教训
聊一个更冷门但仍值得说的真实案例。有次环境检查,某台Windows服务器在采集系统性能指标时,一直提示“无法读取 USBPerf\Performance 注册表项下的 'First Counter' 值”,程序随之返回错误状态。表面上这像是注册表权限问题,实际上它的本质是:我们预设好的“性能计数器结构”在这个具体环境中不存在或不完整,于是整个数据采集流程崩溃了。
这给我们的启示非常清晰:当系统吐出一个错误,首先要检查的是我们和系统之间是否有共同的结构约定。 有可能注册表项被清理、驱动未安装、权限受限,任何一个环节破坏结构,数据就变成了“无结构”的状态。生产环境中遇到这类问题,千万别反复重启电脑,先检查结构依赖是否成立。对应到数据行业里,类似的案例就是接口升级后返回字段变了,老代码解析全部报错——你以为是代码问题,其实是对方的“结构契约”改了。
6. 常见问题与经验小结
6.1 为什么学了数组还要学链表,是不是多此一举?
这是新手最喜欢问的问题。答案是你学的不只是两种结构,而是两种代价模型。数组擅长随机访问,但插入删除要移动大量元素;链表擅长插入删除,但随机访问要遍历。真实程序里没有银弹,你设计缓存、队列、索引时,就是在不同代价模型之间做选择。数据结构课一遍遍强调复杂度,就是让你具备“看到操作特征就想到合适结构”的本能。
6.2 数据结构实验报告怎么写才算有价值?
如果只是把教材上的代码抄一遍然后截图,那这份报告毫无意义。有价值的实验报告至少包含三块内容:一是复杂度的推导过程,为什么插入是O(1)、查找是O(n),要有自己的论证;二是边界条件测试,链表为空怎么办、双端队列从只有单个元素的一端删除怎么办;三是和另一种结构的对比实验,数组和链表在同一操作下的耗时差距,用数据说话。写报告的过程就是逼自己把“为什么选这个结构”讲清楚。
6.3 个人小项目也要严格设计结构吗?
要设计,但记住一个原则:结构服务于变化速度。小程序、脚本、临时分析,可以用字典、JSON糊里糊涂先跑通;但一旦你发现某个字段在三个地方重复用到,某个逻辑改了以后要连带改多条代码,这就是结构需要升级的信号。我自己踩过很多次这种坑:一开始图省事用列表硬存所有数据,后来每次都要通过下标访问,代码又丑又容易错,重构成一排结构体或DataFrame后,整个代码都顺了。结构建模的投入要跟着项目成长,别过度设计,也别始终不设。
6.4 数据和结构,到底哪个优先?
我的建议是先抓数据的雏形,再及时补结构。你要做一个故障告警系统,不可能一开始就设计好二十张表,肯定是先拿一批日志、几个字段验证完告警逻辑,再倒逼出正式的表结构。结构跟着真实数据不断演进,才不会被空想出来的完美设计拖垮。但反过来,当你发现数据已经在好几个地方被复制、被修改、被重新解释,那你就必须停下脚步,把结构定义固化下来。二者是迭代关系,不是先后关系。
写在最后:把数据拿到手,先问六个问题
最后分享一个我用到现在的笨办法。不管从哪拿到的数据,Excel、接口、数据库、日志、传感器流,我都会先问自己六个问题:谁产生的数据?什么格式?数据量多大?更新频率多快?给谁用?怎么校验对错?这六个问题一问完,结构基本就自己浮出水面了。格式不行就转格式,缺失了就补约束,太杂乱就先按目标字段去清洗。
这门“第一章”希望帮你建立的就是这个习惯:看到数据,先想结构,再谈分析。接下来如果继续顺着这个系列往下走,我们就可以带着这份理解,去逐个拆解数组、链表、栈、队列、树和图,看看它们在计算机世界里是怎么落地生根的。数据是原料,结构是骨架,把这两个根扎稳了,后面学什么都快。
