下载工作台
Go 编程实战

数组、切片与 map

试读上半部分 · 解锁后可读全文

第 4 章 · 数组、切片与 map

本章目标:掌握 slice 动态数组、appendcopy 与容量 cap;熟练使用 map 增删查改;理解 slice 共享底层数组 的陷阱;在 toolkit-go 用 map 维护 SKU 库存、用 slice 表示订单行;完成商品库存 map 练习;对照 java-dev ch04 集合与 python-dev ch04 列表/字典。

学时建议:3~4 小时(含 1.5 小时跟练)

前置:完成 go-dev ch03;建议已学 java-dev ch04 数组与集合。


4.1 场景说明:库存与订单行

toolkit-go~/learn-go/toolkit-go,模块 example.com/toolkit-go)模拟商城库存与购物车:

结构Go 类型toolkit-go 用途
固定测试数据数组 [N]T基准样例、常量表
动态订单行[]LineItem订单计算、日志聚合
SKU → 库存map[string]int商品 slug 库存表
slug 去重map[string]struct{}集合语义
toolkit-go/
├── internal/
│   ├── inventory/
│   │   └── stock.go         # Stock 结构体 + 扣减
│   ├── model/
│   │   └── line.go          # LineItem slice 操作
│   └── calc/
│       └── order.go         # ch03 已有
└── cmd/toolkit/main.go
JavaGoPython
int[] 定长[N]int 定长无原生定长数组
ArrayList<T>[]T slicelist
HashMap<K,V>map[K]Vdict

4.2 数组 vs 切片

// 数组:长度是类型一部分,值类型
var arr [3]int = [3]int{1, 2, 3}
arr2 := [...]int{4, 5, 6} // 编译器推断长度

// 切片:动态视图,引用底层数组
s := []int{1, 2, 3}
s = append(s, 4)

fmt.Println(len(s), cap(s)) // 长度 vs 容量
概念说明
len(s)当前元素个数
cap(s)底层数组容量,超出则分配新数组并拷贝
s[low:high]切片表达式,共享底层
s[low:high:max]三索引切片,限制 cap

共享底层陷阱(必考)

orig := []int{1, 2, 3, 4}
sub := orig[1:3] // [2, 3],cap 延伸到 orig 末尾
sub[0] = 99
// orig 变为 [1, 99, 3, 4]

安全拷贝:

dup := make([]int, len(orig))
copy(dup, orig)

// 或
dup2 := append([]int(nil), orig...)

4.3 append 与预分配

// internal/model/line.go
package model

type LineItem struct {
	Slug     string
	Price    int64 // 分
	Quantity int
}

func AppendLine(lines []LineItem, item LineItem) []LineItem {
	return append(lines, item)
}

// 性能友好:已知大小时预分配
func NewCart(capacity int) []LineItem {
	return make([]LineItem, 0, capacity)
}

func TotalQuantity(lines []LineItem) int {
	var n int
	for _, ln := range lines {
		n += ln.Quantity
	}
	return n
}
写法适用
append(s, x)动态增长
make([]T, 0, cap)减少扩容次数
s = s[:0]复用 slice 清空(池化场景)
append(s, s2...)合并两个 slice

append 陷阱:若 append 触发扩容,可能不再共享原底层数组;未扩容则仍共享。

a := []int{1, 2, 3}
b := append(a, 4) // 可能共享或新数组,勿依赖未文档化行为
_ = b

4.4 map 基本操作

// internal/inventory/stock.go
package inventory

import "fmt"

type Stock struct {
	items map[string]int // slug -> 库存
}

func NewStock() *Stock {
	return &Stock{items: make(map[string]int)}
}

func (s *Stock) Set(slug string, qty int) {
	s.items[slug] = qty
}

func (s *Stock) Get(slug string) (int, bool) {
	qty, ok := s.items[slug]
	return qty, ok
}

func (s *Stock) Delete(slug string) {
	delete(s.items, slug)
}

func (s *Stock) All() map[string]int {
	out := make(map[string]int, len(s.items))
	for k, v := range s.items {
		out[k] = v
	}
	return out
}
操作语法注意
创建make(map[K]V)未 make 的 map 不能写入
读取v, ok := m[k]区分零值与不存在
写入m[k] = v
删除delete(m, k)安全,不存在也不 panic
遍历for k, v := range m顺序随机
长度len(m)

4.5 商品库存 map 练习:扣减与合并

// internal/inventory/stock.go(续)
func (s *Stock) Deduct(slug string, n int) error {
	if n <= 0 {
		return fmt.Errorf("invalid deduct amount: %d", n)
	}
	qty, ok := s.Get(slug)
	if !ok {
		return fmt.Errorf("sku not found: %s", slug)
	}
	if qty < n {
		return fmt.Errorf("insufficient stock for %s: have %d need %d", slug, qty, n)
	}
	s.items[slug] = qty - n
	return nil
}

func MergeStock(base, delta map[string]int) map[string]int {
	out := make(map[string]int, len(base)+len(delta))
	for k, v := range base {
		out[k] = v
	}
	for k, v := range delta {
		out[k] += v
	}
	return out
}
步骤业务函数
初始化库存上架 100 件Set("go-book", 100)
下单扣减买 2 件Deduct("go-book", 2)
库存不足买 999 件返回 error
合并快照增量补货MergeStock

gin-web 中 Redis hash 缓存库存概念类似(ch09 并发安全会展开)。


4.6 slice + struct 订单行实战

func UniqueSlugs(lines []model.LineItem) []string {
	seen := make(map[string]struct{})
	out := make([]string, 0, len(lines))
	for _, ln := range lines {
		if _, ok := seen[ln.Slug]; ok {
			continue
		}
		seen[ln.Slug] = struct{}{}
		out = append(out, ln.Slug)
	}
	return out
}

func FilterPositiveQty(lines []model.LineItem) []model.LineItem {
	out := make([]model.LineItem, 0, len(lines))
	for _, ln := range lines {
		if ln.Quantity > 0 {
			out = append(out, ln)
		}
	}
	return out
}

4.7 排序预告(ch14)

slice 可用 sort.Slice 排序(ch14 详讲):

import "sort"

// 预习:按 slug 字典序
sort.Slice(lines, func(i, j int) bool {
	return lines[i].Slug < lines[j].Slug
})

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。