• 【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次程序实现图片的拷贝
    
  • 相关阅读:
    第三天 moyax
    mkfs.ext3 option
    write file to stroage trigger kernel warning
    download fomat install rootfs script
    custom usb-seriel udev relus for compatible usb-seriel devices using kermit
    Wifi Troughput Test using iperf
    learning uboot switch to standby system using button
    learning uboot support web http function in qca4531 cpu
    learngin uboot design parameter recovery mechanism
    learning uboot auto switch to stanbdy system in qca4531 cpu
  • 原文地址:https://www.cnblogs.com/everydawn/p/14001541.html
Copyright © 2020-2023  润新知