go语言文件处理-02
go语言zip归档文件的读写操作
Go语言的标准库提供了对几种压缩格式的支持,其中包括 gzip,因此 Go 程序可以无缝地读写 .gz 扩展名的 gzip 压缩文件或非 .gz 扩展名的非压缩文件。此外标准库也提供了读和写 .zip 文件、tar 包文件(.tar 和 .tar.gz),以及读 .bz2 文件(即 .tar .bz2 文件)的功能。
本节我们将主要介绍 zip 归档文件的读写操作。
创建zip归档文件
Go语言提供了 archive/zip 包来操作压缩文件,下面通过一个简单的的示例演示如何使用Go语言来创建一个 zip 文件,示例代码如下:
func main() {
// 创建一个缓冲区来保存压缩文件的内容
buf := new(bytes.Buffer)
// 创建一个压缩文档
w := zip.NewWriter(buf)
// 将文件加入压缩文档
files := []struct{
Name, Body string
}{
{"golang.txt", "https://coder55.com/golang/"},
{"python.py", "https://coder55.com/python/"},
}
for _, file := range files {
f, err := w.Create(file.Name)
if err != nil {
fmt.Println(err)
}
_, err = f.Write([]byte(file.Body))
if err != nil {
fmt.Println(err)
}
}
// 关闭压缩文档
err := w.Close()
if err != nil {
fmt.Println(err)
}
// 将压缩文档内容写入文件
f, err := os.OpenFile("file.zip", os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
fmt.Println(err)
}
_, _ = buf.WriteTo(f)
}
运行上面的文件会在当前目录下生成 file.zip 文件。
读取zip归档文件
读取一个 .zip 归档文件与创建一个归档文件一样简单,只是如果归档文件中包含带有路径的文件名,就必须重建目录结构。
示例代码如下所示:
func main() {
// 打开一个zip文档
rd, err := zip.OpenReader("./file.zip")
if err != nil {
fmt.Println(err)
}
defer rd.Close()
// 迭代压缩文件中的文件,打印文件内容
for _, f := range rd.File {
fmt.Printf("文件名:%s\n", f.Name)
rc, err := f.Open()
if err != nil {
fmt.Println(err)
}
//_, err = io.Copy(os.Stdout, rc);fmt.Println()
_, err = io.CopyN(os.Stdout, rc, int64(f.UncompressedSize64));fmt.Println()
if err != nil {
fmt.Println(err)
}
rc.Close()
}
}
go语言tar归档文件的读写操作
在上一节《创建 .zip 归档文件》中我们介绍了 zip 归档文件的创建和读取,那么接下来介绍一下 tar 归档文件的创建及读取。
创建tar归档文件
tar 是一种打包格式,但不对文件进行压缩,所以打包后的文档一般远远大于 zip 和 tar.gz,因为不需要压缩的原因,所以打包的速度是非常快的,打包时 CPU 占用率也很低。
tar 的目的在于方便文件的管理,比如在我们的生活中,有很多小物品分散在房间的各个角落,为了方便整洁可以将这些零散的物品整理进一个箱子中,而 tar 的功能就类似这样。
创建 tar 归档文件与创建 .zip 归档文件非常类似,主要不同点在于我们将所有数据都写入相同的 writer 中,并且在写入文件的数据之前必须写入完整的头部,而非仅仅是一个文件名。
tar 打包实现原理如下:
- 创建一个文件 x.tar,然后向 x.tar 写入 tar 头部信息;
- 打开要被 tar 的文件,向 x.tar 写入头部信息,然后向 x.tar 写入文件信息;
- 当有多个文件需要被 tar 时,重复第二步直到所有文件都被写入到 x.tar 中;
- 关闭 x.tar,完成打包。
下面通过示例程序简单演示一下Go语言 tar 打包的实现:
func main() {
// 创建一个tar文件
f, err := os.Create("./output.tar")
if err != nil {
fmt.Println(err)
}
defer f.Close()
tw := tar.NewWriter(f)
defer tw.Close()
// 获取文件相关信息
fileInfo, err := os.Stat("out.txt")
if err != nil {
fmt.Println(err)
}
hdr, err := tar.FileInfoHeader(fileInfo, "")
if err != nil {
fmt.Println(err)
}
// 写入头文件信息
if err = tw.WriteHeader(hdr); err != nil {
fmt.Println(err)
}
file, err := os.Open("out.txt")
if err != nil {
fmt.Println(err)
}
// 将文件的信息写入到压缩包中
m, err := io.Copy(tw, file)
if err != nil {
fmt.Println(err)
}
fmt.Println(m)
}
运行上面的代码会在当前目录下生成一个 output.tar 文件。
解压tar归档文件
解压 tar 归档文件比创建 tar 归档文档稍微简单些。首先需要将其打开,然后从这个 tar 头部中循环读取存储在这个归档文件内的文件头信息,从这个文件头里读取文件名,以这个文件名创建文件,然后向这个文件里写入数据即可。
示例代码如下所示:
func main() {
f, err := os.Open("./output.tar")
if err != nil {
fmt.Println(err)
}
defer f.Close()
r := tar.NewReader(f)
for hdr, err := r.Next(); err != io.EOF; hdr, err = r.Next() {
if err != nil {
fmt.Println(err)
return
}
fileInfo := hdr.FileInfo()
fmt.Println(fileInfo.Name())
f, err := os.Create("123" + fileInfo.Name())
if err != nil {
fmt.Println(err)
}
defer f.Close()
_, err = io.Copy(f, r)
if err != nil {
fmt.Println(err)
}
}
}
运行上面的程序会将 tar 包的文件解压到当前目录中。
至此,我们完成了对压缩和归档文件及常规文件处理的介绍。Go语言使用 io.Reader、io.ReadCloser、io.Writer 和 io.WriteCloser 等接口处理文件的方式让开发者可以使用相同的编码模式来读写文件或者其他流(如网络流或者甚至是字符串),从而大大降低了难度。
go语言使用buffer读取文件
buffer 是缓冲器的意思,Go语言要实现缓冲读取需要使用到 bufio 包。bufio 包本身包装了 io.Reader 和 io.Writer 对象,同时创建了另外的 Reader 和 Writer 对象,因此对于文本 I/O 来说,bufio 包提供了一定的便利性。
buffer 缓冲器的实现原理就是,将文件读取进缓冲(内存)之中,再次读取的时候就可以避免文件系统的 I/O 从而提高速度。同理在进行写操作时,先把文件写入缓冲(内存),然后由缓冲写入文件系统。
使用bufio包写入文件
bufio 和 io 包中有很多操作都是相似的,唯一不同的地方是 bufio 提供了一些缓冲的操作,如果对文件 I/O 操作比较频繁的,使用 bufio 包能够提高一定的性能。
在 bufio 包中,有一个 Writer 结构体,而其相关的方法支持一些写入操作,如下所示。
//Writer 是一个空的结构体,一般需要使用 NewWriter 或者 NewWriterSize 来初始化一个结构体对象
type Writer struct {
// contains filtered or unexported fields
}
//NewWriterSize 和 NewWriter 函数
//返回默认缓冲大小的 Writer 对象(默认是4096)
func NewWriter(w io.Writer) *Writer
//指定缓冲大小创建一个 Writer 对象
func NewWriterSize(w io.Writer, size int) *Writer
//Writer 对象相关的写入数据的方法
//把 p 中的内容写入 buffer,返回写入的字节数和错误信息。如果 nn < len(p),返回错误信息中会包含为什么写入的数据比较短
func (b *Writer) Write(p []byte) (nn int, err error)
//将 buffer 中的数据写入 io.Writer
func (b *Writer) Flush() error
//以下三个方法可以直接写入到文件中
//写入单个字节
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)
示例代码如下:
func main() {
name := "demo.txt"
content := "https://www.mayanan.cn"
f, err := os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
fmt.Println(err)
}
defer f.Close()
writer := bufio.NewWriterSize(f, 4096)
_, err = writer.Write([]byte(content))
if err != nil {
fmt.Println(err)
return
}
if err = writer.Flush(); err != nil {
fmt.Println("刷盘失败")
}else{
fmt.Println("刷盘成功")
}
}
运行上面的代码会在当前目录之下生成 demo.txt 文件,并将http://www.mayanan.cn写入到该文件中。
使用bufio包读取文件
使用 bufio 包读取文件也非常方便,我们先来看下 bufio 包的相关的 Reader 函数方法:
//首先定义了一个用来缓冲 io.Reader 对象的结构体,同时该结构体拥有以下相关的方法
type Reader struct {
}
//NewReader 函数用来返回一个默认大小 buffer 的 Reader 对象(默认大小是 4096) 等同于 NewReaderSize(rd,4096)
func NewReader(rd io.Reader) *Reader
//该函数返回一个指定大小 buffer(size 最小为 16)的 Reader 对象,如果 io.Reader 参数已经是一个足够大的 Reader,它将返回该 Reader
func NewReaderSize(rd io.Reader, size int) *Reader
//该方法返回从当前 buffer 中能被读到的字节数
func (b *Reader) Buffered() int
//Discard 方法跳过后续的 n 个字节的数据,返回跳过的字节数。如果 0 <= n <= b.Buffered(),该方法将不会从 io.Reader 中成功读取数据
func (b *Reader) Discard(n int) (discarded int, err error)
//Peekf 方法返回缓存的一个切片,该切片只包含缓存中的前 n 个字节的数据
func (b *Reader) Peek(n int) ([]byte, error)
//把 Reader 缓存对象中的数据读入到 []byte 类型的 p 中,并返回读取的字节数。读取成功,err 将返回空值
func (b *Reader) Read(p []byte) (n int, err error)
//返回单个字节,如果没有数据返回 err
func (b *Reader) ReadByte() (byte, error)
//该方法在 b 中读取 delimz 之前的所有数据,返回的切片是已读出的数据的引用,切片中的数据在下一次的读取操作之前是有效的。如果未找到 delim,将返回查找结果并返回 nil 空值。因为缓存的数据可能被下一次的读写操作修改,因此一般使用 ReadBytes 或者 ReadString,他们返回的都是数据拷贝
func (b *Reader) ReadSlice(delim byte) (line []byte, err error)
//功能同 ReadSlice,返回数据的拷贝
func (b *Reader) ReadBytes(delim byte) ([]byte, error)
//功能同 ReadBytes,返回字符串
func (b *Reader) ReadString(delim byte) (string, error)
//该方法是一个低水平的读取方式,一般建议使用 ReadBytes('\n') 或 ReadString('\n'),或者使用一个 Scanner 来代替。ReadLine 通过调用 ReadSlice 方法实现,返回的也是缓存的切片,用于读取一行数据,不包括行尾标记(\n 或 \r\n)
func (b *Reader) ReadLine() (line []byte, isPrefix bool, err error)
//读取单个 UTF-8 字符并返回一个 rune 和字节大小
func (b *Reader) ReadRune() (r rune, size int, err error)
示例代码如下:
func main() {
f, err := os.Open("demo.txt")
if err != nil {
fmt.Println(err)
}
defer f.Close()
reader := bufio.NewReaderSize(f, 4096)
ret := make([]byte, 1024)
n, err := reader.Read(ret)
if err != nil {
fmt.Println(err)
}
fmt.Println("读取到的字节数是:", n)
fmt.Println(string(ret[:n]))
}
运行结果:
读取到的字节数是: 28
https://www.mayanan.cn哈哈
go语言文件的写入、追加、读取、复制
Go语言的 os 包下有一个 OpenFile 函数,其原型如下所示:
func OpenFile(name string, flag int, perm FileMode) (*File, error)
其中 name 是文件的文件名,如果不是在当前路径下运行需要加上具体路径;flag 是文件的处理参数,为 int 类型,根据系统的不同具体值可能有所不同,但是作用是相同的。
下面列举了一些常用的 flag 文件处理参数:
- O_RDONLY:只读模式打开文件;
- O_WRONLY:只写模式打开文件;
- O_RDWR:读写模式打开文件;
- O_APPEND:写操作时将数据附加到文件尾部(追加);
- O_CREATE:如果不存在将创建一个新文件;
- O_EXCL:和 O_CREATE 配合使用,文件必须不存在,否则返回一个错误;
- O_SYNC:当进行一系列写操作时,每次都要等待上次的 I/O 操作完成再进行;
- O_TRUNC:如果可能,在打开时清空文件。
【示例 1】:创建一个新文件 golang.txt,并在其中写入 5 句 "http://www.mayanan.cn"
func main() {
file, err := os.OpenFile("golang.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println(err)
}
defer file.Close()
str := "http://www.mayanan.cn\n"
// 写入文件时使用带缓冲的buffer
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
if _, err = writer.WriteString(str); err != nil {
fmt.Println(err)
}
}
// 将缓存文件刷盘
if err = writer.Flush(); err != nil {
fmt.Println(err)
}
}
执行成功之后会在指定目录下生成一个 golang.txt 文件。
【示例 2】:打开一个存在的文件,在原来的内容追加内容“Go语言中文社区”
func main() {
file, err := os.OpenFile("golang.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println(err)
}
defer file.Close()
str := "Go语言中文社区\n"
// 写入文件时使用带缓冲的buffer
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
if _, err = writer.WriteString(str); err != nil {
fmt.Println(err)
}
}
// 将缓存文件刷盘
if err = writer.Flush(); err != nil {
fmt.Println(err)
}
}
【示例 3】:打开一个存在的文件,将原来的内容读出来,显示在终端,并且追加 5 句“Hello,Go语言中文社区”。
func main() {
file, err := os.OpenFile("golang.txt", os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
reader := bufio.NewReader(file)
for {
str, err := reader.ReadString('\n')
if err == io.EOF {
break
}
fmt.Print(str)
}
if err != nil {
fmt.Println(err)
}
defer file.Close()
str := "Hello Go语言中文社区\n"
// 写入文件时使用带缓冲的buffer
writer := bufio.NewWriter(file)
for i := 0; i < 5; i++ {
if _, err = writer.WriteString(str); err != nil {
fmt.Println(err)
}
}
// 将缓存文件刷盘
if err = writer.Flush(); err != nil {
fmt.Println(err)
}
}
执行成功之后,会在控制台打印出文件的内容,并在文件中追加指定的内容。
【示例 4】:编写一个程序,将一个文件的内容复制到另外一个文件(注:这两个文件都已存在)
方法1:io.Copy()方法进行复制
func main() {
rf, err := os.Open("golang.txt")
if err != nil {
fmt.Println(err)
}
defer rf.Close()
wf, err := os.OpenFile("out.txt", os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
fmt.Println(err)
}
_, err = io.Copy(wf, rf)
if err != nil {
fmt.Println(err)
}
}
方法2:ioutil包
func main() {
data, err := ioutil.ReadFile("golang.txt")
if err != nil {
fmt.Println(err)
}
// 注意ioutil.WriteFile的写入flag是:O_WRONLY|O_CREATE|O_TRUNC
// 也就是说不会往文件追加内容,如果文件已经有内容了,先清空在写入
if err = ioutil.WriteFile("out.txt", data, 0644); err != nil {
fmt.Println(err)
}
}
执行成功后,发现内容已经复制成功。