文件处理
打开文件和关闭文件
核心 API
- 打开:
os.Open(path)(只读)、os.OpenFile(path, flag, perm) - 关闭:
defer file.Close()
按行读取示例
f, err := os.Open("test.txt")
if err != nil {
// 处理错误
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
// 处理读取过程中的错误
}
带缓冲的 Reader 读取文件
要点
bufio.NewReader在底层 *os.File 上再包一层缓冲,减少系统调用- 适合大文件、频繁小块读取等场景
- 用 Read 循环读取,每次读到一个 []byte 缓冲中
f, err := os.Open("test.txt")
if err != nil {
panic(err)
}
defer f.Close()
reader := bufio.NewReader(f)
buf := make([]byte, 1024) // 1KB 缓冲
for {
n, err := reader.Read(buf)
if n > 0 {
fmt.Print(string(buf[:n]))
}
if err == io.EOF {
break
}
if err != nil {
fmt.Println("read err:", err)
break
}
}
一次性读取文件
要点
- os.ReadFile:一次性把整个文件读入内存,返回 []byte
- 适合文件较小,如配置文件、模板、示例数据等
- Go 1.16 之后推荐用 os.ReadFile(代替 ioutil.ReadFile)
data, err := os.ReadFile("test.txt")
if err != nil {
panic(err)
}
fmt.Printf("文件大小: %d 字节\n", len(data))
fmt.Println(string(data))
创建文件并写入内容
要点
- os.Create:如果文件存在则清空,不存在则创建
- 返回的 *os.File 可直接调用 Write / WriteString
- 默认权限 0666(再受 umask 影响)
f, err := os.Create("hello.txt")
if err != nil {
panic(err)
}
defer f.Close()
_, err = f.WriteString("Hello, Golang!\n")
if err != nil {
fmt.Println("write err:", err)
}
写文件的四种方式
方式 1:os.WriteFile(一次性写入)
data := []byte("hello, world")
err := os.WriteFile("a.txt", data, 0644)
if err != nil {
fmt.Println("writefile err:", err)
}
特点:简单粗暴,一次性写入所有数据,适合数据量较小场景。
方式 2:(*os.File).Write / WriteString
f, err := os.OpenFile("b.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
panic(err)
}
defer f.Close()
if _, err := f.Write([]byte("hello\n")); err != nil {
fmt.Println("write err:", err)
}
// 或者
if _, err := f.WriteString("hello again\n"); err != nil {
fmt.Println("writeString err:", err)
}
特点:适合需要多次写入、按块写入的场景。
方式 3:bufio.Writer 缓冲写
f, err := os.OpenFile("c.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
panic(err)
}
defer f.Close()
bw := bufio.NewWriter(f)
if _, err := bw.WriteString("hello, bufio\n"); err != nil {
fmt.Println("buffer write err:", err)
}
// 非常重要:写完后要 Flush
if err := bw.Flush(); err != nil {
fmt.Println("flush err:", err)
}
特点
- 先写到内存缓冲区,Flush() 时才真正写到文件
- 适合很多小写操作,减少系统调用次数
方式 4:fmt.Fprint* 系列格式化写
f, err := os.OpenFile("d.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
panic(err)
}
defer f.Close()
name := "Tom"
age := 18
fmt.Fprintf(f, "name: %s, age: %d\n", name, age)
特点
- 支持格式化输出,和 fmt.Printf 一样
- 更适合结构化文本输出(日志、报表等)
判断文件或目录是否存在
要点
- 用
os.Stat获取文件信息 errors.Is(err, os.ErrNotExist)判断“确实不存在”- 返回值中
info.IsDir()可判断是否为目录
封装成工具函数
func PathExists(path string) (exists bool, isDir bool, err error) {
info, err := os.Stat(path)
if err == nil {
return true, info.IsDir(), nil
}
if errors.Is(err, os.ErrNotExist) {
return false, false, nil
}
return false, false, err
}
使用
exists, isDir, err := PathExists("test.txt")
if err != nil {
fmt.Println("stat err:", err)
} else if !exists {
fmt.Println("文件不存在")
} else if isDir {
fmt.Println("是目录")
} else {
fmt.Println("是普通文件")
}
拷贝文件(图片/视频/音频通用)
要点
- 对于二进制文件(图片、视频、音频等),只是按“字节流”拷贝即可
- 用
io.Copy从源文件复制到目标文件,效率高、代码简单 - 最后用
Sync()确保数据刷到磁盘
实现一个通用拷贝函数
func CopyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer out.Close()
if _, err := io.Copy(out, in); err != nil {
return err
}
return out.Sync()
}
使用
if err := CopyFile("a.jpg", "b.jpg"); err != nil {
fmt.Println("copy err:", err)
}
统计不同类型的字符个数
要点
- 用
bufio.Reader.ReadRune按“字符”(rune)读取,支持 UTF-8(中文不会被拆开) - 用
unicode包判断字符类型:unicode.IsLetter:字母(含中英文)unicode.IsDigit:数字unicode.IsSpace:空白字符(空格、换行、制表符等)
示例:统计字母、数字、空白、其他字符数量
func CountChars(path string) (letters, digits, spaces, others int, err error) {
f, err := os.Open(path)
if err != nil {
return
}
defer f.Close()
r := bufio.NewReader(f)
for {
ch, _, e := r.ReadRune()
if e != nil {
if e == io.EOF {
break
}
err = e
return
}
switch {
case unicode.IsLetter(ch):
letters++
case unicode.IsDigit(ch):
digits++
case unicode.IsSpace(ch):
spaces++
default:
others++
}
}
return
}
使用
letters, digits, spaces, others, err := CountChars("test.txt")
if err != nil {
fmt.Println("count err:", err)
} else {
fmt.Println("字母:", letters)
fmt.Println("数字:", digits)
fmt.Println("空白:", spaces)
fmt.Println("其他:", others)
}
命令行参数
在实际开发中我们需要从命令行获得参数,比如配置文件的路径,监听哪个端口,我们希望灵活一点,可以通过命令行动态的传入参数
在os包中有一个os.Args用来存储所有的命令行参数,我们来个例子
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println("命令行的参数有", len(os.Args))
for i, v := range os.Args {
fmt.Printf("args[%v]=%v\n", i, v)
}
}
flag包解析命令行参数
用os包的方式还是比较原生的, 对解析参数并不是特别方便,特别是有指定参数形式的命令行,Go设计者提供了flag包,可以方便的解析命令行参数,且参数的顺序可以随意flag package – flag – Go Packages
package main
import (
"flag"
"fmt"
)
func main(){
var user string
var pwd string
var host string
var port int
//&user就是接受用户命令行输入-u后面的参数值
//“u”就是-u指定参数
flag.StringVar(&user, "u", "root", "数据库用户名,默认为 root")
flag.StringVar(&pwd, "pwd", "123", "数据库用户名,默认为 123")
flag.StringVar(&host, "h", "localhost", "数据库用户名,默认为 localhost")
flag.IntVar(&port, "port", 3306, "数据库用户名,默认为 3306")
//转换,必须执行这个方法
flag.Parse()
fmt.Printf("user=%s,pwd=%s,host=%s,port=%d\n", user, pwd, host, port)
}
我们直接上案例不罗嗦。









