文件和系统指令
文件概述
实际开发中常常会遇到对数据进行持久化操作的场景,而实现数据持久化最直接简单的方式就是将数据保存到文件中。说到“文件”这个词,可能需要先科普一下关于文件系统的知识,但是这里我们并不浪费笔墨介绍这个概念,请大家自行通过维基百科进行了解。
在go中实现文件的读写操作其实非常简单,通过golang内置的os
包和io
包来对文件进行读写操作。简单示例
package main
import (
"fmt"
"os"
)
func main() {
// 创建文件
err := os.WriteFile("example.txt", []byte("Hello, World!"), 0644)
if err != nil {
fmt.Println("创建文件失败:", err)
return
}
fmt.Println("文件创建成功")
}
文件权限位掩码
在 Go 语言中,文件权限使用一个数字来表示,这个数字是文件权限的位掩码,其中每一位代表不同的权限(与linux系统相似)。
- 读权限 (0400):用户可以读取文件内容。
- 写权限 (0200):用户可以修改文件内容。
- 执行权限 (0100):用户可以执行文件(如果文件是一个可执行文件)。
这里以
0400
为例进行解析,4
用二进制表示为100
,这个二进制就表示了文件权限1(读)0(写)0(执行)
其中1
就是拥有权限,0
就是没有;那么010
所对应的2
就是写权限,可以思考一下110
代表了哪个数字,哪个权限。
这些权限位可以组合,形成不同的权限组合。例如,0644
表示用户有读写权限,组和其他用户有读权限。
那么0400
这四位数字又分别代表什么,左起第一个0
代表的是文件类型,0
是普通类型,左起第二位4
代表的是当前用户权限,第三位0
代表的是用户组权限,第四位0
是其他用户权限,可以看一张表
权限位掩码 | 权限 |
---|---|
0400 | 用户读权限 |
0200 | 用户写权限 |
0100 | 用户执行权限 |
0040 | 组读权限 |
0020 | 组写权限 |
0010 | 组执行权限 |
0004 | 其他用户读权限 |
0002 | 其他用户写权限 |
0001 | 其他用户执行权限 |
文件操作
打开和关闭文件,打开文件用os的Open
函数,创建用Create
,这两个都会返回一个文件指针和一个错误类型。关闭文件直接操作File对象,调用Close就会关闭
-
os.Open(name string) (*os.File, error):打开一个文件,返回一个文件指针和可能的错误。
-
os.Create(name string) (*os.File, error):创建一个新文件,返回一个文件指针和可能的错误。
file.Close():关闭一个文件。
file, err := os.Open("example.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close() // 最后关闭文件
注意上面的代码,如果
Open
函数指定的文件并不存在或者无法打开,那么将引发异常状况记录至err
。这是为了让代码有一定的健壮性和容错性,关于err
和defer
的使用,后面会再详细介绍。
读取文件,根据文件的类型不同,可以考虑使用以下的三种方法
file.Read 方法:
- file.Read 是一个低级的方法,它直接从文件描述符中读取数据。
- 它可以用于读取任意长度的数据,而不是整个文件或单行。
- 它需要用户提供一个字节切片作为缓冲区,读取的数据将被写入这个缓冲区。
- 它返回读取的字节数和可能的错误。如果到达文件末尾,错误将是 io.EOF。
os.ReadFile 方法:
- os.ReadFile 是一个高级的方法,它一次性读取整个文件内容(会忽略换行)。
- 它内部会创建一个足够大的缓冲区来存储整个文件内容。
- 它返回文件的字节切片和可能的错误。
bufio.Scanner:
- bufio.Scanner 是一个用于逐行或按自定义规则读取文件的扫描器。
它不会一次性读取整个文件,而是根据分割函数(默认是按行分割)逐块读取。 - 它通过 Scan 方法来推进到下一个数据块,通过 Text 或 Bytes 方法来获取数据。
package main
import (
"fmt"
"io"
"os"
"bufio"
)
func main() {
//用file.Read
file, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
buffer := make([]byte, 1024) // 创建一个 1KB 的缓冲区
for {
n, err := file.Read(buffer) // 读取数据到缓冲区
if err == io.EOF {
break // 如果到达文件末尾,则退出循环
}
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Printf("Read %d bytes: %s\n", n, buffer[:n])
}
//用os.ReadFile
data, err := os.ReadFile("example.txt")
if err != nil {
fmt.Println("Error reading file:", err)
return
}
fmt.Println("File contents:\n", string(data))
//用bufio.Scanner
data2, err := os.Open("example.txt")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer data2.Close()
scanner := bufio.NewScanner(data2)
for scanner.Scan() {
line := scanner.Text()
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
fmt.Println("Error scanning file:", err)
}
}
说明:以上读取均是基于简单纯文本文件,例如日志文件这些,如果要读xsl,csv等相对复杂的文件,需要引入专门的包去处理
写入文件可以使用os的WriteFile
,传入文件名,写入的数据,文件权限;或者使用file对象的Write
方法。
-
os.WriteFile(name string, data []byte, perm FileMode) error:将数据写入文件,如果文件已存在则覆盖,返回可能的错误。
-
file.Write(b []byte) (n int, err error):将字节切片 b 的内容写入文件。
data := []byte("Hello, World!")
err := os.WriteFile("example.txt", data, 0644)
if err != nil {
log.Fatal(err)
}
// 或者使用 file.Write()
err = file.Write(data)
if err != nil {
log.Fatal(err)
}
读写二进制文件
知道了如何读写文本文件要读写二进制文件也就很简单了,下面的代码实现了复制图片文件的功能。
package main
import (
"fmt"
"io"
"os"
)
func main() {
// 打开源图片文件
sourceFile, err := os.Open("source.jpg")
if err != nil {
fmt.Println("Error opening source file:", err)
return
}
defer sourceFile.Close()
// 创建目标图片文件
destFile, err := os.Create("destination.jpg")
if err != nil {
fmt.Println("Error creating destination file:", err)
return
}
defer destFile.Close()
// 创建缓冲区
buffer := make([]byte, 1024)
// 读取并写入数据
for {
n, err := sourceFile.Read(buffer)
if err == io.EOF {
break // 如果到达文件末尾,则退出循环
}
if err != nil {
fmt.Println("Error reading from source file:", err)
return
}
// 写入到目标文件
_, err = destFile.Write(buffer[:n])
if err != nil {
fmt.Println("Error writing to destination file:", err)
return
}
}
fmt.Println("File copied successfully.")
}
读写JSON文件
通过上面的讲解,我们已经知道如何将文本数据和二进制数据保存到文件中,那么这里还有一个问题,如果希望把一个列表或者一个字典中的数据保存到文件中又该怎么做呢?答案是将数据以JSON格式进行保存。JSON是“JavaScript Object Notation”的缩写,它本来是JavaScript语言中创建对象的一种字面量语法,现在已经被广泛的应用于跨平台跨语言的数据交换,原因很简单,因为JSON也是纯文本,任何系统任何编程语言处理纯文本都是没有问题的。目前JSON基本上已经取代了XML作为异构系统间交换数据的事实标准。关于JSON的知识,更多的可以参考JSON的官方网站,从这个网站也可以了解到每种语言处理JSON数据格式可以使用的工具或三方库,下面是一个JSON的简单例子。
{
"id": "file-001",
"bytes": 389510,
"created_at": 1714031539,
"filename": "技术服务岗位模型.pdf",
"object": "file",
"purpose": "assistants",
"status": "processed",
"status_details": null
}
在golang中,json一般用struct
或者map["string"]insterface{}
来存储,然后有专门的json
包来处理读写,可以看看下面的例子
读取
package main
import (
"encoding/json"
"fmt"
"os"
)
// 定义一个结构体来存储 JSON 数据
type File struct {
ID string `json:"id"`
Bytes int `json:"bytes"`
CreatedAt int `json:"created_at"`
Filename string `json:"filename"`
Object string `json:"object"`
Purpose string `json:"purpose"`
Status string `json:"status"`
StatusDetails *string `json:"status_details"`
}
func main() {
// 打开 JSON 文件
file, err := os.Open("file.json")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
// 创建一个 JSON 解码器
decoder := json.NewDecoder(file)
// 定义一个变量来存储解码后的数据
var fileData File
// 解码 JSON 数据
err = decoder.Decode(&fileData)
if err != nil {
fmt.Println("Error decoding JSON:", err)
return
}
// 输出解码后的数据
fmt.Printf("ID: %s, Bytes: %d, Filename: %s, Status: %s\n", fileData.ID, fileData.Bytes, fileData.Filename, fileData.Status)
}
写入
package main
import (
"encoding/json"
"fmt"
"os"
)
// 定义一个结构体来存储 JSON 数据
type File struct {
ID string `json:"id"`
Bytes int `json:"bytes"`
CreatedAt int `json:"created_at"`
Filename string `json:"filename"`
Object string `json:"object"`
Purpose string `json:"purpose"`
Status string `json:"status"`
StatusDetails *string `json:"status_details"`
}
func main() {
// 创建一个 File 对象
file := File{
ID: "file-001",
Bytes: 389510,
CreatedAt: 1714031539,
Filename: "技术服务岗位模型.pdf",
Object: "file",
Purpose: "assistants",
Status: "processed",
}
// 打开或创建 JSON 文件
file, err := os.Create("file.json")
if err != nil {
fmt.Println("Error creating file:", err)
return
}
defer file.Close()
// 创建一个 JSON 编码器
encoder := json.NewEncoder(file)
// 编码 File 对象为 JSON 数据
err = encoder.Encode(file)
if err != nil {
fmt.Println("Error encoding JSON:", err)
return
}
fmt.Println("JSON data written to file successfully.")
}
json模块主要有两个比较重要的概念,分别是:
序列化(Encoding)
- Marshal:
- func Marshal(v interface{}) ([]byte, error)
- 接受一个接口值,返回该值的 JSON 编码形式([]byte),并返回可能的错误。
- MarshalIndent:
- func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
- 类似于 Marshal,但它会在 JSON 值前添加缩进,使得 JSON 数据更易于阅读。
- MarshalJSON:
- func (v interface{}) MarshalJSON() ([]byte, error)
- 接受一个接口值,返回该值的 JSON 编码形式([]byte),并返回可能的错误。
反序列化(Decoding)
- Unmarshal:
- func Unmarshal(data []byte, v interface{}) error
- 接受一个 []byte 类型的 JSON 数据和一个接口值,尝试将 JSON 数据解码到接口值中,并返回可能的错误。
- UnmarshalJSON:
- func (v interface{}) UnmarshalJSON(data []byte) error
- 接受一个 []byte 类型的 JSON 数据和一个接口值,尝试将 JSON 数据解码到接口值中,并返回可能的错误。
这里出现了两个概念,一个叫序列化,一个叫反序列化。自由的百科全书维基百科上对这两个概念是这样解释的:“序列化(serialization)在计算机科学的数据处理中,是指将数据结构或对象状态转换为可以存储或传输的形式,这样在需要的时候能够恢复到原先的状态,而且通过序列化的数据重新获取字节时,可以利用这些字节来产生原始对象的副本(拷贝)。与这个过程相反的动作,即从一系列字节中提取数据结构的操作,就是反序列化(deserialization)”。
说明:Marshal 和 Unmarshal 方法与 Encode 和 Decode 方法是等效的,它们都用于序列化和反序列化 JSON 数据。这些方法的名字和参数略有不同,但它们的行为和功能是相同的
系统指令执行流程
go语言对于linux一类系统,具有比较好的执行系统指令的包可供调用,这也是go的主要应用方向之一,用于编写系统脚本和采集系统指标。
通常是通过 os/exec 包来实现的。这个包提供了一个接口,允许你创建一个 Cmd 结构体,设置命令参数,然后执行这个命令。其执行系统流程例子如下
package main
import (
"bytes"
"fmt"
"os/exec"
)
func main() {
// 创建命令
cmd := exec.Command("ls")
// 设置工作目录(可选)
cmd.Dir = "/path/to/working/directory"
// 设置环境变量(可选)
cmd.Env = os.Environ()
// 执行命令
err := cmd.Start()
if err != nil {
fmt.Println("Error starting command:", err)
return
}
// 等待命令完成
err = cmd.Wait()
if err != nil {
fmt.Println("Error waiting for command to complete:", err)
return
}
// 处理输出和错误
stdoutData, err := ioutil.ReadAll(cmd.Stdout)
if err !=nil{
fmt.Println("Error ReadAll for command to complete:", err)
return
}
}
简单指标采集
在 Go 语言中,采集系统指标通常涉及与操作系统交互,获取 CPU 使用率、内存使用情况、磁盘使用情况等数据。Go 语言的标准库 os 和 runtime 提供了基础的系统信息,而第三方库如 gopsutil 提供了更丰富的系统监控功能
package main
import (
"fmt"
"os"
"runtime"
"time"
)
func main() {
// 获取 CPU 使用率
cpuUsage := runtime.NumCPU()
fmt.Printf("CPU Usage: %d\n", cpuUsage)
// 获取内存使用情况
memUsage := runtime.MemStats{}
runtime.ReadMemStats(&memUsage)
fmt.Printf("Memory Usage: %d MB\n", memUsage.Sys/1e6)
// 获取磁盘使用情况
diskUsage := syscall.Statfs_t{}
err := syscall.Statfs("/", &diskUsage)
if err != nil {
fmt.Println("Error getting disk usage:", err)
return
}
fmt.Printf("Disk Usage: %d GB\n", diskUsage.Blocks*uint64(diskUsage.Bsize)/1e9)
// 等待一段时间
time.Sleep(time.Second * 5)
}
在这个示例中,我们使用了以下标准库函数来采集指标:
- runtime.NumCPU():返回当前系统的 CPU 核心数。
- runtime.MemStats():返回当前程序的内存使用情况。
- syscall.Statfs():返回文件系统的统计信息,包括磁盘使用情况。