【go语言学习】文件操作file

一、File文件操作

file类是在os包中的,封装了底层的文件描述符和相关信息,同时封装了Read和Write的实现。

1、FileInfo接口

FileInfo接口中定义了File信息相关的方法。

go源码:

// os包的Stat方法返回FileInfo接口
func Stat(name string) (FileInfo, error) {}
// FileInfo接口提供了获得文件信息的方法
type FileInfo interface {
	Name() string       // base name of the file 文件名.扩展名 aa.txt
	Size() int64        // 文件大小,字节数 12540
	Mode() FileMode     // 文件权限 -rw-rw-rw-
	ModTime() time.Time // 修改时间 2018-04-13 16:30:53 +0800 CST
	IsDir() bool        // 是否文件夹
	Sys() interface{}   // 基础数据源接口(can return nil)
}
2、权限

至于操作权限perm,除非创建文件时才需要指定,不需要创建新文件时可以将其设定为0。虽然go语言给perm权限设定了很多的常量,但是习惯上也可以直接使用数字,如0666(具体含义和Unix系统的一致)。

go定义了一个权限常量ModePerm FileMode = 0777,使用os.ModePerm可以获取。

3、打开模式

文件打开模式:

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_CREAT  // 如果不存在将创建一个新文件
    O_EXCL   int = syscall.O_EXCL   // 和O_CREATE配合使用,文件必须不存在
    O_SYNC   int = syscall.O_SYNC   // 打开文件用于同步I/O
    O_TRUNC  int = syscall.O_TRUNC  // 如果可能,打开时清空文件
)
4、文件操作

go源码:

package filepath

// IsAbs reports whether the path is absolute.
func IsAbs(path string) (b bool) {}
// Abs returns an absolute representation of path.
func Abs(path string) (string, error) {
	return abs(path)
}
package os

// File 代表一个打开的文件对象
type File struct {
	*file // os specific
}
// Open打开一个文件用于读取。
// 如果操作成功,返回的文件对象的方法可用于读取数据;对应的文件描述符具有O_RDONLY模式。
// 如果出错,错误底层类型是*PathError。
func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}
// OpenFile是一个更一般性的文件打开函数,大多数调用者都应用Open或Create代替本函数。
// 它会使用指定的选项(如O_RDONLY等)、指定的模式(如0666等)打开指定名称的文件。
// 如果操作成功,返回的文件对象可用于I/O。如果出错,错误底层类型是*PathError。
func OpenFile(name string, flag int, perm FileMode) (*File, error) {}
// Create采用模式0666(任何人都可读写,不可执行)创建一个名为name的文件,如果文件已存在会截断它(为空文件)。
// 如果成功,返回的文件对象可用于I/O;对应的文件描述符具有O_RDWR模式。如果出错,错误底层类型是*PathError。
func Create(name string) (*File, error) {
	return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
// 创建文件夹,如果文件夹存在,创建失败
func Mkdir(name string, perm FileMode) error {}
// os.MkDirAll(),可以创建多层
func MkdirAll(path string, perm FileMode) error {}
// 删除文件或目录:慎用,慎用,再慎用
func Remove(name string) error {}
// 删除所有
func RemoveAll(path string) error {}

//Name方法返回文件名称。
func (f *File) Name() string { return f.name }
//Stat返回描述文件f的FileInfo类型值。如果出错,错误底层类型是*PathError。
func (file *File) Stat() (FileInfo, error) {}
// Read reads up to len(b) bytes from the File.
// It returns the number of bytes read and any error encountered.
// At end of file, Read returns 0, io.EOF.
func (f *File) Read(b []byte) (n int, err error) {}
// Write writes len(b) bytes to the File.
// It returns the number of bytes written and an error, if any.
// Write returns a non-nil error when n != len(b).
func (f *File) Write(b []byte) (n int, err error) {}
// Close关闭文件f,使文件不能用于读写。如果文件已经关闭返回错误:file already closed。
func (f *File) Close() error {}

二、i/o操作

I/O操作也叫输入输出操作。其中I是指Input,O是指Output,用于读或者写数据的,有些语言中也叫流操作,是指数据通信的通道。

1、i/o包

io包中提供I/O原始操作的一系列接口。它主要包装了一些已有的实现,如 os 包中的那些,并将这些抽象成为实用性的功能和一些其他相关的接口。

在io包中最重要的是两个接口:Reader和Writer接口

  • Reader接口的定义,Read()方法用于读取数据。
type Reader interface {
        Read(p []byte) (n int, err error)
}

示例代码:

package main

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

func main() {
	filePath := "aa.txt"
	// 1.打开文件
	f, err := os.Open(filePath)
	if err != nil {
		fmt.Println("err:", err)
		return
	}
	// 2.延迟关闭文件
	defer f.Close()
	// 3.读取文件
	// 3.1 创建一个byte类型的切片,用于存储读到的数据
	temp := make([]byte, 4)
	// 3.2 循环读取文件内容
	for {
		n, err := f.Read(temp)
		if n == 0 || err == io.EOF {
			fmt.Println("文件读取完了")
			return
		}
		if err != nil && err != io.EOF {
			fmt.Println("err: ", err)
			return
		}
		fmt.Printf("读取到了%d个字节.
", n)
		fmt.Println(string(temp[:n]))
	}
}

运行结果

读取到了4个字节.
hell
读取到了4个字节.
o wo
读取到了3个字节.
rld
文件读取完了
  • Writer接口的定义,Write()方法用于写出数据。
type Writer interface {
        Write(p []byte) (n int, err error)
}

示例代码

package main

import (
	"fmt"
	"os"
)

func main() {
	filePath := "ab.txt"
	// 1.打开文件(以可读可写的方式)
	f, err := os.OpenFile(filePath, os.O_CREATE|os.O_RDWR, os.ModePerm)
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
	// 2.延迟关闭文件
	defer f.Close()
	// 3.写入内容文件
	n, err := f.Write([]byte("hello world"))
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
	fmt.Printf("写入%d个字节
", n)
	n, err = f.WriteString("你好 世界")
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
	fmt.Printf("写入%d个字节
", n)
}

运行结果

写入11个字节
写入13个字节

三、bufio包

Go语言在io操作中,还提供了一个bufio的包,使用这个包可以大幅提高文件读写的效率。

1、bufio包原理

bufio 是通过缓冲来提高效率。

io操作本身的效率并不低,低的是频繁的访问本地磁盘的文件。所以bufio就提供了缓冲区(分配一块内存),读和写都先在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率。

bufio 封装了io.Reader或io.Writer接口对象,并创建另一个也实现了该接口的对象。

io.Reader或io.Writer 接口实现read() 和 write() 方法,对于实现这个接口的对象都是可以使用这两个方法的。

Reader对象

bufio.Reader 是bufio中对io.Reader 的封装

// Reader implements buffering for an io.Reader object.
type Reader struct {
	buf          []byte
	rd           io.Reader // reader provided by the client
	r, w         int       // buf read and write positions
	err          error
	lastByte     int // last byte read for UnreadByte; -1 means invalid
	lastRuneSize int // size of last rune read for UnreadRune; -1 means invalid
}

bufio.Read(p []byte) 相当于读取大小len(p)的内容,思路如下:

  1. 当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区
  2. 当缓存区没有内容的时候且len(p)>len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可
  3. 当缓存区没有内容的时候且len(p)<len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容)
  4. 以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区(此时和情况1一样)

Writer对象

bufio.Writer 是bufio中对io.Writer 的封装

// Writer implements buffering for an io.Writer object.
// If an error occurs writing to a Writer, no more data will be
// accepted and all subsequent writes, and Flush, will return the error.
// After all data has been written, the client should call the
// Flush method to guarantee all data has been forwarded to
// the underlying io.Writer.
type Writer struct {
	err error
	buf []byte
	n   int
	wr  io.Writer
}

bufio.Write(p []byte) 的思路如下

  1. 判断buf中可用容量是否可以放下 p
  2. 如果能放下,直接把p拼接到buf后面,即把内容放到缓冲区
  3. 如果缓冲区的可用容量不足以放下,且此时缓冲区是空的,直接把p写入文件即可
  4. 如果缓冲区的可用容量不足以放下,且此时缓冲区有内容,则用p把缓冲区填满,把缓冲区所有内容写入文件,并清空缓冲区
  5. 判断p的剩余内容大小能否放到缓冲区,如果能放下(此时和步骤1情况一样)则把内容放到缓冲区
  6. 如果p的剩余内容依旧大于缓冲区,(注意此时缓冲区是空的,情况和步骤3一样)则把p的剩余内容直接写入文件。
2、bufio包

bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。

  • bufie.Reader:
// NewReaderSize 将 rd 封装成一个带缓存的 bufio.Reader 对象,
// 缓存大小由 size 指定(如果小于 16 则会被设置为 16)。
// 如果 rd 的基类型就是有足够缓存的 bufio.Reader 类型,则直接将
// rd 转换为基类型返回。
func NewReaderSize(rd io.Reader, size int) *Reader{}

// NewReader 相当于 NewReaderSize(rd, 4096)
func NewReader(rd io.Reader) *Reader {
	return NewReaderSize(rd, defaultBufSize)
}

// Read 从 b 中读出数据到 p 中,返回读出的字节数和遇到的错误。
func (b *Reader) Read(p []byte) (n int, err error) {}

// ReadByte读取并返回一个字节.
// 如果没有可用的字节,则返回一个错误.
func (b *Reader) ReadByte() (byte, error) {}

// ReadBytes,读取直到第一次出现分隔符,返回读取到的字节切片。
func (b *Reader) ReadBytes(delim byte) ([]byte, error) {}

// ReadString 功能同 ReadBytes,只不过返回的是字符串。
func (b *Reader) ReadString(delim byte) (string, error) {}

示例代码:

package main

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

func main() {
	filePath := "aa.txt"
	// 1.打开文件
	f, err := os.Open(filePath)
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
	// 2.延迟关闭文件
	defer f.Close()
	// 3.读取内容
	r := bufio.NewReader(f)
	for {
		s, err := r.ReadString('
')
		if err == io.EOF {
			// 判断s中是否有内容需要输出。
			if len(s) != 0 {
				fmt.Println(s)
			}
			fmt.Println("读取完毕")
			break
		}
		if err != nil && err != io.EOF {
			fmt.Println("err: ", err)
			return
		}
		fmt.Println(s)
	}
}

输出结果

两岸舟船各背驰,

波痕交涉亦难为。

只余鸥鹭无拘管,

北去南来自在飞。
读取完毕
  • bufio.Writer:
// NewWriterSize 将 wr 封装成一个带缓存的 bufio.Writer 对象,
// 缓存大小由 size 指定(如果小于 4096 则会被设置为 4096)。
// 如果 wr 的基类型就是有足够缓存的 bufio.Writer 类型,则直接将
// wr 转换为基类型返回。
func NewWriterSize(wr io.Writer, size int) *Writer{}

// NewWriter 相当于 NewWriterSize(wr, 4096)
func NewWriter(wr io.Writer) *Writer{}

// WriteString 功能同 Write,只不过写入的是字符串
func (b *Writer) WriteString(s string) (int, error){}

// Flush 将缓存中的数据提交到底层的 io.Writer 中
func (b *Writer) Flush() error{}

示例代码:

package main

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

func main() {
	filePath := "ab.txt"
	// 1.打开文件
	f, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
	// 2.延迟关闭文件
	defer f.Close()
	// 3.写入内容
	w := bufio.NewWriter(f)
	n, err := w.WriteString("hello world")
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
	fmt.Printf("写入了%d个字节
", n)
}

运行结果

写入了11个字节

四、ioutil包

除了io包可以读写数据,Go语言中还提供了一个辅助的工具包就是ioutil,里面的方法虽然不多,但是都还蛮好用的。

该包的介绍只有一句话:Package ioutil implements some I/O utility functions。

ioutil包的方法:

// ReadAll 读取 r 中的所有数据,返回读取的数据和遇到的错误。
// 如果读取成功,则 err 返回 nil,而不是 EOF,因为 ReadAll 定义为读取
// 所有数据,所以不会把 EOF 当做错误处理。
func ReadAll(r io.Reader) ([]byte, error){}

// ReadFile 读取文件中的所有数据,返回读取的数据和遇到的错误。
// 如果读取成功,则 err 返回 nil,而不是 EOF
func ReadFile(filename string) ([]byte, error){}

// WriteFile 向文件中写入数据,写入前会清空文件。
// 如果文件不存在,则会以指定的权限创建该文件。
// 返回遇到的错误。
func WriteFile(filename string, data []byte, perm os.FileMode) error{}

// ReadDir 读取指定目录中的所有目录和文件(不包括子目录)。
// 返回读取到的文件信息列表和遇到的错误,列表是经过排序的。
func ReadDir(dirname string) ([]os.FileInfo, error){}

// TempFile 在 dir 目录中创建一个以 prefix 为前缀的临时文件,并将其以读
// 写模式打开。返回创建的文件对象和遇到的错误。
// 如果 dir 为空,则在默认的临时目录中创建文件(参见 os.TempDir),多次
// 调用会创建不同的临时文件,调用者可以通过 f.Name() 获取文件的完整路径。
// 调用本函数所创建的临时文件,应该由调用者自己删除。
func TempFile(dir, prefix string) (f *os.File, err error){}

// TempDir 功能同 TempFile,只不过创建的是目录,返回目录的完整路径。
func TempDir(dir, prefix string) (name string, err error){}

示例代码:

package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"strings"
)

func main() {
	filePath := "aa.txt"
	// 1.ReadFile
	data, err := ioutil.ReadFile(filePath)
	handleError(err)
	fmt.Println(string(data))
	// 2.ReadAll
	s := "相濡以沫,不如相忘于江湖"
	r := strings.NewReader(s)
	data, err = ioutil.ReadAll(r)
	handleError(err)
	fmt.Println(string(data))
	// 3.ReadDir
	dirName := "E:/golang/go_project"
	fileInfoData, err := ioutil.ReadDir(dirName)
	handleError(err)
	for k, v := range fileInfoData {
		fmt.Printf("第%d个文件名称是%s, 是目录吗?%v
", k, v.Name(), v.IsDir())
	}
	// 4.WriteFile
	filePath = "cc.txt"
	err = ioutil.WriteFile(filePath, []byte("相见亦难别亦难,东风无力百花残"), os.ModePerm)
	handleError(err)
	// 5.TempFile
	f, err := ioutil.TempFile(dirName, "test")
	handleError(err)
	fmt.Println(f)
	n, err := f.WriteString("这是一种的临时测试文件")
	handleError(err)
	fmt.Println("写入字符个数:", n)
	// 6.TempDir
	name, err := ioutil.TempDir(dirName, "test")
	handleError(err)
	fmt.Println(name)
}

func handleError(err error) {
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
}

运行结果

两岸舟船各背驰,
波痕交涉亦难为。
只余鸥鹭无拘管,
北去南来自在飞。
相濡以沫,不如相忘于江湖
第0个文件名称是aa.txt, 是目录吗?false
第1个文件名称是ab.txt, 是目录吗?false
第2个文件名称是bb.txt, 是目录吗?false
第3个文件名称是cc.txt, 是目录吗?false
第4个文件名称是go.mod, 是目录吗?false
第5个文件名称是main, 是目录吗?true
第6个文件名称是main.go, 是目录吗?false
第7个文件名称是model, 是目录吗?true
第8个文件名称是test523723675, 是目录吗?false
&{0xc00007b180}
写入字符个数: 33
E:golanggo_project	est800637258

五、文件复制

  • 第一种:io包下Read()和Write()直接读写:我们自己创建读取数据的切片的大小,直接影响性能。
  • 第二种:ioutil包
  • 第三种:io包下Copy()方法:
package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
)

func main() {
	srcFile := "aa.txt"
	distFile := "bb.txt"
	// n, err := copyFile1(srcFile, distFile)
	// n, err := copyFile2(srcFile, distFile)
	n, err := copyFile3(srcFile, distFile)
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
	fmt.Println("拷贝的字节数:", n)
}

// copyFile1 io包的Read和Write方法
func copyFile1(srcFile, distFile string) (n int, err error) {
	srcfile, err := os.Open(srcFile)
	if err != nil {
		return 0, err
	}
	distfile, err := os.OpenFile(distFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
	if err != nil {
		return 0, err
	}
	defer srcfile.Close()
	defer distfile.Close()

	total := 0
	temp := make([]byte, 1024)
	for {
		n, err = srcfile.Read(temp)
		if err == io.EOF || n == 0 {
			fmt.Println("拷贝完毕")
			break
		} else if err != nil && err != io.EOF {
			return total, err
		}
		total += n
		_, err = distfile.Write(temp[:n])
	}
	return total, nil
}

// copyFile2 ioutil包的方法
func copyFile2(srcFile, distFile string) (n int, err error) {
	data, err := ioutil.ReadFile(srcFile)
	if err != nil {
		return 0, err
	}
	err = ioutil.WriteFile(distFile, data, os.ModePerm)
	if err != nil {
		return 0, err
	}
	return len(data), nil
}

// copyFile3 io包的copy方法
func copyFile3(srcFile, distFile string) (n int, err error) {
	srcfile, err := os.Open(srcFile)
	if err != nil {
		return 0, err
	}
	distfile, err := os.OpenFile(distFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, os.ModePerm)
	if err != nil {
		return 0, err
	}
	defer srcfile.Close()
	defer distfile.Close()
	total, err := io.Copy(distfile, srcfile)
	return int(total), err
}

六、断点续传

1、Seeker接口

Seeker是包装基本Seek方法的接口。

type Seeker interface {
        Seek(offset int64, whence int) (int64, error)
}

seek(offset, whence),设置指针光标的位置,随机读写文件:

第一个参数:偏移量
第二个参数:如何设置			

			0:seekStart表示相对于文件开始,
			1:seekCurrent表示相对于当前偏移量,
			2:seek end表示相对于结束。
const (
	SEEK_SET int = 0 // seek relative to the origin of the file
	SEEK_CUR int = 1 // seek relative to the current offset
	SEEK_END int = 2 // seek relative to the end
)

示例代码:

package main

import (
	"fmt"
	"os"
)

func main() {
	filePath := "aa.txt" // ABCDEFGH
	temp := make([]byte, 1)
	f, _ := os.Open(filePath)
	f.Seek(2, os.SEEK_SET)
	f.Read(temp)
	fmt.Println(string(temp[:])) // C
}
2、断点续传实现

首先思考几个问题
Q1:如果你要传的文件比较大,那么是否有方法可以缩短耗时?
Q2:如果在文件传递过程中,程序因各种原因被迫中断了,那么下次再重启时,文件是否还需要重头开始?
Q3:传递文件的时候,支持暂停和恢复么?即使这两个操作分布在程序进程被杀前后。

通过断点续传可以实现,不同的语言有不同的实现方式。我们看看Go语言中,通过Seek()方法如何实现:

先说一下思路:想实现断点续传,主要就是记住上一次已经传递了多少数据,那我们可以创建一个临时文件,记录已经传递的数据量,当恢复传递的时候,先从临时文件中读取上次已经传递的数据量,然后通过Seek()方法,设置到该读和该写的位置,再继续传递数据。

示例代码:

package main

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

func main() {
	srcFile := "06.jpg"
	distFile := "美女.jpg"
	tempFile := "temp.txt"
	tempfile, err := os.OpenFile(tempFile, os.O_CREATE|os.O_RDWR, os.ModePerm)
	handleError(err)
	srcfile, err := os.Open(srcFile)
	handleError(err)
	distfile, err := os.OpenFile(distFile, os.O_CREATE|os.O_APPEND|os.O_RDWR, os.ModePerm)
	handleError(err)
	defer srcfile.Close()
	defer distfile.Close()
	// defer tempfile.Close()

	// 1.读取临时文件夹的内容,获得已拷贝的字节数
	_, err = tempfile.Seek(0, io.SeekStart)
	handleError(err)
	temp := make([]byte, 1024*4)
	n1, err := tempfile.Read(temp)
	handleError(err)
	count, err := strconv.Atoi(string(temp[:n1]))
	handleError(err)
	// 2.设置读写偏移量
	_, err = srcfile.Seek(int64(count), os.SEEK_SET)
	handleError(err)
	_, err = distfile.Seek(int64(count), os.SEEK_SET)
	handleError(err)
	data := make([]byte, 1024*4)
	total := int(count)
	// 3.循环读写数据
	for {
		n2, err := srcfile.Read(data)
		if n2 == 0 || err == io.EOF {
			fmt.Println("文件读完了")
			tempfile.Close()
			os.Remove(tempFile)
			break
		}
		// 写入数据
		n3, err := distfile.Write(data[:n2])
		// 将写入量写入临时文件中
		total += n3
		_, err = tempfile.Seek(0, os.SEEK_SET)
		handleError(err)
		_, err = tempfile.WriteString(strconv.Itoa(total))
		handleError(err)
		// 模拟程序中断
		if total > 2000 {
			panic("崩了")
		}
	}

}

func handleError(err error) {
	if err != nil {
		fmt.Println("err: ", err)
		return
	}
}

运行结果

运行5次程序实现图片的拷贝
原文地址:https://www.cnblogs.com/everydawn/p/14001541.html