以下是第五周复合类型(数组、切片与映射)的详细学习内容,按照第四周的深度要求设计:
第五周:复合类型与词频统计
一、复合类型详解
1. 数组(Array)
// 声明与初始化
var arr1 [3]int // 声明:[0 0 0]
arr2 := [3]string{"A", "B", "C"} // 显式初始化
arr3 := [...]int{1,2,3} // 编译器推断长度
// 特性:
// - 固定长度(长度是类型的一部分)
// - 值传递(赋值/传参产生副本)
// - 内存连续分配
// 操作示例:
arr := [5]int{10,20,30,40,50}
fmt.Println(arr[1]) // 20
arr[2] = 35 // 修改元素
fmt.Println(len(arr)) // 5(长度)
fmt.Println(cap(arr)) // 5(容量)
2. 切片(Slice)
// 创建方式
s1 := make([]int, 3, 5) // 类型,长度,容量
s2 := []float64{1.1, 2.2} // 字面量
s3 := arr[1:3] // 从数组切割
// 特性:
// - 动态大小(自动扩容)
// - 引用类型(底层数组视图)
// - 包含ptr/len/cap三元组
// 操作示例:
s := []int{10,20,30}
s = append(s, 40) // 扩容追加
copy(s[1:], s[2:]) // 删除元素(20)
s = s[:len(s)-1] // 新长度:[10 30 40]
3. 映射(Map)
// 初始化方式
m1 := make(map[string]int)
m2 := map[string]float64{
"pi": 3.1415,
"e": 2.7182,
}
// 特性:
// - 无序键值对集合
// - 引用类型
// - 线程不安全
// 操作示例:
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}
ages["Charlie"] = 28 // 添加/修改
delete(ages, "Bob") // 删除
if age, ok := ages["David"]; !ok {
fmt.Println("不存在")
}
二、词频统计任务
需求分析
- 输入一段英文文本
- 输出单词出现频率(不区分大小写)
- 过滤标点符号和数字
- 支持并发处理(可选优化)
版本1:基础实现
func wordFrequency(text string) map[string]int {
// 清理文本
cleaner := func(r rune) rune {
if unicode.IsLetter(r) {
return unicode.ToLower(r)
}
return ' ' // 非字母转为空格
}
cleaned := strings.Map(cleaner, text)
// 分割单词
words := strings.Fields(cleaned)
// 统计频率
freq := make(map[string]int)
for _, word := range words {
freq[word]++
}
return freq
}
版本2:并发优化
func concurrentWordFrequency(text string) map[string]int {
// 文本预处理(同上)
cleaner := func(r rune) rune {/* 同版本1 */}
cleaned := strings.Map(cleaner, text)
words := strings.Fields(cleaned)
// 并发处理
var mu sync.Mutex
var wg sync.WaitGroup
freq := make(map[string]int)
chunkSize := 1000
for i := 0; i < len(words); i += chunkSize {
end := i + chunkSize
if end > len(words) {
end = len(words)
}
chunk := words[i:end]
wg.Add(1)
go func(words []string) {
defer wg.Done()
localFreq := make(map[string]int)
for _, w := range words {
localFreq[w]++
}
mu.Lock()
for k, v := range localFreq {
freq[k] += v
}
mu.Unlock()
}(chunk)
}
wg.Wait()
return freq
}
三、测试与性能
1. 表格驱动测试
func TestWordFrequency(t *testing.T) {
tests := []struct {
input string
want map[string]int
}{
{
"Hello hello world",
map[string]int{"hello":2, "world":1},
},
{
"Go! 100% Go...",
map[string]int{"go":2},
},
{
"A man a plan a canal: Panama",
map[string]int{"a":3, "man":1, "plan":1, "canal":1, "panama":1},
},
}
for _, tt := range tests {
got := wordFrequency(tt.input)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("输入 %q\n期望 %v\n实际 %v", tt.input, tt.want, got)
}
}
}
2. 性能基准测试
# 运行测试
go test -bench . -benchmem
# 预期结果:
BenchmarkWordFrequency-8 5000 324521 ns/op 138920 B/op 502 allocs/op
BenchmarkConcurrent-8 8000 198745 ns/op 189654 B/op 1502 allocs/op
四、进阶技巧
1. 内存优化(预分配)
// 预估容量减少扩容
words := make([]string, 0, len(text)/5) // 按平均单词长度5预估
freq := make(map[string]int, 1000) // 预分配哈希表槽位
2. 正则表达式优化
// 使用正则分割更高效
re := regexp.MustCompile(`\W+`)
words := re.Split(strings.ToLower(text), -1)
3. 自定义排序输出
func sortedFrequency(freq map[string]int) []string {
type pair struct {
word string
count int
}
pairs := make([]pair, 0, len(freq))
for k, v := range freq {
pairs = append(pairs, pair{k, v})
}
sort.Slice(pairs, func(i, j int) bool {
return pairs[i].count > pairs[j].count
})
result := make([]string, len(pairs))
for i, p := range pairs {
result[i] = fmt.Sprintf("%s:%d", p.word, p.count)
}
return result
}
五、扩展练习
-
停用词过滤:
func filterStopWords(freq map[string]int, stopWords map[string]struct{}) { for w := range freq { if _, exists := stopWords[w]; exists { delete(freq, w) } } }
-
词云生成器:
func generateWordCloud(freq map[string]int, size int) []string { // 根据频率生成不同字号标记 // 示例:["GO(12)", "语言(8)", "并发(20)"] }
六、学习检查清单
- 能正确定义数组、切片和映射
- 理解切片扩容机制(容量翻倍策略)
- 会使用sync.Mutex处理并发map访问
- 能解释数组与切片的底层关系
- 理解map的哈希表实现原理
- 会进行切片的内存预分配优化
- 能处理Unicode字符的文本清洗
- 会编写并发安全的统计程序
通过本学习内容,您将掌握Go语言核心复合类型的特性和高效使用方法,并能够根据实际场景选择最佳数据结构。建议:
- 尝试处理1GB以上的大文本文件
- 比较不同分块策略对并发版本的影响
- 使用pprof分析内存分配热点
- 实现扩展练习中的词云可视化功能