一、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", 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)
n, err = f.WriteString("你好 世界")
if err != nil {
fmt.Println("err: ", err)
return
}
fmt.Printf("写入%d个字节\n", 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)的内容,思路如下:
- 当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区
- 当缓存区没有内容的时候且len(p)>len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可
- 当缓存区没有内容的时候且len(p)<len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容)
- 以后再次读取时缓存区有内容,将缓存区内容全部填入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) 的思路如下
- 判断buf中可用容量是否可以放下 p
- 如果能放下,直接把p拼接到buf后面,即把内容放到缓冲区
- 如果缓冲区的可用容量不足以放下,且此时缓冲区是空的,直接把p写入文件即可
- 如果缓冲区的可用容量不足以放下,且此时缓冲区有内容,则用p把缓冲区填满,把缓冲区所有内容写入文件,并清空缓冲区
- 判断p的剩余内容大小能否放到缓冲区,如果能放下(此时和步骤1情况一样)则把内容放到缓冲区
- 如果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('\n')
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", 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\n", 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:\golang\go_project\test800637258
五、文件复制
- 第一种: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次程序实现图片的拷贝
网友评论