编程基础篇 · AI 背后的数据结构

数组:你聊的每句话都躺在里面

message list 就是一个数组:对话历史怎么排队、上下文截断为什么掐头不掐尾;顺便看数组中间插一条数据有多贵

本页解决的问题

先给结论

「数组:你聊的每句话都躺在里面」要解决的关键问题是什么?

message list 就是一个数组:对话历史怎么排队、上下文截断为什么掐头不掐尾;顺便看数组中间插一条数据有多贵

判断标准

让这个结论先证明自己值得留下。 把这一页当成决策工具,而不是需要背下来的定义。把概念连到一个真实任务、一个可观察结果,以及一个能改变你判断的失败上。

下一步

写下一个问题:试完这个方法后,你能用什么证据回答它?

常见误区

结论听起来很完整,却没有检查最关键的假设。

先玩 · 你的对话正躺在一排格子里

下面就是一个 message list:每条消息占一个格子,格子上方是它的索引(编号,从 0 开始数——程序员的老习惯)。点「发一条消息」,看新消息落在哪;再拖「上下文窗口」滑块,留意哪些格子变灰了、哪个格子永远不灰。

system(📌 钉住) user(你) assistant(AI) 底下有蓝条 = 在上下文窗口里
4 条
这就是「聊久了忘事」的全部真相。模型一次能读的内容有上限(上下文窗口),装不下时就得扔。扔哪头?掐头不掐尾——最早的闲聊先扔,最近几句必须留着,不然它连你刚说什么都不知道。唯独 [0] 号格子的 system 提示词被钉住了:它是人设和规矩(「你是一个贴心的助理」),扔了它,AI 就忘了自己是谁。所以「忘事」不是玄学,就是数组切片:留下 system + 最近 K 条,其余不再送进模型。
第二局 · 往中间插一条,有多贵?

数组的格子在内存里是紧挨着排的,中间不许有空位。这带来一个麻烦:想往中间塞一个新元素,右边的所有元素都得挨个往右搬一格给它腾地方。点任意一个格子,在它的位置插入一颗 ⭐,盯着「搬动次数」;再点「在末尾追加」对比一下。

👇 点格子 = 在这个位置插入 ⭐(越靠左,右边要搬家的越多) 搬动次数0
先点一个靠左的格子试试,比如 [1]
看出规律了吗?末尾追加永远只要 1 步,中间插入要搬动一大片——格子越多、插得越靠前,搬得越狠。这就是为什么对话历史被设计成只往后长(append-only):每条新消息 push 到末尾,谁也不搬家,快且省。你从来没见过哪个聊天软件让你「把一句话插到十分钟前」,不是产品经理没想到,是这么干真的贵。
数组的看家本领(和它的软肋)
🎯

看家本领:按编号直达

想拿第 3 条消息?messages[3]不用从头数,一步到位。因为格子紧挨着排,编号本身就是地址——这叫 O(1),翻译成人话是「不管数组多长,耗时都一样」。按位置取数据,数组是所有收纳方式里最快的,没有之一。

⚖️

软肋:中间插入贵

你刚才亲手搬过了。顺带认识一个亲戚:链表——它中间插入很便宜(改两根「下一个是谁」的指针就行),代价是失去了按编号直达,找第 100 个得从头挨个走。没有全能的收纳方式,只有取舍。对话这种「只追加、常整段读」的场景,数组完胜,所以 message list 用它。

「先玩 · 你的对话正躺在一排格子里」为什么要看操作

「下面就是一个 message list:每条消息占一个格子,格子上方是它的 索引 (编号,从 0 开始数——程序员的老习惯)。点 「发一条消息」 ,看新消息落在哪;再拖 「上下文窗口」滑块 ,留意哪些格子变灰了、哪个格子永远不灰」把结构落到了一个具体动作。这里真正要比较的不是名词谁更高级,而是数据如何被放置,以及最常发生的操作需要走多远。

读懂结构,要同时看访问方式和变化方式

「数组的格子在内存里是 紧挨着 排的,中间不许有空位。这带来一个麻烦:想往中间塞一个新元素,右边的所有元素都得 挨个往右搬一格 给它腾地方。」揭示了一个容易被忽略的取舍:按位置读取、按键查找、从两端进出、插入新元素和遍历关系,适合的组织方式并不相同。一个结构在某个操作上很快,不代表它在所有操作上都快。

  • message list 就是数组 :一排编了号的格子,每条消息躺一格,索引从 0 开始
  • 索引直达 O(1) :按位置取数据,数组是最快的收纳方式
  • 上下文截断 = 数组切片 :留 system + 最近 K 条,「掐头不掐尾」,这就是聊久了忘事的真相

把规模和更新频率一起算进去

实践时可以把「你刚才亲手搬过了。顺带认识一个亲戚: 链表 ——它中间插入很便宜(改两根「下一个是谁」的指针就行),代价是失去了按编号直达,找第 100 个得从头挨个走。」当作边界提醒:先写下数据量、最常用的操作和允许的延迟,再看 AI 给出的结构是否真的匹配。

从「先玩 · 你的对话正躺在一排格子里」走到「第二局 · 往中间插一条,有多贵」

「先玩 · 你的对话正躺在一排格子里」先把问题落在「下面就是一个 message list:每条消息占一个格子,格子上方是它的 索引 (编号,从 0 开始数——程序员的老习惯)。点 「发一条消息」 ,看新消息落在哪;再拖 「上下文窗口」滑块 ,留意哪些格子变灰了、哪个格子永远不灰」上;到了「第二局 · 往中间插一条,有多贵」,讨论继续推进到「数组的格子在内存里是 紧挨着 排的,中间不许有空位。这带来一个麻烦:想往中间塞一个新元素,右边的所有元素都得 挨个往右搬一格 给它腾地方。 点任意一个格子 ,在它的位置插入一颗 ⭐,盯着「搬动次数」;再点「在末尾追加」对比一下」。两段连起来,重点就不只是记住一个结论,而是看清它成立所依赖的条件。

把这条判断带到下一个场景

遇到一个新的数据结构时,不要从定义开始背。先写出最频繁的操作,再估计数据量和更新方式,最后检查结构是否让这三个条件同时成立。

  • 「先玩 · 你的对话正躺在一排格子里」:下面就是一个 message list:每条消息占一个格子,格子上方是它的 索引 (编号,从 0 开始数——程序员的老习惯)。点 「发一条消息」 ,看新消息落在哪;再拖 「上下文窗口」滑块 ,留意哪些格子变灰了、哪个格子永远不灰
  • 「第二局 · 往中间插一条,有多贵」:数组的格子在内存里是 紧挨着 排的,中间不许有空位。这带来一个麻烦:想往中间塞一个新元素,右边的所有元素都得 挨个往右搬一格 给它腾地方。 点任意一个格子 ,在它的位置插入一颗 ⭐,盯着「搬动次数」;再点「在末尾追加」对比一下
  • 「最后的要点」:收纳方式都是取舍 :链表中间插得快但没了直达——场景决定选择

最后的「最后的要点」把讨论落到「收纳方式都是取舍 :链表中间插得快但没了直达——场景决定选择」。回看这条线索时,最值得保留的是:当输入、规模或风险改变,哪些判断需要重新做一遍。

✅ 这一课想和你分享的

  • message list 就是数组:一排编了号的格子,每条消息躺一格,索引从 0 开始
  • 索引直达 O(1):按位置取数据,数组是最快的收纳方式
  • 上下文截断 = 数组切片:留 system + 最近 K 条,「掐头不掐尾」,这就是聊久了忘事的真相
  • 中间插入贵、末尾追加便宜:所以对话历史只往后长(append-only)
  • 收纳方式都是取舍:链表中间插得快但没了直达——场景决定选择
标记为已学完 阅读进度会自动记录
← 上一篇下一篇 →

继续阅读

同一条线上的下一篇。

文章讨论

读到这里,留下一个判断。

把刚想明白的地方、还没想通的问题,留给下一位一起学习的人。

正在讨论 数组:你聊的每句话都躺在里面 AI 背后的数据结构
3条讨论文章讨论 · 与共学社区同步
在共学社区查看
AM
Asha Morgan内容编辑
观点实践记录

我把这篇文章里的一个判断改写成了今天可以验证的小实验。比记住结论更有用的是,知道下一步要观察什么。

文章讨论7 有帮助
LH
Lin Harper独立开发者
观点观点

读完以后我先回头找它成立的条件,而不是直接把方法搬进项目。这个顺序让后面的取舍清楚很多。

文章讨论5 有帮助
KM
Kiki Moore产品运营
问题问题

如果把这个判断放到真实工作里,最先需要补的约束是什么?我想知道从阅读到第一次实践之间,哪一步最值得先做。

文章讨论4 有帮助