Go 入门到精通-18-文件操作

目录

  • [🔵 Go 入门到精通:文件操作 ------ 从读写到遍历的完整指南](#🔵 Go 入门到精通:文件操作 —— 从读写到遍历的完整指南)
    • [1. 开篇:Go 文件操作的哲学](#1. 开篇:Go 文件操作的哲学)
    • [2. os 包:文件与目录的"操作系统"](#2. os 包:文件与目录的"操作系统")
      • [2.1 打开与创建文件](#2.1 打开与创建文件)
      • [2.2 目录操作](#2.2 目录操作)
      • [2.3 文件信息 os.Stat](#2.3 文件信息 os.Stat)
    • [3. 文件打开模式:位掩码的艺术](#3. 文件打开模式:位掩码的艺术)
      • [3.1 常用模式组合](#3.1 常用模式组合)
      • [3.2 实战示例](#3.2 实战示例)
    • [4. io 包:一切 I/O 的抽象](#4. io 包:一切 I/O 的抽象)
      • [4.1 理念:组合 > 继承](#4.1 理念:组合 > 继承)
    • [5. io.Reader 与 io.Writer 接口](#5. io.Reader 与 io.Writer 接口)
      • [5.1 文件读写基础](#5.1 文件读写基础)
      • [5.2 Reader 的契约](#5.2 Reader 的契约)
    • [6. io.Copy / io.ReadAll / io.TeeReader](#6. io.Copy / io.ReadAll / io.TeeReader)
      • [6.1 io.Copy:数据搬运工](#6.1 io.Copy:数据搬运工)
      • [6.2 io.ReadAll:一口气读完](#6.2 io.ReadAll:一口气读完)
      • [6.3 io.TeeReader:数据分流](#6.3 io.TeeReader:数据分流)
      • [6.4 io 包工具箱速查](#6.4 io 包工具箱速查)
    • [7. bufio 包:带缓冲的高效读写](#7. bufio 包:带缓冲的高效读写)
      • [7.1 为什么需要缓冲?](#7.1 为什么需要缓冲?)
      • [7.2 bufio.Reader](#7.2 bufio.Reader)
      • [7.3 bufio.Scanner:逐行读取最佳实践](#7.3 bufio.Scanner:逐行读取最佳实践)
      • [7.4 bufio.Writer](#7.4 bufio.Writer)
      • [7.5 Scanner vs Reader 对比](#7.5 Scanner vs Reader 对比)
    • [8. ioutil 已废弃:用 os.ReadFile / os.WriteFile 替代](#8. ioutil 已废弃:用 os.ReadFile / os.WriteFile 替代)
      • [8.1 迁移指南](#8.1 迁移指南)
      • [8.2 新 API 使用](#8.2 新 API 使用)
    • [9. path/filepath 包:跨平台路径操作](#9. path/filepath 包:跨平台路径操作)
      • [9.1 路径拼接与分割](#9.1 路径拼接与分割)
      • [9.2 路径清理与判断](#9.2 路径清理与判断)
      • [9.3 通配符匹配](#9.3 通配符匹配)
    • [10. os.FileInfo 与文件属性](#10. os.FileInfo 与文件属性)
    • [11. 临时文件与目录](#11. 临时文件与目录)
    • [12. 文件权限 os.FileMode](#12. 文件权限 os.FileMode)
      • [12.1 实战:设置文件权限](#12.1 实战:设置文件权限)
      • [12.2 权限速查表](#12.2 权限速查表)
    • [13. 实战:递归目录遍历工具](#13. 实战:递归目录遍历工具)
    • [14. 小结与互动](#14. 小结与互动)

🔵 Go 入门到精通:文件操作 ------ 从读写到遍历的完整指南

📅 更新于 2026年7月 | ✍️ 原创文章,转载请注明出处


1. 开篇:Go 文件操作的哲学

Go 的文件操作设计优雅而克制。与许多语言不同,Go 没有庞大的"一切皆文件"框架,而是通过组合小接口来构建强大的 I/O 能力:

复制代码
┌─────────────────────────────────────────────┐
│              你的业务代码                      │
├─────────────────────────────────────────────┤
│  bufio(缓冲读写、Scanner 逐行读取)            │
├─────────────────────────────────────────────┤
│  io(Reader/Writer 接口、Copy/ReadAll/Pipe)  │
├─────────────────────────────────────────────┤
│  os(Open/Create/Stat/Remove/Rename/Mkdir)  │
├─────────────────────────────────────────────┤
│  syscall(系统调用层,通常不直接使用)            │
└─────────────────────────────────────────────┘

这篇文章将带你从底层到高层、从 osbufio,完整掌握 Go 的文件操作。


2. os 包:文件与目录的"操作系统"

2.1 打开与创建文件

go 复制代码
package main

import (
    "fmt"
    "os"
)

func main() {
    // 1️⃣ os.Open:只读打开
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("打开失败:", err)
        return
    }
    defer file.Close() // ⭐ 务必关闭

    // 2️⃣ os.Create:创建或截断(覆盖)
    f2, err := os.Create("output.txt")
    if err != nil {
        panic(err)
    }
    defer f2.Close()
    f2.WriteString("Hello, Go!\n")

    // 3️⃣ os.OpenFile:完全控制(最灵活)
    f3, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    defer f3.Close()
    f3.WriteString("追加一行日志\n")
}

2.2 目录操作

go 复制代码
func main() {
    // 创建目录
    os.Mkdir("mydir", 0755)           // 创建单级目录
    os.MkdirAll("a/b/c/d", 0755)      // 创建多级目录(类似 mkdir -p)

    // 获取当前工作目录
    cwd, _ := os.Getwd()
    fmt.Println("当前目录:", cwd)

    // 切换目录
    os.Chdir("/tmp")

    // 删除
    os.Remove("output.txt")           // 删除文件
    os.RemoveAll("mydir")             // 递归删除目录

    // 重命名(也可用于移动)
    os.Rename("old.txt", "new.txt")

    // 读取目录内容
    entries, _ := os.ReadDir(".")
    for _, entry := range entries {
        fmt.Printf("  %s (是目录: %v)\n", entry.Name(), entry.IsDir())
    }
}

2.3 文件信息 os.Stat

go 复制代码
func main() {
    info, err := os.Stat("example.txt")
    if err != nil {
        if os.IsNotExist(err) {
            fmt.Println("文件不存在")
        }
        return
    }

    fmt.Println("文件名:", info.Name())
    fmt.Println("大小:", info.Size(), "字节")
    fmt.Println("权限:", info.Mode())
    fmt.Println("修改时间:", info.ModTime())
    fmt.Println("是目录:", info.IsDir())
    fmt.Println("系统信息:", info.Sys()) // 底层系统相关
}

3. 文件打开模式:位掩码的艺术

Go 使用位掩码(bitmask)来组合多个打开模式:

go 复制代码
const (
    O_RDONLY int = syscall.O_RDONLY // 只读
    O_WRONLY int = syscall.O_WRONLY // 只写
    O_RDWR   int = syscall.O_RDWR   // 读写
    O_APPEND int = syscall.O_APPEND // 追加
    O_CREATE int = syscall.O_CREATE // 不存在则创建
    O_EXCL   int = syscall.O_EXCL   // 与 O_CREATE 配合,文件存在则失败
    O_SYNC   int = syscall.O_SYNC   // 同步写入
    O_TRUNC  int = syscall.O_TRUNC  // 打开时清空文件
)

3.1 常用模式组合

组合 含义 使用场景
O_RDONLY 只读 os.Open() 内部使用
`O_WRONLY O_CREATE O_TRUNC`
`O_WRONLY O_CREATE O_APPEND`
`O_RDWR O_CREATE` 读写,不存在则创建
`O_RDWR O_CREATE O_EXCL`

3.2 实战示例

go 复制代码
// 日志文件:追加模式
func openLogFile(path string) (*os.File, error) {
    return os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
}

// 锁文件:原子创建(存在即失败)
func createLockFile(path string) (*os.File, error) {
    return os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
}

// 配置文件:读写,保留原有内容
func openConfigFile(path string) (*os.File, error) {
    return os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
}

4. io 包:一切 I/O 的抽象

io 包是 Go I/O 的基石,它通过 ReaderWriter 两个核心接口统一了所有数据源和数据汇。

4.1 理念:组合 > 继承

go 复制代码
// 一切数据源都是 Reader
type Reader interface {
    Read(p []byte) (n int, err error)
}

// 一切数据汇都是 Writer
type Writer interface {
    Write(p []byte) (n int, err error)
}

// Closer:需要关闭的资源
type Closer interface {
    Close() error
}

// 更多组合接口
type ReadWriter interface {
    Reader
    Writer
}

type ReadCloser interface {
    Reader
    Closer
}

type ReadWriteCloser interface {
    Reader
    Writer
    Closer
}

这意味着:文件、网络连接、HTTP 响应体、压缩流、加密流......它们都是 io.Reader。你可以用同样的代码处理完全不同的数据源。


5. io.Reader 与 io.Writer 接口

5.1 文件读写基础

go 复制代码
package main

import (
    "fmt"
    "io"
    "os"
)

func main() {
    f, _ := os.Open("example.txt")
    defer f.Close()

    // 方式1:固定大小读取
    buf := make([]byte, 1024)
    for {
        n, err := f.Read(buf)
        if err == io.EOF {
            break // 读取完毕
        }
        if err != nil {
            panic(err)
        }
        fmt.Print(string(buf[:n]))
    }

    // 方式2:写入
    fw, _ := os.Create("copy.txt")
    defer fw.Close()
    fw.Write([]byte("Hello, Go!\n"))
    fw.WriteString("第二行内容\n")
}

5.2 Reader 的契约

go 复制代码
// Read 方法的行为规范:
// 1. 最多读取 len(p) 字节到 p
// 2. 返回实际读取的字节数 n(0 <= n <= len(p))
// 3. 可能同时返回 n > 0 和 err(这是合法的!)
// 4. 返回 io.EOF 表示流结束(可能同时返回数据)

func readFull(r io.Reader, buf []byte) (int, error) {
    total := 0
    for total < len(buf) {
        n, err := r.Read(buf[total:])
        total += n
        if err == io.EOF {
            return total, io.EOF
        }
        if err != nil {
            return total, err
        }
    }
    return total, nil
}

6. io.Copy / io.ReadAll / io.TeeReader

6.1 io.Copy:数据搬运工

go 复制代码
func main() {
    // 文件复制(最简洁的方式)
    src, _ := os.Open("source.txt")
    defer src.Close()
    dst, _ := os.Create("dest.txt")
    defer dst.Close()

    written, err := io.Copy(dst, src) // 内部用 32KB 缓冲区
    fmt.Printf("复制了 %d 字节\n", written)

    // io.CopyBuffer:自定义缓冲区大小
    buf := make([]byte, 1024*1024) // 1MB 缓冲区
    io.CopyBuffer(dst, src, buf)

    // io.CopyN:限制复制字节数
    io.CopyN(dst, src, 1024) // 最多复制 1KB
}

6.2 io.ReadAll:一口气读完

go 复制代码
func main() {
    // Go 1.16+ 推荐使用 io.ReadAll(替代 ioutil.ReadAll)
    f, _ := os.Open("config.json")
    defer f.Close()

    data, err := io.ReadAll(f) // 读取全部内容到内存
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))

    // ⚠️ 注意:ReadAll 会把整个文件加载到内存
    // 大文件请使用 bufio.Scanner 或分块读取
}

6.3 io.TeeReader:数据分流

go 复制代码
func main() {
    // TeeReader:读取数据的同时写到另一个 Writer(类似 Unix tee 命令)
    src, _ := os.Open("input.txt")
    defer src.Close()

    logFile, _ := os.Create("read.log")
    defer logFile.Close()

    // 从 src 读取的数据会同时写入 logFile
    tee := io.TeeReader(src, logFile)

    // 处理 tee(同时完成了数据记录)
    data, _ := io.ReadAll(tee)
    fmt.Println("读取内容:", string(data))
    // read.log 中也有一份完全相同的副本
}

6.4 io 包工具箱速查

函数 功能 适用场景
io.Copy(dst, src) 数据搬运 文件复制、HTTP 下载
io.CopyN(dst, src, n) 限字节搬运 限制大小的下载
io.ReadAll(r) 全部读取 小文件/配置读取
io.TeeReader(r, w) 分流读取 日志记录、数据审计
io.MultiReader(...) 串联多个 Reader 合并多个文件
io.MultiWriter(...) 广播到多个 Writer 同时写日志+控制台
io.Pipe() 内存管道 goroutine 间流式传输
io.LimitReader(r, n) 限制读取长度 限制输入大小

7. bufio 包:带缓冲的高效读写

7.1 为什么需要缓冲?

每次 os.File.Read() 都会触发系统调用,频繁小量读取性能极差。bufio 在用户空间维护一个缓冲区,减少系统调用次数:

复制代码
无缓冲: 每 1 字节 → 1 次系统调用  → 1000 字节 = 1000 次系统调用
有缓冲: 每 4096 字节 → 1 次系统调用 → 1000 字节 = 1 次系统调用

7.2 bufio.Reader

go 复制代码
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    f, _ := os.Open("example.txt")
    defer f.Close()

    reader := bufio.NewReader(f)           // 默认 4KB 缓冲区
    // reader := bufio.NewReaderSize(f, 64*1024)  // 自定义 64KB

    // ReadString:读到分隔符
    line, err := reader.ReadString('\n')
    fmt.Println("第一行:", line)

    // ReadLine:低级 API,不包含 \n
    // ReadBytes:读直到分隔符(返回 []byte)

    // Peek:预览而不消费
    preview, _ := reader.Peek(10)
    fmt.Println("前10字节:", string(preview))
}

7.3 bufio.Scanner:逐行读取最佳实践

go 复制代码
func main() {
    f, _ := os.Open("access.log")
    defer f.Close()

    scanner := bufio.NewScanner(f)

    // 自定义分割函数(默认按行)
    // scanner.Split(bufio.ScanWords)  // 按单词
    // scanner.Split(bufio.ScanRunes)  // 按字符
    // scanner.Split(bufio.ScanBytes)  // 按字节

    // 设置缓冲区大小(处理长行)
    const maxScanTokenSize = 1024 * 1024 // 1MB
    buf := make([]byte, maxScanTokenSize)
    scanner.Buffer(buf, maxScanTokenSize)

    lineNum := 0
    for scanner.Scan() {
        lineNum++
        line := scanner.Text()
        fmt.Printf("第%d行: %s\n", lineNum, line)
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("扫描出错:", err)
    }
}

7.4 bufio.Writer

go 复制代码
func main() {
    f, _ := os.Create("output.txt")
    defer f.Close()

    writer := bufio.NewWriter(f) // 默认 4KB 缓冲区

    writer.WriteString("Hello, ")
    writer.WriteString("World!\n")
    writer.WriteByte('!')

    // ⚠️ 必须 Flush!否则缓冲区的数据不会写入文件
    writer.Flush()

    // 更好的做法:defer Flush
    // defer writer.Flush()
}

7.5 Scanner vs Reader 对比

特性 bufio.Scanner bufio.Reader
使用复杂度 ⭐ 简单 ⭐⭐ 中等
逐行读取 Scan() + Text() ReadString('\n')
长行支持 需手动调大 Buffer() ✅ 自动处理
预览 ❌ 不支持 Peek()
行号追踪 ❌ 需手动计数 ❌ 需手动计数
推荐场景 简单逐行读取 需要灵活控制的场景

8. ioutil 已废弃:用 os.ReadFile / os.WriteFile 替代

8.1 迁移指南

Go 1.16 起,io/ioutil 包被标记为废弃(Deprecated),所有函数迁移到 osio 包:

废弃 API (ioutil) 新 API 功能
ioutil.ReadFile os.ReadFile 读取整个文件
ioutil.WriteFile os.WriteFile 写入整个文件
ioutil.ReadAll io.ReadAll 读取全部
ioutil.ReadDir os.ReadDir 读取目录
ioutil.TempFile os.CreateTemp 创建临时文件
ioutil.TempDir os.MkdirTemp 创建临时目录
ioutil.NopCloser io.NopCloser 无操作 Closer

8.2 新 API 使用

go 复制代码
package main

import (
    "fmt"
    "os"
)

func main() {
    // 读取整个文件(小文件适用)
    data, err := os.ReadFile("config.json")
    if err != nil {
        panic(err)
    }
    fmt.Println(string(data))

    // 写入整个文件(一次性写入)
    content := []byte("Hello, Go 2026!\n")
    err = os.WriteFile("hello.txt", content, 0644)
    if err != nil {
        panic(err)
    }

    // 追加模式
    f, _ := os.OpenFile("hello.txt", os.O_APPEND|os.O_WRONLY, 0644)
    defer f.Close()
    f.WriteString("追加的内容\n")
}

⚠️ os.ReadFileos.WriteFile 只适合处理小文件 (KB 级别)。大文件(MB/GB 级别)请使用 bufio 或流式处理。


9. path/filepath 包:跨平台路径操作

9.1 路径拼接与分割

go 复制代码
package main

import (
    "fmt"
    "path/filepath"
)

func main() {
    // Join:智能路径拼接(自动处理分隔符)
    path := filepath.Join("home", "user", "documents", "file.txt")
    fmt.Println(path) // home/user/documents/file.txt (Linux)
                      // home\user\documents\file.txt (Windows)

    // Dir / Base:获取目录和文件名
    fmt.Println(filepath.Dir("/a/b/c.txt"))  // /a/b
    fmt.Println(filepath.Base("/a/b/c.txt")) // c.txt

    // Ext:获取扩展名
    fmt.Println(filepath.Ext("file.txt"))    // .txt
    fmt.Println(filepath.Ext("file"))        // ""(无扩展名)
    fmt.Println(filepath.Ext("file.tar.gz")) // .gz(只取最后一个)

    // 去除扩展名
    name := "file.tar.gz"
    fmt.Println(name[:len(name)-len(filepath.Ext(name))]) // file.tar

    // Split:分割目录与文件名
    dir, file := filepath.Split("/a/b/c.txt")
    fmt.Println(dir, file) // /a/b/  c.txt
}

9.2 路径清理与判断

go 复制代码
// Clean:清理路径(消除 ../ 和多余分隔符)
fmt.Println(filepath.Clean("/a/b/../c/./d")) // /a/c/d

// Abs:获取绝对路径
abs, _ := filepath.Abs("./relative/path")
fmt.Println(abs) // /home/user/relative/path

// Rel:获取相对路径
rel, _ := filepath.Rel("/a/b", "/a/b/c/d")
fmt.Println(rel) // c/d

// IsAbs:判断是否绝对路径
fmt.Println(filepath.IsAbs("/usr/bin"))  // true
fmt.Println(filepath.IsAbs("./config"))  // false

9.3 通配符匹配

go 复制代码
// Match:模式匹配
fmt.Println(filepath.Match("*.go", "main.go"))       // true <nil>
fmt.Println(filepath.Match("???", "abc"))            // true <nil>
fmt.Println(filepath.Match("data[0-9].json", "data1.json")) // true <nil>

// Glob:查找匹配的文件
matches, _ := filepath.Glob("*.go")
for _, m := range matches {
    fmt.Println(m)
}

10. os.FileInfo 与文件属性

go 复制代码
func inspectFile(path string) {
    info, err := os.Stat(path)
    if err != nil {
        fmt.Println("错误:", err)
        return
    }
    if info.IsDir() {
        fmt.Println("📁", path, "(目录)")
    } else {
        fmt.Println("📄", path)
    }

    fmt.Printf("  ├─ 大小: %d 字节\n", info.Size())
    fmt.Printf("  ├─ 权限: %s (%o)\n", info.Mode(), info.Mode().Perm())
    fmt.Printf("  ├─ 修改时间: %s\n", info.ModTime().Format("2006-01-02 15:04:05"))
    fmt.Printf("  └─ 系统信息: %T\n", info.Sys())
}

// 判断文件类型
func fileType(info os.FileInfo) string {
    mode := info.Mode()
    switch {
    case mode.IsRegular():
        return "普通文件"
    case mode.IsDir():
        return "目录"
    case mode&os.ModeSymlink != 0:
        return "符号链接"
    case mode&os.ModeDevice != 0:
        return "设备文件"
    default:
        return "未知类型"
    }
}

11. 临时文件与目录

go 复制代码
package main

import (
    "fmt"
    "os"
)

func main() {
    // 创建临时文件
    // 参数:(目录, 文件名前缀)
    tmpFile, err := os.CreateTemp("", "go-prefix-*.txt")
    if err != nil {
        panic(err)
    }
    defer os.Remove(tmpFile.Name()) // 手动清理
    defer tmpFile.Close()

    fmt.Println("临时文件:", tmpFile.Name()) // /tmp/go-prefix-123456.txt

    tmpFile.WriteString("临时数据")
    tmpFile.Seek(0, 0)

    data, _ := os.ReadFile(tmpFile.Name())
    fmt.Println("内容:", string(data))

    // 创建临时目录
    tmpDir, err := os.MkdirTemp("", "go-tmpdir-*")
    if err != nil {
        panic(err)
    }
    defer os.RemoveAll(tmpDir) // 递归清理

    fmt.Println("临时目录:", tmpDir)
}
函数 用途 清理方式
os.CreateTemp(dir, pattern) 创建临时文件 os.Remove(path)
os.MkdirTemp(dir, pattern) 创建临时目录 os.RemoveAll(path)

12. 文件权限 os.FileMode

os.FileModeuint32 的类型别名,表示文件的模式和权限。

go 复制代码
type FileMode uint32

const (
    // 权限位(Unix 风格)
    ModeDir        FileMode = 1 << (32 - 1 - iota) // d: 目录
    ModeAppend                                     // a: 仅追加
    ModeExclusive                                  // l: 独占
    ModeTemporary                                  // T: 临时文件
    ModeSymlink                                    // L: 符号链接
    ModeDevice                                     // D: 设备文件
    ModeNamedPipe                                  // p: 命名管道(FIFO)
    ModeSocket                                     // S: Unix 域 socket
    ModeSetuid                                     // u: setuid
    ModeSetgid                                     // g: setgid
    ModeCharDevice                                 // c: Unix 字符设备
    ModeSticky                                     // t: sticky
    ModeIrregular                                  // ?: 非常规文件
)

// Permission 位(Unix 权限)
// rwx rwx rwx = 所有者 组 其他
// 0644 = rw-r--r--
// 0755 = rwxr-xr-x

12.1 实战:设置文件权限

go 复制代码
func main() {
    // 创建时指定权限
    os.WriteFile("script.sh", []byte("#!/bin/bash\necho hello"), 0755)
    // 权限 = rwxr-xr-x

    // 修改已有文件权限
    os.Chmod("script.sh", 0755)

    // 修改所有者(Unix only)
    // os.Chown("file.txt", uid, gid)

    // 检查权限
    info, _ := os.Stat("script.sh")
    fmt.Printf("权限: %s (%o)\n", info.Mode().Perm(), info.Mode().Perm())
    // 输出:权限: -rwxr-xr-x (755)
}

12.2 权限速查表

八进制 符号 含义
0400 r-------- 所有者可读
0200 -w------- 所有者可写
0100 --x------ 所有者可执行
0644 rw-r--r-- 所有者读写,组和其他只读(常用)
0755 rwxr-xr-x 所有者全权限,组和其他读执行(可执行文件)
0600 rw------- 仅所有者读写(敏感文件)
0777 rwxrwxrwx 所有人全权限( ⚠️ 慎用)

13. 实战:递归目录遍历工具

go 复制代码
package main

import (
    "fmt"
    "os"
    "path/filepath"
)

// WalkDir 递归遍历目录(Go 1.16+ 高效版本)
func walkDirectory(root string) error {
    return filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error {
        if err != nil {
            return err // 访问出错则跳过
        }

        // 计算缩进
        rel, _ := filepath.Rel(root, path)
        depth := len(filepath.SplitList(rel))
        indent := ""
        for i := 0; i < depth; i++ {
            indent += "  "
        }

        info, _ := d.Info()
        if d.IsDir() {
            fmt.Printf("%s📁 %s/\n", indent, d.Name())
        } else {
            size := info.Size()
            sizeStr := formatSize(size)
            fmt.Printf("%s📄 %s (%s)\n", indent, d.Name(), sizeStr)
        }
        return nil
    })
}

func formatSize(bytes int64) string {
    const unit = 1024
    if bytes < unit {
        return fmt.Sprintf("%d B", bytes)
    }
    div, exp := int64(unit), 0
    for n := bytes / unit; n >= unit; n /= unit {
        div *= unit
        exp++
    }
    return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
}

func main() {
    if len(os.Args) < 2 {
        fmt.Println("用法: go run main.go <目录>")
        return
    }
    walkDirectory(os.Args[1])
}

14. 小结与互动

核心要点回顾

知识点 一句话总结
os.Open / os.Create 基本的文件打开/创建,defer Close() 是铁律
os.OpenFile 通过位掩码组合任意打开模式
io.Reader / io.Writer Go I/O 的万能抽象,统一了所有数据源
io.Copy 复制数据的最简洁方式(32KB 默认缓冲)
bufio.Scanner 逐行读取的首选方案
os.ReadFile / os.WriteFile 替代废弃的 ioutil,适合小文件
filepath.Join 跨平台路径拼接,禁止手动拼 /
filepath.WalkDir Go 1.16+ 的高效目录遍历

📝 思考题

  1. io.Copy 内部默认使用多大的缓冲区?为什么要用这个大小?
  2. 如何用 io.TeeReader 实现一个"读取文件的同时计算其 MD5 哈希"的功能?
  3. bufio.Scanner 默认最大行长度是多少?如果日志文件单行超过这个长度会发生什么?

💬 互动

你在 Go 文件操作中踩过哪些坑?是用 ioutil 的老代码迁移了吗?欢迎在评论区分享你的经验和心得!


👨‍💻 作者 :布朗克168

📚 系列Go 入门到精通 2026

上一篇:时间与日期| 下一篇:Goroutine 入门


如果你觉得这篇文章对你有帮助,别忘了点赞 👍、收藏 ⭐ 和关注!

相关推荐
ShoreKiten6 小时前
SQL注入之SQL Server增删改查
数据库·sql
Mico186 小时前
MySQL 8.0.35 源码编译安装笔记
数据库·笔记·mysql
罗超驿6 小时前
10.Java对象比较完全指南:从equals到Comparable与Comparator
java·开发语言
蚰蜒螟6 小时前
从 Jedis 到内核:深入剖析 Redis BRPOP 的阻塞与唤醒机制
数据库·redis·缓存
脱胎换骨-军哥6 小时前
C++ 与 Go 生成质量比拼,谁是分布式主力语言
c++·分布式·golang
LiaoWL1236 小时前
【JUC合集-01】JUC并发编程复习总结
java
怒放de生命20107 小时前
【web3基础】go-zero创建rpc服务,创建配置文件(二)
开发语言·rpc·golang·web3
微露清风7 小时前
浅析 MySQL 索引
android·数据库·mysql
沈浩(种子思维作者)7 小时前
心理的研究,我从何来?
数据库·人工智能·量子计算