包管理和文件读写

Package与模块化管理

在 Go 中,包(Package) 是代码组织和复用的基本单位。而自 Go 1.11 版本引入的 Go Modules (go mod),已成为官方推荐的依赖管理系统,它使得项目可以独立于 $GOPATH 存在,并能精确控制依赖版本。

包的基本概念

  • 包声明: 每个 .go 文件的第一行都必须是 package <包名>
  • main 包: 一个可执行程序的入口点必须是 main 包,且包含一个 main() 函数。
  • 导入包: 使用 import 关键字导入其他包。可以为导入的包设置别名,或使用 . 将其成员直接引入当前命名空间(不推荐)。
    go
    import (
        "fmt"
        "os"
        fm "fmt"      // 使用别名 fm
        . "strings" // 不推荐,直接使用 strings 包的函数,如 ToUpper()
    )
  • 可见性: 标识符(变量、常量、类型、函数等)的可见性由其首字母的大小写决定。大写字母开头的标识符是公开的(Exported),可以被其他包访问;小写字母开头的是私有的(Unexported),仅在包内可见。

使用 go mod 管理项目

go mod 让依赖管理变得前所未有的简单。

核心命令:

  • go mod init <module_path>: 在当前目录下初始化一个新的模块。<module_path> 通常是代码仓库的路径,如 github.com/your-username/my-project。此命令会生成一个 go.mod 文件。
    bash
    mkdir myproject
    cd myproject
    go mod init github.com/user/myproject
  • go get <package_path>[@version]: 添加新的依赖或更新现有依赖。例如,获取 gorilla/mux 的最新版本。
    bash
    go get github.com/gorilla/mux
  • go mod tidy: 最常用的命令之一。它会分析代码中的 import 语句,移除 go.mod 文件中不再使用的依赖,并添加代码中需要但尚未记录的依赖。
  • go build, go test, go run: 这些命令在模块模式下会自动下载和管理所需的依赖。

go.mod 文件: 这是模块的核心,记录了模块路径、Go 版本以及所有直接和间接依赖及其版本。

go.sum 文件: 自动生成,记录了每个依赖包在特定版本的加密哈希值,用于保证依赖的完整性和安全性,防止下载被篡改的包。应将其提交到版本控制系统中


文件读写(I/O 操作)

Go 的 osio 包提供了强大而简洁的文件操作接口。

读取文件

最常用、最安全的方式是使用 os.ReadFile,它能一次性读取整个文件的内容。

go
package main

import (
    "fmt"
    "os"
)

func main() {
    // 读取整个文件
    content, err := os.ReadFile("example.txt")
    if err != nil {
        fmt.Println("Error reading file:", err)
        return
    }
    fmt.Println(string(content))
}

对于大文件,应使用流式读取,以避免一次性加载过多内容到内存。

go
package main

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

func main() {
    file, err := os.Open("large_file.txt")
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close() // 确保文件在函数结束时关闭

    scanner := bufio.NewScanner(file)
    // 逐行读取
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Error scanning file:", err)
    }
}

写入文件

使用 os.WriteFile 可以方便地将字节切片写入文件。该函数会自动处理文件的创建和权限设置。

go
package main

import (
    "fmt"
    "os"
)

func main() {
    data := []byte("Hello, Golang I/O!")
    // 写入文件,如果文件不存在则创建。0666 是文件权限。
    err := os.WriteFile("output.txt", data, 0666)
    if err != nil {
        fmt.Println("Error writing to file:", err)
    }
}

追加内容到文件,需要使用更底层的 os.OpenFile

go
package main

import (
    "fmt"
    "os"
)

func main() {
    // os.O_APPEND: 追加模式, os.O_CREATE: 文件不存在则创建, os.O_WRONLY: 只写模式
    file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
    if err != nil {
        fmt.Println("Error opening file:", err)
        return
    }
    defer file.Close()

    if _, err := file.WriteString("This is a new log entry.\n"); err != nil {
        fmt.Println("Error writing to file:", err)
    }
}

JSON 操作

encoding/json 包是 Go 处理 JSON 的标准库,支持将 Go 的结构体与 JSON 数据进行相互转换(序列化和反序列化)。

序列化 (Marshal)

序列化 (Marshal): Go 结构体 -> JSON

将一个 Go 的数据结构(通常是 structmap)转换为 JSON 字节流。

关键点:结构体的字段必须是**首字母大写(公开的)**才能被 json 包访问和序列化。可以使用 json 标签自定义字段在 JSON 中的名称。

go
package main

import (
    "encoding/json"
    "fmt"
    "os"
)

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Email    string `json:"email"`
    Password string `json:"-"` // "-" 表示此字段不参与序列化
}

func main() {
    user := User{ID: 1, Name: "Alice", Email: "[email protected]", Password: "secret"}

    // 序列化
    jsonData, err := json.MarshalIndent(user, "", "  ") // MarshalIndent 生成带缩进的格式化 JSON
    if err != nil {
        fmt.Println("Error marshaling to JSON:", err)
        return
    }

    // 打印或写入文件
    fmt.Println(string(jsonData))
    os.WriteFile("user.json", jsonData, 0666)
}

反序列化 (Unmarshal)

反序列化 (Unmarshal): JSON -> Go 结构体 :将 JSON 字节流解析到 Go 的数据结构中。

go
package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func main() {
    jsonData := []byte(`{"id": 2, "name": "Bob", "email": "[email protected]"}`)
    var user User

    // 反序列化
    err := json.Unmarshal(jsonData, &user) // 第二个参数必须是指针
    if err != nil {
        fmt.Println("Error unmarshaling JSON:", err)
        return
    }

    fmt.Printf("User: %+v\n", user)
}

表格处理(以 CSV 为例)

处理表格数据(如 CSV, Excel)是数据处理的常见任务。Go 的 encoding/csv 包提供了对 CSV 文件的强大支持。

读取 CSV 文件

go
package main

import (
    "encoding/csv"
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("data.csv")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer file.Close()

    reader := csv.NewReader(file)
    // 可以设置分隔符,默认为逗号 ','
    // reader.Comma = ';' 

    // 读取所有记录
    records, err := reader.ReadAll()
    if err != nil {
        fmt.Println("Error reading CSV:", err)
        return
    }

    for i, record := range records {
        fmt.Printf("Row %d: %v\n", i+1, record)
    }
}

写入 CSV 文件

go
package main

import (
    "encoding/csv"
    "fmt"
    "os"
)

func main() {
    file, err := os.Create("output.csv")
    if err != nil {
        fmt.Println("Error:", err)
        return
    }
    defer file.Close()

    writer := csv.NewWriter(file)
    defer writer.Flush() // 确保所有缓冲的数据都写入文件

    // 待写入的数据
    data := [][]string{
        {"Name", "Age", "City"},
        {"Alice", "30", "New York"},
        {"Bob", "25", "London"},
    }

    for _, record := range data {
        err := writer.Write(record)
        if err != nil {
            fmt.Println("Error writing record to csv:", err)
        }
    }
}

通过 writer.WriteAll(data) 可以一次性写入所有数据,更为便捷。