Go 文件与 IO 操作知识点总结:操作系统底层、源码原理与 bufio 流式高性能实战
Go 文件与 IO 操作 完整精讲
文章目录
- Go 文件与 IO 操作 完整精讲
- ⚠️ 高频易错坑
-
- 1. 常用 flag 组合(背诵)
- 2. 易错坑点
- 3. 使用场景建议
- 五、bufio 缓冲 IO:高效流式处理(考点核心)
- 六、三种读写方案对比(选型标准)
- 七、底层深入:os.File 源码结构(简化)
- 八、关键坑点(面试高频)
- 九、完整流式大文件 Demo(贴合题干「高效流式处理」)
- 十、知识点总结背诵版
核心包:
os / io / bufio
目标:文件读写、路径操作、高效流式处理,附带操作系统底层原理 + Go 源码层面讲解
一、先理清三大包定位(面试必区分)
-
os:操作系统资源封装- 直接操作文件、目录、进程、权限;
os.File代表操作系统打开的文件描述符 (fd);- 提供路径、文件创建、删除、文件元信息查询。
-
io:基础抽象接口(整个 Go IO 体系基石)定义标准接口:
ReaderWriterCloserSeeker所有可读对象只要实现
Read(p []byte)(n int, err error)就是 Reader,实现解耦。 -
bufio:缓冲 IO,流式高效读写在 os.File 之上增加内存缓冲区,减少系统调用次数,优化 IO 性能。
二、底层前置知识:操作系统 IO 基础
文件描述符 File Descriptor(fd)
进程打开文件时,操作系统内核返回一个非负整数 fd;
os.File 内部封装了 fd,所有读写操作最终都会调用 Linux 系统调用:
open/read/write/close。
两种 IO 模式
-
无缓冲 IO(os.File 直接 Read/Write)
每次调用 Read → 直接发起一次系统调用,频繁读写小数据时开销巨大。
-
用户态缓冲 IO(bufio)
在应用内存开辟一块缓冲区,程序先读写缓冲区;缓冲区满 / 空时,才向内核发起系统调用,大幅减少系统调用次数。
系统调用需要 CPU 从用户态切换内核态,切换成本很高,这就是 bufio 性能优势根源。
三、io 包核心接口(抽象层,重中之重)
// 基础读接口
type Reader interface {
Read(p []byte) (n int, err error)
}
// 基础写接口
type Writer interface {
Write(p []byte) (n int, err error)
}
// 关闭资源
type Closer interface {
Close() error
}
// 偏移读写(文件支持,管道/网络流不支持)
type Seeker interface {
Seek(offset int64, whence int) (int64, error)
}
✅ os.File 同时实现了 Reader/Writer/Closer/Seeker。
工具函数 io.Copy(dst Writer, src Reader)
不需要手动开辟缓冲区,自动把源数据拷贝到目标,通用流式拷贝。
1. io.ReadAll () 一次性读取全部数据
作用:循环读取 Reader 直到 EOF,返回全部 [] byte
✅适用:小数据
❌禁止:超大文件、无限数据流(会爆内存)
r := bytes.NewBufferString("read all demo")
data, err := io.ReadAll(r)
if err != nil {
panic(err)
}
fmt.Println(string(data))
2. io.Copy(dst Writer, src Reader)
作用:从 src 读取,写入 dst;内置 32KB 缓冲,不用手动循环 Read
重点:Copy 不会自动关闭 src /dst,文件、网络连接需要手动 Close!
src := bytes.NewBufferString("copy test")
var dst bytes.Buffer
_, err := io.Copy(&dst, src)
if err != nil {
panic(err)
}
fmt.Println(dst.String())
3. io.CopyN(dst Writer, src Reader, n int64)
只复制最多 N 个字节,超过部分直接丢弃
// 只拷贝前5个字节
io.CopyN(&dst, src, 5)
4. io.LimitReader(r Reader, n int64)
包装 Reader,限制最多读取 n 字节,常用于防攻击(限制请求 body 最大大小)
r := bytes.NewBufferString("123456789")
limitR := io.LimitReader(r, 4) // 最多读4字节
data, _ := io.ReadAll(limitR)
fmt.Println(string(data)) // 输出:1234
5. io.MultiReader 拼接多个 Reader
按顺序读取多个数据源,读完第一个自动读取第二个
r1 := bytes.NewBufferString("a")
r2 := bytes.NewBufferString("b")
rAll := io.MultiReader(r1, r2)
data, _ := io.ReadAll(rAll)
fmt.Println(string(data)) // ab
6. io.MultiWriter 多目的地同时写入
一份数据,同时写入多个 Writer(类似日志同时输出到控制台 + 文件)
var buf1, buf2 bytes.Buffer
w := io.MultiWriter(&buf1, &buf2)
_, _ = w.Write([]byte("hello"))
fmt.Println(buf1.String(), buf2.String())
7. io.TeeReader(r Reader, w Writer)
一边读取源数据,一边自动复制一份写入 w;实现「读取同时备份」
src := bytes.NewBufferString("content")
var backup bytes.Buffer
teeR := io.TeeReader(src, &backup)
data, _ := io.ReadAll(teeR)
fmt.Println("读到的数据:", string(data))
fmt.Println("备份缓冲区:", backup.String())
四、os 包:文件基础操作 + 路径操作
一、文件操作(最高频)
1. os.Open () 只读打开文件
func Open(name string) (*File, error)
特性:只读模式,不能写入;文件不存在返回错误。
file, err := os.Open("test.txt")
if err != nil {
panic(err)
}
defer file.Close() // 必须关闭,防止文件句柄泄漏
// 搭配io.ReadAll直接读取全部内容
data, err := io.ReadAll(file)
fmt.Println(string(data))
2. os.Create () 创建 / 覆写文件
func Create(name string) (*File, error)
规则:
- 文件不存在 → 创建;
- 文件已存在 → 清空原有内容(截断);
- 权限:
0666(rw-rw-rw);读写模式。
file, err := os.Create("out.txt")
defer file.Close()
file.Write([]byte("hello os"))
3. os.OpenFile () 万能打开函数(底层,Open/Create 都是封装它)
简易封装:
os.Open(path)→ O_RDONLY 只读打开os.Create(path)→ O_RDWR|O_CREATE|O_TRUNC 创建 / 清空文件
func OpenFile(name string, flag int, perm FileMode) (*File, error)
flag 常用常量(可组合,| 连接)
| 常量 | 含义 |
|---|---|
os.O_RDONLY |
只读 |
os.O_WRONLY |
只写 |
os.O_RDWR |
读写 |
os.O_APPEND |
追加写入,不覆盖旧数据 |
os.O_CREATE |
不存在则创建 |
os.O_TRUNC |
存在就清空截断 |
perm 文件权限(Linux)
0644:所有者读写,其他人只读(最常用)0755:可执行文件权限
示例:追加写文件(不会覆盖)
file, err := os.OpenFile("log.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
defer file.Close()
file.Write([]byte("追加一行日志\n"))
4. File 对象读写方法
// 实现 io.Reader
n, err := file.Read(buf []byte)
// 实现 io.Writer
n, err := file.Write([]byte)
n, err := file.WriteString("文本")
// 文件指针跳转(实现 io.Seeker)
offset, err := file.Seek(10, io.SeekStart)
5. 文件删除、重命名
// 删除文件
err := os.Remove("test.txt")
// 重命名/移动文件
err := os.Rename("old.txt", "new.txt")
// RemoveAll:递归删除文件夹+内部所有内容(rm -rf)
err := os.RemoveAll("./tmpdir")
二、文件夹操作
// 创建单层目录
err := os.Mkdir("mydir", 0755)
// MkdirAll:递归创建多级目录(mkdir -p)
err := os.MkdirAll("a/b/c", 0755)
// 读取目录下所有文件信息
entries, err := os.ReadDir("./")
for _, entry := range entries {
fmt.Println(entry.Name(), entry.IsDir())
}
三、文件信息查询
// 获取文件元信息(大小、修改时间、是否目录)
info, err := os.Stat("test.txt")
fmt.Println("大小:", info.Size())
fmt.Println("是否目录:", info.IsDir())
fmt.Println("修改时间:", info.ModTime())
// os.Lstat():和Stat类似,但不跟随软链接
info, err := os.Lstat("link.txt")
四、环境变量相关
// 获取环境变量,不存在返回空字符串
val := os.Getenv("PATH")
// 设置环境变量
err := os.Setenv("TEST_KEY", "123")
// 查询环境变量是否存在
val, ok := os.LookupEnv("TEST_KEY")
// 清除环境变量
os.Unsetenv("TEST_KEY")
// 获取全部环境变量
envs := os.Environ()
五、进程标准流(Stdin/Stdout/Stderr)
os.Stdin // 标准输入(终端输入)*os.File
os.Stdout // 标准输出 fmt.Println底层写入这里
os.Stderr // 标准错误输出
示例:直接往控制台输出
os.Stdout.WriteString("打印到终端\n")
六、进程相关
// 获取当前进程PID
pid := os.Getpid()
// 获取父进程PID
ppid := os.Getppid()
// 退出程序;非0代表异常退出
os.Exit(1)
七、路径工具(搭配 os 使用,更多路径操作推荐 path /filepath 包)
filepath:跨平台路径处理(Windows / Linux 分隔符自动适配,优先使用)
常用 API:
-
filepath.Join(a,b,c):拼接路径,自动处理分隔符 -
filepath.Abs(path):获取绝对路径 -
filepath.Base(path):获取文件名 -
filepath.Dir(path):获取目录 -
filepath.Glob("*.txt"):通配符匹配文件
// 判断文件是否存在(易错!不能用err==nil直接判断)
func FileExists(path string) bool {
_, err := os.Stat(path)
return !os.IsNotExist(err)
}
// 判断错误是否为「文件不存在」
os.IsNotExist(err)
// 判断是否权限不足
os.IsPermission(err)
⚠️ 高频易错坑
- 打开文件必须 defer Close (),长期运行服务会耗尽文件句柄;
os.Create()打开已有文件直接清空!追加场景必须使用OpenFile + O_APPEND;os.Mkdir只能创建单层目录,多级目录请用MkdirAll;os.File实现io.Reader,所以可以直接传入io.Copy、io.ReadAll;- Windows 和 Linux 文件路径分隔符不一样,路径拼接优先使用
filepath.Join()。
基础读写示例
package main
import (
"os"
)
func main() {
// 只读打开
f, err := os.Open("test.txt")
if err != nil {panic(err)}
defer f.Close() // 必须关闭,释放文件描述符!
buf := make([]byte, 1024)
n, err := f.Read(buf) // 直接无缓冲读取
_ = n
}
⚠️ 重要规范:打开文件一定要 defer Close ()
底层:进程不 close,fd 不会释放;大量泄漏会触发「打开文件数过多」崩溃。
1. 常用 flag 组合(背诵)
// 等价 os.Open(只读打开)
f, _ := os.OpenFile("a.txt", os.O_RDONLY, 0)
// 等价 os.Create(读写,不存在创建,存在清空)
// O_RDWR | O_CREATE | O_TRUNC
f, _ := os.OpenFile("a.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
// 追加写入(文件尾部添加,不清空旧内容)
f, _ := os.OpenFile("a.txt", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
数字含义拆分
权限格式:0 [所有者][同组用户][其他用户]
0 6 4 4
第一位 0:八进制前缀,无权限含义
第二位 6 → 文件拥有者(user)
第三位 4 → 同组用户(group)
第四位 4 → 外部其他用户(other)
单个数字 = 读 + 写 + 执行权限之和:
4 = 读权限 r
2 = 写权限 w
1 = 执行权限 x
拆解 0644
所有者 6 = 4+2 → r w - 可读、可写,不可执行
同组用户 4 = 4 → r - - 仅可读
其他用户 4 = 4 → r - - 仅可读
字符表示:-rw-r--r--
2. 易错坑点
- os.Open 拿到的文件句柄不能 Write ()
f, _ := os.Open("test.txt")
f.Write([]byte("hello")) // ❌ 报错,只读文件无法写入
-
perm 参数仅新建文件时生效
文件已经存在时,perm 参数会被直接忽略,不会修改已有文件权限。
想要修改已有文件权限,单独调用
os.Chmod()。err = os.Chmod(fileName, 0644) if err != nil { fmt.Println("修改权限失败:", err) return } fmt.Println("权限修改成功,设置为 0644 (-rw-r--r--)") -
flag 三大基础模式三选一,不能同时写多个
O_RDONLY只读O_WRONLY只写O_RDWR读写
错误示范:
O_RDONLY | O_WRONLY非法!
3. 使用场景建议
- 只读取文件内容 → 直接用
os.Open(简洁) - 需要写入、创建、追加、覆盖文件 → 必须使用
os.OpenFile
五、bufio 缓冲 IO:高效流式处理(考点核心)
底层原理
bufio.Reader / bufio.Writer 内部维护一块[]byte缓冲区。
bufio.Reader 读流程
- 用户调用 Read → 先尝试从内存缓冲区拿数据;
- 缓冲区空 → 调用底层 os.File.Read,一次性读取一大块数据填满缓冲区;
- 后续读取继续复用缓冲区,减少系统调用。
bufio.Writer 写流程
- Write 数据先写入内存缓冲区;
- 缓冲区满,自动刷入底层文件;
- 文件关闭前必须调用 Flush (),把缓冲区残留数据写入磁盘!
常用代码示例
package main
import (
"bufio"
"os"
)
func main() {
f, _ := os.OpenFile("test.txt", os.O_RDWR|os.O_CREATE, 0644)
defer f.Close()
// 带缓冲写入
w := bufio.NewWriter(f)
w.WriteString("hello go io\n")
w.Flush() // 强制刷盘,不可省略!
// 带缓冲读取
r := bufio.NewReader(f)
// ReadString('\n') 按行读取,流式处理大文件首选
line, _ := r.ReadString('\n')
_ = line
}
高频函数
bufio.Scanner:最适合按行遍历大文件
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
// 逐行处理,不需要一次性把整个文件加载进内存(流式)
}
优势:处理 GB 级大文件不会 OOM,流式处理,题干重点要求。
一、基础创建
// 从io.Reader创建(文件、strings.Reader都可以)
scanner := bufio.NewScanner(r)
二、核心执行流程(固定模板)
scanner := bufio.NewScanner(reader)
// 循环扫描,直到读到末尾或者出错
for scanner.Scan() {
// 获取当前扫描到的文本
line := scanner.Text()
fmt.Println(line)
}
// ❗必须检查扫描过程中的错误(不能忘!)
if err := scanner.Err(); err != nil {
fmt.Println("读取失败:", err)
}
三、常用方法清单
1. Scan() bool
- 作用:向前读取下一段数据
- 返回值:
true:成功读到一段内容,可以调用 Text ()/Bytes () 取值false:读取结束(EOF)或者发生错误
⚠️ 注意:Scan 不会返回错误,错误统一在循环结束后用
scanner.Err()获取
2. Text() string
获取本次扫描内容,返回 string。底层是把字节切片转为字符串。
3. Bytes() []byte
获取本次扫描内容,返回 []byte。
✅ 性能更好,注意:下一次调用 Scan () 会覆盖这个切片内容!不要异步保存!
4. Err() error
获取扫描期间产生的错误(排除 io.EOF,EOF 不算错误)
循环结束一定要调用,防止漏掉读写异常。
5.Split(split SplitFunc)
⭐重点:自定义分割函数,控制 Scanner 按什么规则切割数据
内置 4 种分割器(直接背)
// 1. bufio.ScanLines 默认!按行分割,自动去掉 \n \r\n
scanner.Split(bufio.ScanLines)
// 2. bufio.ScanWords 按空格分割单词
scanner.Split(bufio.ScanWords)
// 3. bufio.ScanBytes 逐个字节读取
scanner.Split(bufio.ScanBytes)
// 4. bufio.ScanRunes 按utf8字符读取
scanner.Split(bufio.ScanRunes)
6. Buffer(buf []byte, max int)
设置缓冲区,解决单行超过 64KB 默认上限报错
默认限制:单行最大 64KB,超长直接返回错误!
// 设置最大支持 1MB 的单行
buf := make([]byte, 1024*1024)
scanner.Buffer(buf, 1024*1024)
7. MaxScanTokenSize
常量:默认最大扫描长度 64 * 1024 = 65536
四、完整可运行示例
package main
import (
"bufio"
"fmt"
"strings"
)
func main() {
data := strings.NewReader("第一行\n第二行\r\n第三行")
sc := bufio.NewScanner(data)
// 默认分割:ScanLines
for sc.Scan() {
str := sc.Text()
fmt.Printf("行内容:%q\n", str)
}
// 检查错误
if err := sc.Err(); err != nil {
fmt.Println("err:", err)
}
}
五、面试对比:Scanner VS bufio.Reader ReadLine / ReadString
-
bufio.Scanner ✅推荐业务使用
- API 简洁,自动处理换行
\n \r\n - 不用手动处理截断、缓冲区碎片
- 唯一缺点:单行默认上限 64KB,超长要手动扩容
- API 简洁,自动处理换行
-
bufio.Reader ReadLine ❌不推荐
底层原始 API,容易出现超长行截断 bug,需要自己循环拼接碎片
-
bufio.Reader ReadString(‘\n’)
可控性更强,适合精细 IO 场景,适合学习底层原理
六、高频坑(必背)
-
循环结束必须判断
scanner.Err(),很多人漏掉! -
scanner.Bytes()返回的切片会被下一次 Scan 覆盖,如果需要保存要手动拷贝
// 错误:直接保存,下次Scan内容会变 tmp := scanner.Bytes() // 正确:拷贝 tmp := append([]byte(nil), scanner.Bytes()...) -
单行超过 64KB 直接报错,大日志文件要调用
Buffer()扩容
📝极简背诵总结
bufio.Scanner 是封装好的扫描工具,Scan() 推进读取,Text()/Bytes() 获取内容;支持自定义分割规则;默认按行切割,单行上限 64KB;循环结束务必检查 Err ()。
2.bufio.NewReader/bufio.NewWriter 常用方法汇总(面试速记)
前置:
// 读缓冲 r := bufio.NewReader(io.Reader) // 写缓冲 w := bufio.NewWriter(io.Writer)
一、bufio.Reader(NewReader)
核心方法
-
ReadString(delim byte) (string, error)
读到分隔符停止,返回内容包含分隔符。
示例:
r.ReadString('\n')按行读取。读到文件末尾无分隔符时,返回读到的数据 +
io.EOF。 -
ReadBytes(delim byte) ([]byte, error)
功能和 ReadString 一致,返回
[]byte而非字符串。 -
ReadLine() ([]byte, bool, error)
按行读取,丢弃换行符
\n/\r\n;官方不推荐优先使用,存在缓冲区截断问题。 -
Read(p []byte) (n int, err error)
标准 io.Reader 接口方法,填充字节切片。
-
Peek(n int) ([]byte, error)
预读 n 个字节,不移动读取指针,只偷看不消费。高频考点。
-
Discard(n int) (discarded int, err error)
直接跳过 n 个字节,丢弃数据。
记忆重点
✅ 按行优先:ReadString('\n')
✅ 偷看数据:Peek()
❌ 少用 ReadLine
二、bufio.Writer(NewWriter)
核心方法
-
Write(p []byte) (n int, err error)
写入字节切片,数据存入缓冲区,不直接落盘。
-
WriteString(s string) (n int, err error)
写入字符串,优先使用,省去 string→[] byte 转换开销。
-
Flush () error 【重中之重】
强制把缓冲区所有缓存数据写入底层流。
⚠️ 忘记 Flush → 缓冲区残留数据丢失!
-
Buffered() int
查询当前缓冲区暂存的字节数量。
-
Available() int
缓冲区剩余空闲空间大小。
致命坑(必背)
defer 文件.Close()不会自动调用 Flush ()
标准顺序:
w.Flush()
f.Close()
极简对比速查表
| 对象 | 高频方法 | 核心考点 |
|---|---|---|
| Reader | ReadString、Peek、ReadBytes | ReadString 带回分隔符;Peek 不移动指针 |
| Writer | WriteString、Flush、Buffered | 必须 Flush,否则丢数据 |
六、三种读写方案对比(选型标准)
-
os.File 直接 Read/Write(无缓冲)
适用:单次读写大块数据;不适合频繁小 IO。
缺点:小循环读写,大量系统调用,性能差。
-
bufio 缓冲读写
适用:频繁小数据读写、按行读取大文件、流式处理。
优点:合并 IO 请求,减少用户态 / 内核态切换,性能高。
-
os.ReadFile / os.WriteFile
os.ReadFile("a.txt") // 一次性把整个文件读入内存
⚠️ 致命限制:一次性加载全部内容到内存!
禁止用于超大文件,极易内存溢出,不属于流式处理。
七、底层深入:os.File 源码结构(简化)
// src/os/file.go
type File struct {
*file // 指针
}
type file struct {
fd int // 文件描述符
name string
dirinfo *dirInfo
}
所有 f.Read() 最终调用平台相关系统调用:
Linux:syscall.Read(fd, buf)
bufio.Reader 简化结构
type Reader struct {
buf []byte // 缓冲区内存
rd io.Reader // 底层数据源(os.File)
r, w int // 读写位置指针
}
缓冲区默认大小 4096 字节,可以手动指定:bufio.NewReaderSize(f, 8192)
八、关键坑点(面试高频)
-
bufio.Writer 忘记 Flush ()
程序退出,缓冲区剩余数据丢失!
-
忘记 Close () 文件 → 文件描述符泄漏
-
os.ReadFile读取大文件造成 OOM,不能用于流式场景 -
filepath和path包区别:path:只处理/分隔符(url 路径)filepath:适配操作系统,本地文件路径必须用它
-
权限参数
0644Linux 文件权限:读写权限,创建文件时必须指定。
九、完整流式大文件 Demo(贴合题干「高效流式处理」)
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
f, err := os.Open("bigfile.log")
if err != nil {
panic(err)
}
defer f.Close()
scanner := bufio.NewScanner(f)
// 逐行流式读取,内存只保存一行数据
lineNo := 0
for scanner.Scan() {
lineNo++
content := scanner.Text()
fmt.Printf("行%d:%s\n", lineNo, content)
}
// 检查扫描是否出现错误
if err := scanner.Err(); err != nil {
panic(err)
}
}
特点:无论文件 1GB 还是 10GB,内存占用稳定,典型流式 IO。
十、知识点总结背诵版
io包定义Reader/Writer抽象接口,统一所有可读写资源;os包封装操作系统文件、fd、路径、目录操作;bufio在用户态提供缓冲区,减少系统调用,实现高效 IO;- 无缓冲 IO每次读写触发系统调用;缓冲 IO 批量合并请求;
- 超大文件处理使用
bufio.Scanner流式逐行读取,避免一次性载入内存; - 文件打开务必
defer Close();bufio 写入结束必须Flush()。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)