文章目录
- [Go 零基础数据结构:顺序表(像「排抽屉」一样学增删改查)](#Go 零基础数据结构:顺序表(像「排抽屉」一样学增删改查))
-
- [一、先复习:顺序表 = 连续的抽屉](#一、先复习:顺序表 = 连续的抽屉)
- [二、顺序表的 4 个核心操作(像玩抽屉一样简单)](#二、顺序表的 4 个核心操作(像玩抽屉一样简单))
-
- [1. 查:直接拿(最快的操作)](#1. 查:直接拿(最快的操作))
- [2. 增:分「末尾加」和「中间插」](#2. 增:分「末尾加」和「中间插」)
-
- [① 末尾加:直接塞(最简单的增)](#① 末尾加:直接塞(最简单的增))
- [② 中间插:先挪后塞(要费点劲)](#② 中间插:先挪后塞(要费点劲))
- [3. 删:分「末尾删」和「中间删」](#3. 删:分「末尾删」和「中间删」)
-
- [① 末尾删:直接空着(最简单的删)](#① 末尾删:直接空着(最简单的删))
- [② 中间删:先塞后空(要费点劲)](#② 中间删:先塞后空(要费点劲))
- [4. 改:直接换(很简单)](#4. 改:直接换(很简单))
- 三、顺序表的扩容:抽屉不够了怎么办?
-
- [1. 什么是扩容?(生活例子)](#1. 什么是扩容?(生活例子))
- [2. 扩容的过程(像搬家一样)](#2. 扩容的过程(像搬家一样))
- [3. Go 里的扩容(不用懂代码,看效果)](#3. Go 里的扩容(不用懂代码,看效果))
- [四、垃圾回收机制和 STW:空抽屉怎么处理?](#四、垃圾回收机制和 STW:空抽屉怎么处理?)
-
- [1. 垃圾回收:把空抽屉收走再利用](#1. 垃圾回收:把空抽屉收走再利用)
- [2. STW:回收时「暂停一下」](#2. STW:回收时「暂停一下」)
- 五、顺序表的优缺点(零基础秒懂)
-
- [优点:查、改、末尾增删 都很快](#优点:查、改、末尾增删 都很快)
- [缺点:中间增删 比较慢](#缺点:中间增删 比较慢)
- 与扩容、垃圾回收相关的考量
- 六、和「数组」的关系(提前透底)
- 七、零基础总结(5句话记牢)
- 预告:下一篇讲链接表
Go 零基础数据结构:顺序表(像「排抽屉」一样学增删改查)
大家好~上一篇我们知道了「顺序表是把元素塞在连续的抽屉里」。今天我们用**「抽屉」的生活例子**,把顺序表的「增、删、改、查」讲透,同时引入扩容、垃圾回收机制和 STW 的概念,全程大白话,零基础也能跟着做!
一、先复习:顺序表 = 连续的抽屉
再确认下核心概念:
- 内存 = 一排连在一起的抽屉(每个抽屉是一个「存储格子」)
- 顺序表 = 把元素紧挨着塞进连续的抽屉,中间没有空隙
比如存 10、20、30 的顺序表长这样:
┌───┬───┬───┬───┬───┐
│10 │20 │30 │空 │空 │ ← 连续的抽屉,前3个装满,后2个空着
└───┴───┴───┴───┴───┘
编号0 编号1 编号2 编号3 编号4 ← 抽屉的「编号」(后面会用到)
二、顺序表的 4 个核心操作(像玩抽屉一样简单)
我们用「抽屉存数字」的例子,逐个讲「增、删、改、查」。
1. 查:直接拿(最快的操作)
需求 :想拿第2个抽屉里的元素(比如 20)。
操作:直接找到「抽屉编号1」(注意:编号从0开始),打开就能拿到。
这就是顺序表的**「直接访问」**------像你教室座位是连续的,想找第3排的同学,直接走到第3排就行,不用翻前面的座位。
Go 代码体验(不用懂语法,看效果):
go
// 顺序表(连续抽屉)存了10、20、30
seqList := []int{10, 20, 30}
// 拿编号1的抽屉里的元素
fmt.Println("编号1的元素:", seqList[1]) // 输出:20
2. 增:分「末尾加」和「中间插」
① 末尾加:直接塞(最简单的增)
需求 :在现有元素后面加一个 40。
操作:找到最后一个装满的抽屉(编号2),直接塞进下一个空抽屉(编号3)。
因为不用挪其他元素,所以很快!
加之前:┌───┬───┬───┬───┬───┐
│10 │20 │30 │空 │空 │
└───┴───┴───┴───┴───┘
加之后:┌───┬───┬───┬───┬───┐
│10 │20 │30 │40 │空 │
└───┴───┴───┴───┴───┘
Go 代码体验:
go
seqList := []int{10, 20, 30}
// 末尾加40
seqList = append(seqList, 40)
fmt.Println("末尾加40后:", seqList) // 输出:[10 20 30 40]
② 中间插:先挪后塞(要费点劲)
需求 :在 10 和 20 中间插一个 15。
操作:
-
先把「编号1、2、3」的元素(20、30、40)往后挪一个抽屉,腾出空位;
-
把
15塞进空出来的「编号1」抽屉。挪之前:┌───┬───┬───┬───┬───┐
│10 │20 │30 │40 │空 │
└───┴───┴───┴───┴───┘挪之后:┌───┬───┬───┬───┬───┐
│10 │20 │30 │40 │40 │ ← 20、30、40往后挪
└───┴───┴───┴───┴───┘插之后:┌───┬───┬───┬───┬───┐
│10 │15 │20 │30 │40 │
└───┴───┴───┴───┴───┘
因为要挪后面的元素,所以元素越多,挪的时间越长。
Go 代码体验:
go
seqList := []int{10, 20, 30, 40}
// 在编号1的位置插15
seqList = append(seqList[:1], append([]int{15}, seqList[1:]...)...)
fmt.Println("插15后:", seqList) // 输出:[10 15 20 30 40]
3. 删:分「末尾删」和「中间删」
① 末尾删:直接空着(最简单的删)
需求 :删掉最后一个元素 40。
操作:直接把最后一个抽屉(编号4)标记为「空」,不用动其他元素。
删之前:┌───┬───┬───┬───┬───┐
│10 │15 │20 │30 │40 │
└───┴───┴───┴───┴───┘
删之后:┌───┬───┬───┬───┬───┐
│10 │15 │20 │30 │空 │
└───┴───┴───┴───┴───┘
Go 代码体验:
go
seqList := []int{10, 15, 20, 30, 40}
// 删最后一个元素
seqList = seqList[:len(seqList)-1]
fmt.Println("删最后一个后:", seqList) // 输出:[10 15 20 30]
② 中间删:先塞后空(要费点劲)
需求 :删掉中间的 15。
操作:
-
把「编号2、3」的元素(20、30)往前挪一个抽屉 ,补上
15空出来的位置; -
把最后一个抽屉标记为「空」。
删之前:┌───┬───┬───┬───┬───┐
│10 │15 │20 │30 │空 │
└───┴───┴───┴───┴───┘挪之后:┌───┬───┬───┬───┬───┐
│10 │20 │30 │30 │空 │ ← 20、30往前挪
└───┴───┴───┴───┴───┘删之后:┌───┬───┬───┬───┬───┐
│10 │20 │30 │空 │空 │
└───┴───┴───┴───┴───┘
和「中间插」一样,元素越多,挪的时间越长。
Go 代码体验:
go
seqList := []int{10, 15, 20, 30}
// 删编号1的元素
seqList = append(seqList[:1], seqList[2:]...)
fmt.Println("删15后:", seqList) // 输出:[10 20 30]
4. 改:直接换(很简单)
需求 :把编号2的 30 改成 35。
操作 :找到编号2的抽屉,把里面的 30 拿出来,换成 35 就行------不用动其他元素。
Go 代码体验:
go
seqList := []int{10, 20, 30}
// 把编号2的元素改成35
seqList[2] = 35
fmt.Println("改之后:", seqList) // 输出:[10 20 35]
三、顺序表的扩容:抽屉不够了怎么办?
1. 什么是扩容?(生活例子)
你有 3 个连续抽屉,装满了 10、20、30,现在想加 40,但抽屉不够了------这时候需要**「加一组新的连续抽屉」**,把旧抽屉里的元素全搬过去,再把新元素塞进去。
这就是顺序表的**「扩容」**:当现有抽屉(容量)不够时,自动申请更大的连续抽屉,把旧数据搬过去。
2. 扩容的过程(像搬家一样)
比如原来有 3 个抽屉(容量3),现在要扩容到 6 个(通常扩为原来的 2 倍):
旧抽屉(容量3):
┌───┬───┬───┐
│10 │20 │30 │
└───┴───┴───┘
↓ 搬家:把旧抽屉的元素全搬到新抽屉
新抽屉(容量6):
┌───┬───┬───┬───┬───┬───┐
│10 │20 │30 │空 │空 │空 │
└───┴───┴───┴───┴───┴───┘
↓ 塞新元素40
新抽屉:
┌───┬───┬───┬───┬───┬───┐
│10 │20 │30 │40 │空 │空 │
└───┴───┴───┴───┴───┴───┘
3. Go 里的扩容(不用懂代码,看效果)
Go 的切片(顺序表)会自动扩容,比如原来容量是3,加第4个元素时会自动扩到6:
go
seqList := make([]int, 3, 3) // 初始化:3个元素,容量3
seqList[0], seqList[1], seqList[2] = 10, 20, 30
// 加第4个元素,触发扩容
seqList = append(seqList, 40)
fmt.Println("扩容后容量:", cap(seqList)) // 输出:6(扩为原来的2倍)
四、垃圾回收机制和 STW:空抽屉怎么处理?
当你删除元素后,抽屉会空出来(比如删了 15,编号1的抽屉空了)------这些「空抽屉」如果一直留着,会浪费内存,这时候就需要**「垃圾回收(GC)」**。
1. 垃圾回收:把空抽屉收走再利用
比如你有一堆空抽屉,不用了就把它们「收起来」,等需要新抽屉时再拿出来用------这就是垃圾回收的作用:自动回收不再用的内存(空抽屉),避免浪费。
2. STW:回收时「暂停一下」
但回收空抽屉时,得先「暂停所有操作」------不然你一边往抽屉里塞元素,一边收抽屉,容易乱。
这就是 Go 里的 STW(Stop The World):垃圾回收时,会短暂暂停程序的其他工作,等回收完空抽屉再继续。
不过不用怕,Go 的 STW 时间已经很短了(通常毫秒级),几乎感觉不到卡顿。
五、顺序表的优缺点(零基础秒懂)
优点:查、改、末尾增删 都很快
- 查/改:直接找编号,不用动其他元素;
- 末尾增删:不用挪元素,直接塞/直接空。
缺点:中间增删 比较慢
- 中间插/删都要挪后面的元素,元素越多,挪的时间越长。
与扩容、垃圾回收相关的考量
- 扩容开销:虽然扩容能解决空间不足问题,但扩容过程涉及内存申请和数据迁移,有一定性能开销。
- 垃圾回收与STW影响:垃圾回收是必要的内存管理手段,但 STW 可能会对程序的实时性有轻微影响,不过在现代优化下,这种影响通常较小。
六、和「数组」的关系(提前透底)
你可能听过「数组」,其实数组就是「固定长度的顺序表」:
- 顺序表:抽屉不够了可以自动加新的连续抽屉;
- 数组:抽屉数量固定,装满了就不能再加。
七、零基础总结(5句话记牢)
- 顺序表 = 元素塞在连续的抽屉里,有编号;
- 查、改、末尾增删很快,中间增删要挪元素;
- 顺序表扩容 = 抽屉不够了,搬去更大的连续抽屉(通常扩2倍);
- 垃圾回收 = 把不用的空抽屉收走,避免浪费内存;
- STW = 回收时短暂暂停程序,保证不乱。
预告:下一篇讲链接表
🎉今天咱们借助「抽屉」这个形象的比喻,把顺序表的增删改查琢磨得透透的,还顺带搞懂了扩容、垃圾回收机制以及 STW 这些稍微进阶点的概念,是不是成就感满满😎?
📢下一篇,咱们的探索之旅将转向链表的增删改查。链表和顺序表可不一样,它就像把珠子用线串起来,珠子在内存里可不是紧挨着放的哦🧐。链表的增删改查操作又有什么独特之处呢?为啥说它在某些场景下比顺序表更有优势?别急,下一篇博客,我会像今天一样,用最通俗易懂的方式,带你零基础搞懂链表的增删改查。
👉觉得今天的内容对你有帮助的话,记得点赞👍、收藏⭐,再点个关注📌,这样就不会错过下一篇精彩内容啦!咱们下一篇博客,不见不散~