Go 语言:并发编程 + 文件操作 + 正则表达式
- [一、Go 并发编程](#一、Go 并发编程)
-
- [1.1 核心基础概念](#1.1 核心基础概念)
- [1.2 Goroutine 协程](#1.2 Goroutine 协程)
-
- [1.2.1 基本语法](#1.2.1 基本语法)
- [1.2.2 协程等待:sync.WaitGroup](#1.2.2 协程等待:sync.WaitGroup)
- [1.2.3 踩坑](#1.2.3 踩坑)
- [1.3 Channel 通道](#1.3 Channel 通道)
-
- [1.3.1 分类与基础语法](#1.3.1 分类与基础语法)
-
- [1. 无缓冲通道(同步通道)](#1. 无缓冲通道(同步通道))
- [2. 有缓冲通道](#2. 有缓冲通道)
- [1.3.2 通道关闭 & 遍历](#1.3.2 通道关闭 & 遍历)
- [1.3.3 单向通道(只读/只写)](#1.3.3 单向通道(只读/只写))
- [1.3.4 通道踩坑](#1.3.4 通道踩坑)
- [1.4 select 多路监听](#1.4 select 多路监听)
- [1.5 同步锁(解决数据竞争)](#1.5 同步锁(解决数据竞争))
- [1.6 并发常见问题](#1.6 并发常见问题)
- 二、文件与目录操作
-
- [2.1 常用函数速查表](#2.1 常用函数速查表)
- [2.2 基础文件操作](#2.2 基础文件操作)
-
- [2.2.1 创建 & 打开 & 关闭文件](#2.2.1 创建 & 打开 & 关闭文件)
- [2.2.2 读取文件](#2.2.2 读取文件)
- [2.2.3 写入文件](#2.2.3 写入文件)
- [2.3 目录操作](#2.3 目录操作)
- [2.4 辅助操作](#2.4 辅助操作)
- [2.5 文件操作踩坑](#2.5 文件操作踩坑)
- 三、正则表达式
-
- [3.1 核心函数](#3.1 核心函数)
- [3.2 常用正则元字符](#3.2 常用正则元字符)
- [3.3 实战示例](#3.3 实战示例)
-
- [3.3.1 匹配校验(如账号、手机号)](#3.3.1 匹配校验(如账号、手机号))
- [3.3.2 提取所有匹配内容](#3.3.2 提取所有匹配内容)
- [3.3.3 正则替换](#3.3.3 正则替换)
- [3.3.4 字符串分割](#3.3.4 字符串分割)
- [3.4 正则踩坑](#3.4 正则踩坑)
- 四、速记表
本文整理 Go 并发编程、文件读写操作、正则表达式 三大实用模块,每个章节包含 语法说明、完整可运行示例、核心规则、高频踩坑 ,适配 VSCode + Go Modules 开发环境,兼顾基础使用与实战场景。
目录
- 并发编程(Goroutine、Channel、Select、同步工具)
- 文件与目录操作
- 正则表达式
- 知识点速记
一、Go 并发编程
Go 原生支持高并发,核心组件为 Goroutine(轻量级协程) 和 Channel(通道) ,搭配 select、sync 包实现协程同步、通信与资源安全,区别于传统线程模型,开销极低。
1.1 核心基础概念
- Goroutine :Go 运行时管理的轻量级执行单元,由
go关键字启动,数千上万个协程也可高效调度。 - Channel :协程之间专用通信管道,实现数据传递与同步,推荐以通信代替共享内存,规避多线程数据竞争。
- GMP 调度模型:Go 底层调度机制(G=协程、M=系统线程、P=逻辑处理器),开发者无需手动管理。
- 常见并发问题:死锁 、数据竞争。
1.2 Goroutine 协程
1.2.1 基本语法
使用 go 函数名(参数) 即可启动一个新协程,主协程(main)退出后,所有子协程会直接终止。
go
package (main)
import "fmt"
import "time"
// 普通函数
func sayHello() {
for i := 0; i < 5; i++ {
fmt.Println("Hello")
time.Sleep(100 * time.Millisecond)
}
}
func main() {
go sayHello() // 启动子协程
// 主协程循环
for i := 0; i < 5; i++ {
fmt.Println("Main")
time.Sleep(100 * time.Millisecond)
}
}
运行特点:输出顺序随机,两个协程交替执行。
1.2.2 协程等待:sync.WaitGroup
单纯 go 启动协程无法等待执行完毕,sync.WaitGroup 专门用于批量等待多个协程结束 。
核心方法:
Add(n):设置/增加等待计数器;Done():协程执行完毕,计数器减 1;Wait():阻塞主协程,直到计数器为 0。
示例
go
package main
import (
"fmt"
"sync"
)
func worker(id int, wg *sync.WaitGroup) {
defer wg.Done() // 函数结束自动计数减1
fmt.Printf("协程 %d 执行完毕\n", id)
}
func main() {
var wg sync.WaitGroup
// 启动3个协程
for i := 1; i <= 3; i++ {
wg.Add(1) // 计数器+1
go worker(i, &wg)
}
wg.Wait() // 等待所有协程完成
fmt.Println("所有协程执行结束")
}
1.2.3 踩坑
- 忘记调用
Add()/Done(),导致Wait()永久阻塞或提前退出; WaitGroup必须传指针,值传递会拷贝对象,计数失效;- 主函数提前退出,子协程会被强制终止。
1.3 Channel 通道
通道是协程间的数据载体,使用 chan 关键字声明,make 创建,通过 <- 完成收发。
1.3.1 分类与基础语法
1. 无缓冲通道(同步通道)
创建:ch := make(chan 数据类型)
规则:发送方、接收方必须同时就绪,否则互相阻塞。
go
package main
import "fmt"
func sum(s []int, c chan int) {
total := 0
for _, v := range s {
total += v
}
c <- total // 向通道发送数据
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // 从通道接收数据
fmt.Println(x + y)
}
2. 有缓冲通道
创建:ch := make(chan 类型, 缓冲区大小)
规则:缓冲区未满时发送不阻塞;缓冲区满/无数据时阻塞。
go
package main
import "fmt"
func main() {
// 缓冲区大小为2
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
1.3.2 通道关闭 & 遍历
close(ch):关闭通道,关闭后不可再发送数据,但仍可接收;for range:遍历通道,通道关闭后循环自动退出(通道不关闭会永久阻塞)。
示例
go
package main
import "fmt"
func fib(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c) // 关闭通道
}
func main() {
c := make(chan int, 10)
go fib(10, c)
// 遍历通道
for val := range c {
fmt.Println(val)
}
}
1.3.3 单向通道(只读/只写)
限制通道方向,提升代码安全性:
chan<- int:只写通道(仅发送数据);<-chan int:只读通道(仅接收数据)。
1.3.4 通道踩坑
- 向已关闭通道发送数据 → 运行 panic;
- 接收无数据、无缓冲且无发送方的通道 → 死锁;
- 重复关闭通道 → panic;
- 遍历未关闭的通道 → 永久阻塞。
1.4 select 多路监听
select 用于同时监听多个通道 ,语法类似 switch:
- 多个
case同时就绪:随机选一个执行; - 所有 case 未就绪:无
default则阻塞,有default直接执行默认分支。
示例
go
package main
import "fmt"
func fib(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit: // 接收退出信号
fmt.Println("协程退出")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0 // 发送退出信号
}()
fib(c, quit)
}
1.5 同步锁(解决数据竞争)
多个协程同时读写同一共享变量会产生数据竞争 ,使用 sync.Mutex(互斥锁)、sync.RWMutex(读写锁)保护资源。
Lock():加锁,进入临界区;Unlock():解锁,释放资源。
go
package main
import (
"fmt"
"sync"
)
var count int
var mu sync.Mutex
func add(wg *sync.WaitGroup) {
defer wg.Done()
mu.Lock() // 加锁
count++
mu.Unlock() // 解锁
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go add(&wg)
}
wg.Wait()
fmt.Println(count)
}
1.6 并发常见问题
- 死锁 :所有协程互相等待,无任何协程继续执行;
诱因:通道收发不匹配、锁未释放、循环等待。 - 数据竞争 :多协程同时读写共享变量;
解决方案:使用通道通信 或 互斥锁。
二、文件与目录操作
Go 依靠标准库完成文件/目录读写、创建、删除、遍历等操作,核心库:
os:底层文件/目录操作(主流);bufio:带缓冲读写,优化大文件 I/O 性能;io:通用读写接口;path/filepath:跨平台路径处理;ioutil:Go1.16+ 已弃用 ,功能迁移至os/io。
2.1 常用函数速查表
| 库/函数 | 作用 |
|---|---|
os.Create(name) |
创建文件,存在则清空内容 |
os.Open(name) |
只读打开文件 |
os.OpenFile() |
自定义模式(读写/追加)打开文件 |
os.ReadFile(name) |
一次性读取整个文件(小文件首选) |
os.WriteFile() |
一次性写入文件(覆盖) |
os.Remove() |
删除文件/空目录 |
os.Mkdir() |
创建单层目录 |
os.MkdirAll() |
递归创建多级目录 |
bufio.Scanner |
逐行读取(大文件) |
bufio.Writer |
缓冲写入,提升效率 |
filepath.Join() |
跨平台拼接路径 |
2.2 基础文件操作
2.2.1 创建 & 打开 & 关闭文件
规则:文件打开后必须关闭 ,推荐搭配 defer 延迟关闭,避免文件句柄泄漏。
go
package main
import (
"log"
"os"
)
func main() {
// 创建文件
file, err := os.Create("test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close() // 函数退出前自动关闭文件
log.Println("文件创建成功")
// 只读打开文件
f2, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
defer f2.Close()
}
2.2.2 读取文件
方式1:一次性读取(小文件)
go
package main
import (
"fmt"
"os"
)
func main() {
data, err := os.ReadFile("test.txt")
if err != nil {
fmt.Println("读取失败:", err)
return
}
fmt.Println(string(data)) // 字节切片转字符串
}
方式2:逐行读取(大文件,bufio)
go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Open("test.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text()) // 逐行输出
}
// 检查读取错误
if err := scanner.Err(); err != nil {
fmt.Println("读取异常:", err)
}
}
2.2.3 写入文件
方式1:整体覆盖写入
go
package main
import (
"fmt"
"os"
)
func main() {
content := []byte("Hello Go 文件操作")
err := os.WriteFile("test.txt", content, 0644)
if err != nil {
fmt.Println(err)
}
}
方式2:追加写入(os.O_APPEND)
go
package main
import (
"fmt"
"os"
)
func main() {
// 追加+只写,文件权限 0644
file, err := os.OpenFile("test.txt", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
_, _ = file.WriteString("\n追加新内容")
}
方式3:缓冲写入(大批量数据)
go
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.Create("write.txt")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
writer := bufio.NewWriter(file)
_, _ = writer.WriteString("缓冲写入内容\n")
writer.Flush() // 刷新缓冲区,数据落地磁盘
}
2.3 目录操作
go
package main
import (
"log"
"os"
)
func main() {
// 1. 创建单层目录
err := os.Mkdir("mydir", 0755)
if err != nil {
log.Println(err)
}
// 2. 递归创建多级目录
err = os.MkdirAll("a/b/c", 0755)
if err != nil {
log.Println(err)
}
// 3. 读取目录下文件/子目录
entries, err := os.ReadDir(".")
if err != nil {
log.Fatal(err)
}
for _, entry := range entries {
fmt.Println(entry.Name(), entry.IsDir())
}
// 4. 删除空目录/文件
_ = os.Remove("mydir")
// 递归删除目录及所有内容
_ = os.RemoveAll("a")
}
2.4 辅助操作
- 判断文件是否存在
go
if _, err := os.Stat("test.txt"); os.IsNotExist(err) {
fmt.Println("文件不存在")
}
- 文件复制 :使用
io.Copy - 跨平台路径 :统一使用
filepath.Join("dir", "file.txt")拼接路径,兼容 Windows / Linux / Mac。
2.5 文件操作踩坑
- 打开文件后忘记 Close,长期运行导致文件句柄泄漏;
- 追加文件未使用
os.O_APPEND,直接覆盖原有内容; - 缓冲写入后未执行
Flush(),数据留在缓冲区,磁盘无内容; - 权限数字
0644/0755必须以0开头,写法错误导致权限异常; - 区分
os.Remove(仅删空目录/文件)和os.RemoveAll(递归删除)。
三、正则表达式
Go 使用标准库 regexp 实现正则,用于字符串匹配、查找、替换、分割,适合表单校验、文本提取等场景。
3.1 核心函数
regexp.MustCompile(正则):编译正则,出错直接 panic(常用);regexp.Compile(正则):编译正则,返回错误(严谨场景);MatchString():判断字符串是否完全匹配规则;FindString():获取第一个匹配结果;FindAllString():获取所有匹配结果;ReplaceAllString():正则替换;Split():按正则分割字符串。
3.2 常用正则元字符
| 符号 | 作用 |
|---|---|
. |
匹配任意单个字符(不含换行) |
\d |
数字 [0-9] |
\w |
字母、数字、下划线 |
\s |
空白符(空格/制表符/换行) |
+ |
前面字符出现 1 次及以上 |
* |
前面字符出现 0 次及以上 |
? |
前面字符出现 0 或 1 次 |
^ |
字符串开头 |
$ |
字符串结尾 |
[] |
匹配括号内任意字符 |
Go 中推荐使用反引号 ````` 包裹正则,避免
\二次转义。
3.3 实战示例
3.3.1 匹配校验(如账号、手机号)
go
package main
import (
"fmt"
"regexp"
)
func main() {
// 匹配纯字母+数字
reg := regexp.MustCompile(`^[a-zA-Z0-9]+$`)
str1 := "Go123"
str2 := "Go@123"
fmt.Println(reg.MatchString(str1)) // true
fmt.Println(reg.MatchString(str2)) // false
}
3.3.2 提取所有匹配内容
go
package main
import (
"fmt"
"regexp"
)
func main() {
// 提取所有数字
reg := regexp.MustCompile(`\d+`)
str := "苹果3个,香蕉5斤"
res := reg.FindAllString(str, -1)
fmt.Println(res) // [3 5]
}
3.3.3 正则替换
go
package main
import (
"fmt"
"regexp"
)
func main() {
// 把多个空格替换为单个空格
reg := regexp.MustCompile(`\s+`)
str := "Hello World Go"
newStr := reg.ReplaceAllString(str, " ")
fmt.Println(newStr) // Hello World Go
}
3.3.4 字符串分割
go
package main
import (
"fmt"
"regexp"
)
func main() {
reg := regexp.MustCompile(`,`)
str := "apple,banana,orange"
parts := reg.Split(str, -1)
fmt.Println(parts)
}
3.4 正则踩坑
- 普通双引号内
\需要转义为\\,优先使用 ````` 反引号; - 正则编译建议全局一次执行,不要循环内反复
Compile(性能差); MustCompile正则语法错误会直接崩溃,正式环境优先用Compile+ 错误判断;- 复杂正则会大幅降低性能,大数据场景尽量改用普通字符串处理。
四、速记表
| 模块 | 核心要点 | 高频坑点 |
|---|---|---|
| 并发 | go启动协程;channel协程通信;select监听多通道;WaitGroup等待;Mutex加锁 | 通道死锁、协程计数错误、锁忘记释放 |
| 文件操作 | os为主,bufio做缓冲;defer关闭文件;0644权限;Append追加 | 句柄泄漏、缓冲未刷新、覆盖/追加混淆 |
| 正则 | regexp包;````` 原生字符串;Compile/MustCompile | 转义符问题、循环编译正则、复杂正则性能低 |