Go 的 IO 模型 #
核心接口 #
Go语言的IO操作建立在几个核心接口上,这些接口构成了灵活而一致的IO模型。
在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 ReadWriter interface {
Reader
Writer
}
type ReadCloser interface {
Reader
Closer
}
type WriteCloser interface {
Writer
Closer
}
type ReadWriteCloser interface {
Reader
Writer
Closer
}
IO操作模式 #
Go的IO操作遵循一些常见模式:
- 数据块处理:大多数IO函数处理的是字节切片(
[]byte),而非单个字节。 - 错误处理:IO操作总是返回读/写的字节数和一个错误值。
- EOF标识:当读取到文件或流的末尾时,返回特殊的
io.EOF错误。 - 资源管理:使用
defer和Close()确保资源正确释放。
各个IO相关包的职责 #
Go标准库中与IO相关的包有多个,各自负责不同层次的功能:
io:提供基础的IO接口和函数io/ioutil:简化常见IO操作(Go 1.16后部分功能迁移到io和os包)os:提供操作系统功能,包括文件操作bufio:提供缓冲IO,提高性能bytes和strings:提供对字节切片和字符串的类似文件的访问
os.File #
在 Golang 中内置包os中,打开文件使用 Open 函数(只读)和OpenFile函数(可切换模式),关闭文件使用 Close 函数,打开文件、关闭文件以及大多数文件操作都涉及到一个很重要的结构体 os.File 结构体。
实现了io.Reader、io.Writer、io.Closer等接口
type File struct {
*file // file 指针
}
type file struct {
//是一个FD结构体类型,是一个文件的唯一标志,每一个被打开的文件在操作系统中,都会有一个文件标志符来唯一标识一个文件
pfd poll.FD
//文件名
name string
//文件的路径信息,也是一个结构体
dirinfo *dirInfo
//是一个 bool 类型,表明该文件是否可以被追加写入内容。
appendMode bool
}
io.EOF #
是 error 类型。
根据 reader 接口的说明,在 n > 0 且数据被读完了的情况下,返回的 error 有可能是 EOF 也有可能是 nil。
var EOF = errors.New("EOF")
文件操作 #
打开文件 #
使用os.Open和os.OpenFile打开文件:
Open #
Open 函数接受一个字符串类型的文件名做为参数,如果打开成功,则返回一个 File 结构体的指针,否则就返回error错误信息。
底层使用的是OpenFile(name, O_RDONLY, 0),只读
os.Open(name string) (*File, error)
// 打开文件进行读取
file, err := os.Open("input.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close() // 确保文件会被关闭
OpenFile #
os.OpenFile(name string, flag int, perm FileMode) (file *File, err error)
//name:文件路径
//flag:文件打开方式,多个可使用|分隔开
//perm:打开文件的模式,即权限,例如0666、0777
// 打开文件进行读写
rwFile, err := os.OpenFile("data.txt", os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
log.Fatal(err)
}
defer rwFile.Close()
flag可选参数 #
| 打开方式 | 说明 |
|---|---|
os.O_RDONLY |
只读方式打开 |
os.O_WRONLY |
只写方式打开 |
os.O_RDWR |
读写方式打开 |
os.O_APPEND |
追加方式打开 |
os.O_CREATE |
不存在,则创建 |
os.O_EXCL |
如果文件存在,且标定了O_CREATE的话,则产生一个错误 |
os.O_TRUNG |
如果文件存在,且它成功地被打开为只写或读写方式,将其长度裁剪唯一。(覆盖) |
os.O_NOCTTY |
如果文件名代表一个终端设备,则不把该设备设为调用进程的控制设备 |
os.O_NONBLOCK |
如果文件名代表一个FIFO,或一个块设备,字符设备文件,则在以后的文件及I/O操作中置为非阻塞模式。 |
os.O_SYNC |
当进行一系列写操作时,每次都要等待上次的I/O操作完成再进行。 |
// 常用标志组合
// 只读模式
file, err := os.OpenFile("file.txt", os.O_RDONLY, 0)
// 只写模式,如果文件不存在则创建
file, err := os.OpenFile("file.txt", os.O_WRONLY|os.O_CREATE, 0666)
// 追加模式,如果文件不存在则创建
file, err := os.OpenFile("file.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
// 读写模式,如果文件不存在则创建
file, err := os.OpenFile("file.txt", os.O_RDWR|os.O_CREATE, 0666)
// 创建新文件,如果文件已存在则截断为空
file, err := os.OpenFile("file.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
关闭文件 #
使用 File 指针来调用 Close 函数,如果关闭失败会返回 error 错误信息。
一般在构建 File 对象后,使用defer file.Close()来保证文件关闭。
func (file *File) Close() error
其他方法 #
Seek #
Seek设置下一次读/写的位置。
offset:为相对偏移量
whence:决定相对位置:0为相对文件开头,1为相对当前位置,2为相对文件结尾。
它返回新的偏移量(相对开头)和可能的错误。
func (f *File) Seek(offset int64, whence int) (ret int64, err error)
Truncate #
Truncate改变文件的大小,它不会改变I/O的当前位置。 如果截断文件,多出的部分就会被丢弃,也就是从头开始保留size个字节。如果出错,错误底层类型是*PathError,size = 0则清空文件。
func (f *File) Truncate(size int64) error
也可以使用os包提供的方法,一样的作用,name为文件名
func Truncate(name string, size int64) error
获取文件信息 #
type FileInfo interface {
Name() string // 文件的名字
Size() int64 // 普通文件返回值表示其大小;其他文件的返回值含义各系统不同
Mode() FileMode // 文件的模式位 (例-rw-rw-r--)
ModTime() time.Time // 文件的修改时间
IsDir() bool // 等价于Mode().IsDir()
Sys() interface{} // 底层数据来源(可以返回nil)
}
os.fileStat实现了FileInfo接口的所有方法,使用os.Stat(path)函数可以获取name对应文件的fileStat实例
func Stat(name string) (FileInfo, error)
示例:
// 获取文件信息
info, err := os.Stat("myfile.txt")
if err != nil {
if os.IsNotExist(err) {
fmt.Println("文件不存在")
} else {
log.Fatal(err)
}
return
}
fmt.Println("名称:", info.Name())
fmt.Println("大小:", info.Size(), "字节")
fmt.Println("权限:", info.Mode())
fmt.Println("修改时间:", info.ModTime())
fmt.Println("是目录:", info.IsDir())
读写文件 #
io包 #
由于os.File实现了io.Reader、io.Writer接口,所以这里实际调用的是File中实现的Read()和Write()方法
常用方法 #
//读,文件读取结束的标志是返回的 n 等于 0,并且err会返回io.EOF,单次读取的大小和b的大小一样
func (f *File) Read(b []byte) (n int, err error)
//写,以字节数组写入
func (f *File) Write(b []byte) (n int, err error)
//写,以字符串写入
func (f *File) WriteString(s string) (n int, err error)
//复制文件
io.Copy(dst io.Writer, src io.Reader) (written int64, err error)
读 #
func (f *File) Read(b []byte) (n int, err error),其中b为保存读到数据的字节切片,n为读到的字节长度,如果读到文件末尾,方法会返回0,io.EOF
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Println(err)
}
defer file.Close()
//定义切片存放每次读取字节
data := make([]byte, 1024)
//声明切片,将每次读到的数据存放起来
var result []byte
for {
i, err := file.Read(data)
if err != nil && err != io.EOF {
fmt.Println(err)
break
}
if i == 0 {
break
}
result = append(result, data[:i]...)
}
fmt.Println(string(result))
}
写 #
func (f *File) Write(b []byte) (n int, err error)
package main
import (
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("./test.txt", os.O_RDWR|os.O_CREATE, 0777)
if err != nil {
fmt.Println(err)
}
defer file.Close()
s := "hello world\nhello golang"
//写入文件,将字符串转字节数组,也可以直接使用WriteString
n, err := file.Write([]byte(s))
if err != nil {
fmt.Println(err)
} else {
fmt.Println("write success,byte count =", n)
}
}
复制 #
func Copy(dst Writer, src Reader) (written int64, err error),此处注意,由于File已经实现了Writer、Reader接口,所以可以直接传入File实例
package main
import (
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Println(err)
}
defer file.Close()
fileCp, err := os.OpenFile("./testCp.txt", os.O_CREATE|os.O_WRONLY, 0777)
if err != nil {
fmt.Println(err)
}
defer fileCp.Close()
n, err := io.Copy(fileCp, file)
if err != nil {
fmt.Println(err)
}
fmt.Println("copy success,n =", n)
}
ioutil包 #
**注意:**此包内的方法,在Go 1.16后,被移动到了io或os包内。
常用方法 #
//读,由于这两个方法都是一次读取文件全部数据,所以不适合大文件,该方法在 Go1.16 被移动到 os 包
ioutil.ReadFile(filename string) ([]byte, error)
//读,这个方法需要打开一个文件,并将文件句柄传入参数
ioutil.ReadAll(r io.Reader) ([]byte, error)
//读取单层目录下所有文件(夹),返回FileInfo切片
ioutil.ReadDir(dirname string) ([]fs.FileInfo, error)
//写,文件不存在则创建文件,存在则清空文件,FileMode可以直接用0666或者0777
ioutil.WriteFile(filename string, data []byte, perm os.FileMode)
读 #
package main
import (
"fmt"
"io/ioutil"
)
func main() {
data, err := ioutil.ReadFile("./test.txt")
if err != nil {
fmt.Println(err)
}
//将字节数组转为string类型
fmt.Println(string(data))
}
写 #
package main
import (
"fmt"
"io/ioutil"
)
func main() {
s := "hello world\nhello golang"
err := ioutil.WriteFile("./test.txt", []byte(s), 0777)
if err != nil {
fmt.Println(err)
}
}
bufio包 #
bufio 包实现了缓存IO。它包装了 io.Reader 和 io.Writer 对象,创建了另外的Reader和Writer对象,它们也实现了 io.Reader 和 io.Writer 接口,不过它们是有缓存的。
io操作本身的效率并不低,低的是频繁的访问本地磁盘的文件。所以bufio就提供了缓冲区(分配一块内存),读和写都先在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率。
缓冲区原理 #
- 无论读写,设立缓冲区的目的就是:减少磁盘io的次数,以达到提高性能的目的
- 读的时候,
bufio.Read(p []byte)相当于每次读取大小len(p)的内容- 当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区
- 当缓存区没有内容的时候且
len(p)>len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可 - 当缓存区没有内容的时候且
len(p)<len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容,供下一次读取,这样也就减少了下次一读取磁盘io的数据量) - 以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区(此时和情况1一样)
- 写的时候,
bufio.Write(p []byte)相当于写入len(p)大小的内容- 判断buf中可用容量是否可以放下 p
- 如果能放下,直接把p拼接到buf后面,即把内容放到缓冲区
- 如果缓冲区的可用容量不足以放下,且此时缓冲区是空的,直接把p写入文件即可
- 如果缓冲区的可用容量不足以放下,且此时缓冲区有内容,则用p把缓冲区填满,把缓冲区所有内容写入文件,并清空缓冲区
- 判断p的剩余内容大小能否放到缓冲区,如果能放下(此时和步骤1情况一样)则把内容放到缓冲区
- 如果p的剩余内容依旧大于缓冲区,(注意此时缓冲区是空的,情况和步骤3一样)则把p的剩余内容直接写入文件
常用方法 #
bufio.Reader #
//NewReader 函数用来返回一个默认大小 buffer 的 Reader 对象(默认大小是 4096)
func NewReader(rd io.Reader) *Reader
//NewReaderSize 返回一个指定大小 buffer(size 最小为 16)的 Reader 对象,如果 io.Reader 参数已经是一个足够大的 Reader,它将返回该 Reader
func NewReaderSize(rd io.Reader, size int) *Reader
//Peek 返回缓存的一个切片,该切片引用缓存中前 n 个字节的数据,该操作不会将数据读出,只是引用
func (b *Reader) Peek(n int) ([]byte, error)
//Read 从 b 中读出数据到 p 中,返回读出的字节数和遇到的错误
func (b *Reader) Read(p []byte) (n int, err error)
//Buffered 返回缓存中未读取的数据的长度
func (b *Reader) Buffered() int
//ReadSlice 从输入中读取,直到遇到第一个界定符(delim)为止,返回一个指向缓存(也就意味着随着缓存改变而改变,所以一般用ReadBytes和ReadString)中字节的 slice,在下次调用读操作(read)时,这些字节会无效,如果在找到界定符之前缓存已经满了,ReadSlice 会返回 bufio.ErrBufferFull 错误
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
//ReadBytes 和ReadSlice区别就是返回的是当前缓存中字节slice的一个拷贝
func (b *Reader) ReadBytes(delim byte) (line []byte, err error)
//ReadString 和ReadBytes一样,只不过返回的是字符串,底层调用的是ReadBytes
func (b *Reader) ReadString(delim byte) (string, error)
//ReadLine 逐行读取,底层调用的是ReadSlice('\n')
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
//读取单个UTF-8编码的Unicode字符,并返回字符及其字节大小
func (b *Reader) ReadRune() (r rune, size int, err error)
bufio.Writer #
//返回默认缓冲大小的 Writer 对象(默认是4096),关闭文件前一定要Flush,将缓冲区的数据写入w
func NewWriter(w io.Writer) *Writer
//指定缓冲大小创建一个 Writer 对象,关闭文件前一定要Flush,将缓冲区的数据写入w
func NewWriterSize(w io.Writer, size int) *Writer
//写入单个字节,直接写入文件
func (b *Writer) WriteByte(c byte) error
//写入单个 Unicode 指针返回写入字节数错误信息,直接写入文件
func (b *Writer) WriteRune(r rune) (size int, err error)
//写入字符串并返回写入字节数和错误信息,直接写入文件
func (b *Writer) WriteString(s string) (int, error)
bufio.Scanner #
// 创建一个用于逐个读取输入缓冲区的扫描器
func NewScanner(r io.Reader) *Scanner
// 用于读取输入缓冲区中的下一个数据块,并将其保存在内部的缓冲区中。如果读取成功,则返回 true;如果已经读取了所有数据或者发生了错误,则返回 false。
func (s *Scanner) Scan() bool
// 用于获取内部缓冲区中的文本内容,通常与 Scan() 方法一起使用,用于获取读取的数据。
func (s *Scanner) Text() string
// 用于获取内部缓冲区中的字节内容,通常与 Scan() 方法一起使用,用于获取读取的数据。
func (s *Scanner) Bytes() []byte
// 用于获取在读取输入时发生的错误信息,如果读取过程中没有发生错误,则返回 nil;否则,返回一个非 nil 的错误对象。
func (s *Scanner) Err() error
// 用于自定义输入缓冲区大小,接受一个 []byte 类型的参数,用于指定缓冲区的大小。
func (s *Scanner) Buffer(buf []byte, max int)
// 用于指定一个分割函数,将输入分割成多个数据块,接受一个 func([]byte) bool 类型的参数,该函数在每次读取输入时被调用,用于判断是否需要将当前数据块分割成多个小块。通常用于处理非常大的数据块,以避免内存溢出等问题。
func (s *Scanner) Split(split SplitFunc)
读 #
package main
import (
"bufio"
"fmt"
"io"
"os"
)
func main() {
file, err := os.Open("./test.txt")
if err != nil {
fmt.Println(err)
}
defer file.Close()
fileBuf := bufio.NewReader(file)
data := make([]byte, 1024)
var result []byte
for {
i, err := fileBuf.Read(data)
if err != nil && err != io.EOF {
fmt.Println(err)
break
}
if i == 0 {
break
}
result = append(result, data[:i]...)
}
fmt.Println(string(result))
}
逐字读取utf8字符 #
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("./test.txt", os.O_RDWR, 777)
if err != nil {
fmt.Errorf("%+v", err)
}
reader := bufio.NewReader(file)
r, i, err := reader.ReadRune()
for i > 0 && err == nil {
fmt.Print(string(r))
r, i, err = reader.ReadRune()
}
file.Close()
}
逐行读 #
file, err := os.Open("data.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text() // 获取当前行文本
// 或者使用scanner.Bytes()获取[]byte
fmt.Println(line)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
自定义分割函数 #
Scanner默认按行分割,但可以自定义分割方式,内置的分割函数:
bufio.ScanLines:按行分割(默认)bufio.ScanWords:按单词分割bufio.ScanRunes:按UTF-8编码的Unicode码点分割bufio.ScanBytes:按字节分割
file, err := os.Open("words.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
// 使用单词分割函数
scanner.Split(bufio.ScanWords)
wordCount := 0
for scanner.Scan() {
word := scanner.Text()
wordCount++
fmt.Printf("单词%d: %s\n", wordCount, word)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
写 #
注意:关闭文件前一定要Flush()
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, err := os.OpenFile("./test.txt", os.O_WRONLY|os.O_CREATE, 0777)
if err != nil {
fmt.Println(err)
}
defer file.Close()
fileBuf := bufio.NewWriter(file)
s := "hello world\nhello golang"
fileBuf.Write([]byte(s))
//关闭文件前一定要fulsh,将缓冲区内的数据写入文件
fileBuf.Flush()
}
常见读取文件示例 #
一次性读取整个文件到内存(适合小文件) #
// Go 1.16前
data, err := ioutil.ReadFile("input.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
// Go 1.16+
data, err := os.ReadFile("input.txt")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(data))
使用缓冲区分块读取(适合大文件) #
file, err := os.Open("largefile.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
buffer := make([]byte, 1024) // 1KB缓冲区
for {
bytesRead, err := file.Read(buffer)
if err != nil {
if err != io.EOF {
log.Fatal(err)
}
break // 读取完毕
}
// 处理读取的数据
fmt.Print(string(buffer[:bytesRead]))
}
使用bufio逐行读取(文本文件) #
file, err := os.Open("lines.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
scanner := bufio.NewScanner(file)
// 设置每行最大长度(可选,默认64K)
// scanner.Buffer(make([]byte, 1024), 1024*1024) // 缓冲区和最大容量
lineCount := 0
for scanner.Scan() {
line := scanner.Text()
lineCount++
fmt.Printf("第%d行: %s\n", lineCount, line)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
读取特定位置的数据 #
file, err := os.Open("data.bin")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 移动到文件第100个字节
_, err = file.Seek(100, 0)
if err != nil {
log.Fatal(err)
}
// 读取10个字节
data := make([]byte, 10)
count, err := file.Read(data)
if err != nil {
log.Fatal(err)
}
fmt.Printf("读取了%d字节: %v\n", count, data)
常见写入文件示例 #
一次性写入(适合小数据) #
// Go 1.16前
data := []byte("Hello, 世界!")
err := ioutil.WriteFile("output.txt", data, 0666)
if err != nil {
log.Fatal(err)
}
// Go 1.16+
data := []byte("Hello, 世界!")
err := os.WriteFile("output.txt", data, 0666)
if err != nil {
log.Fatal(err)
}
分块写入(适合大数据) #
file, err := os.Create("output.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 多次写入
for i := 0; i < 5; i++ {
data := []byte(fmt.Sprintf("第%d行数据\n", i+1))
_, err := file.Write(data)
if err != nil {
log.Fatal(err)
}
}
使用缓冲写入(提高性能) #
file, err := os.Create("buffered.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
writer := bufio.NewWriter(file)
// 写入缓冲区
for i := 0; i < 1000; i++ {
fmt.Fprintf(writer, "第%d行\n", i+1)
}
// 确保缓冲区内容写入文件
err = writer.Flush()
if err != nil {
log.Fatal(err)
}
追加到文件 #
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
log.Fatal(err)
}
defer file.Close()
data := []byte("新的日志条目\n")
if _, err := file.Write(data); err != nil {
log.Fatal(err)
}
目录操作 #
//创建
os.Mkdir(name string, perm FileMode) error //创建目录,只创建一层,相当于mkdir
os.MkdirAll(path string, perm FileMode) error //创建多层目录,相当于mkdir -p
os.Create(name string) (file *File, err error) //创建文件,如果存在则覆盖
//移动
os.Rename(oldpath, newpath string) error //重命名、移动文件
//删除
os.Remove(name string) error //删除单个
os.RemoveAll(path string) error //递归删除目录
创建和删除目录 #
// 创建单个目录
err := os.Mkdir("newdir", 0755)
if err != nil {
log.Fatal(err)
}
// 创建多级目录
err = os.MkdirAll("path/to/nested/dir", 0755)
if err != nil {
log.Fatal(err)
}
// 删除单个目录(必须为空)
err = os.Remove("emptydir")
if err != nil {
log.Fatal(err)
}
// 删除目录及其内容
err = os.RemoveAll("dir-with-content")
if err != nil {
log.Fatal(err)
}
遍历目录内容 #
列出单级目录内容 #
// 打开目录
dir, err := os.Open(".")
if err != nil {
log.Fatal(err)
}
defer dir.Close()
// 读取目录中的条目
entries, err := dir.ReadDir(-1) // -1表示读取所有条目
if err != nil {
log.Fatal(err)
}
// 遍历条目
for _, entry := range entries {
fmt.Println("名称:", entry.Name())
fmt.Println("是目录:", entry.IsDir())
info, err := entry.Info()
if err == nil {
fmt.Println(" 大小:", info.Size())
fmt.Println(" 修改时间:", info.ModTime())
fmt.Println(" 权限:", info.Mode())
}
fmt.Println()
}
递归遍历目录 #
// 使用filepath.Walk遍历目录及其子目录
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if err != nil {
fmt.Printf("访问 %s 错误: %v\n", path, err)
return err
}
fmt.Printf("%-50s | %-10d | %s\n", path, info.Size(), info.Mode())
return nil
})
if err != nil {
log.Fatal(err)
}
WalkDir #
Go 1.16 新增。
// 使用更高效的filepath.WalkDir
err := filepath.WalkDir(".", func(path string, d fs.DirEntry, err error) error {
if err != nil {
fmt.Printf("访问 %s 错误: %v\n", path, err)
return err
}
// 只处理常规文件
if !d.IsDir() {
fmt.Printf("文件: %s\n", path)
}
return nil
})
if err != nil {
log.Fatal(err)
}
临时文件和目录 #
在开发过程中,经常需要创建临时文件和目录来存储中间数据或缓存信息。Go语言的os包提供了一些函数用于创建和管理临时文件和目录。
Go 1.16 之前在ioutil包中。
创建临时文件 #
使用os.CreateTemp函数可以创建一个临时文件。该函数会在指定目录下创建一个具有唯一名称的临时文件,并返回该文件的文件对象和路径。
package main
import (
"fmt"
"os"
)
func main() {
// 创建临时文件
file, err := os.CreateTemp("", "example_*.txt")
if err != nil {
fmt.Println("Error creating temporary file:", err)
return
}
defer os.Remove(file.Name()) // 在使用完后删除临时文件
defer file.Close()
fmt.Println("Temporary file created:", file.Name())
// 向临时文件写入数据
_, err = file.WriteString("Hello, Temporary File!")
if err != nil {
fmt.Println("Error writing to temporary file:", err)
return
}
fmt.Println("Data written to temporary file successfully")
}
上述代码会在系统默认的临时文件目录下创建一个临时文件,并写入一些数据。临时文件的名称具有唯一性,并包含example_前缀。
创建临时目录 #
使用os.MkdirTemp函数可以创建一个临时目录。该函数会在指定目录下创建一个具有唯一名称的临时目录,并返回该目录的路径。
package main
import (
"fmt"
"os"
)
func main() {
// 创建临时目录
dir, err := os.MkdirTemp("", "exampledir_*")
if err != nil {
fmt.Println("Error creating temporary directory:", err)
return
}
defer os.RemoveAll(dir) // 在使用完后删除临时目录
fmt.Println("Temporary directory created:", dir)
// 在临时目录中创建一个文件
tempFile := fmt.Sprintf("%s/%s", dir, "tempfile.txt")
file, err := os.Create(tempFile)
if err != nil {
fmt.Println("Error creating file in temporary directory:", err)
return
}
defer file.Close()
// 向文件中写入数据
_, err = file.WriteString("Hello, Temporary Directory!")
if err != nil {
fmt.Println("Error writing to file in temporary directory:", err)
return
}
fmt.Println("Data written to file in temporary directory successfully")
}
上述代码会在系统默认的临时文件目录下创建一个临时目录,并在该目录中创建一个文件并写入一些数据。
清理临时文件与目录 #
在创建临时文件和目录后,记得在使用完毕后进行清理。可以使用os.Remove和os.RemoveAll函数分别删除单个文件和目录树:
package main
import (
"fmt"
"os"
)
func main() {
// 创建临时文件
file, err := os.CreateTemp("", "example_*.txt")
if err != nil {
fmt.Println("Error creating temporary file:", err)
return
}
defer file.Close()
// 创建临时目录
dir, err := os.MkdirTemp("", "exampledir_*")
if err != nil {
fmt.Println("Error creating temporary directory:", err)
return
}
// 清理临时文件
err = os.Remove(file.Name())
if err != nil {
fmt.Println("Error removing temporary file:", err)
return
}
fmt.Println("Temporary file removed:", file.Name())
// 清理临时目录
err = os.RemoveAll(dir)
if err != nil {
fmt.Println("Error removing temporary directory:", err)
return
}
fmt.Println("Temporary directory removed:", dir)
}
上述代码会创建一个临时文件和一个临时目录,并在使用完后将其删除。
终端操作 #
- 终端其实是一个文件,相关实例如下:
os.Stdin:标准输入的文件实例,类型为*Fileos.Stdout:标准输出的文件实例,类型为*Fileos.Stderr:标准错误输出的文件实例,类型为*File
package main
import "os"
func main() {
var buf [16]byte
os.Stdin.Read(buf[:])
os.Stdin.WriteString(string(buf[:]))
}
路径解析 #
路径相关的函数有两个包,path 和 path/filepath,两个包内有一些相同的函数,如IsAbs()、Join()、Dir(),两个包的区别在于:
-
path包:提供了一些基本的路径处理功能,如路径拼接、获取文件名、获取文件扩展名等。这些功能适用于简单的路径处理需求。 -
filepath包:在path包的基础上,增加了对不同操作系统的兼容性处理,能够自动根据不同的操作系统进行路径转换。因此,如果你有跨平台的需求,建议使用filepath包。
跨平台支持:filepath包:自动根据不同的操作系统进行路径转换,确保跨平台的兼容性。例如,在Windows系统中,路径分隔符是反斜杠(\),而在Unix和Linux系统中是正斜杠(/)。filepath包能够处理这些差异,使得代码在不同操作系统上都能正常运行。
path #
// 路径拼接
func Join(elem ...string) string
// 判断文件是否是绝对路径
func IsAbs(path string) bool
// 获取目录
func Dir(path string) string
// 获取文件拓展名
func Ext(path string) string
filepath #
// 获取targpath相对于basepath的相对路径
func Rel(basepath, targpath string) (string, error)
// 获取绝对路径,如果path不是绝对路径,会加入当前工作目录以使之成为绝对路径。
func Abs(path string) (string, error)
// 获取路径中的最后一个元素
func Base(path string) string
// 获取路径中除去最后一个元素后前面的部分
func Dir(path string) string
// 获取文件拓展名
func Ext(path string) string
// 路径拼接
func Join(elem ...string) string
// 将路径拆分为目录和文件名两部分
func Split(path string) (dir, file string)