断点续传 Seeker接口 Seeker是包装基本Seek方法的接口。
1 2 3 type Seeker interface { Seek(offset int64 , whence int ) (int64 , error ) }
seek(offset,whence),设置指针光标的位置,随机读写文件:
第一个参数:偏移量 第二个参数:如何设置
0:seekStart表示相对于文件开始, 1:seekCurrent表示相对于当前偏移量, 2:seek end表示相对于结束。
1.txt内容
1 ABCDEFababHelloWorldHelloWorld
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport ( "fmt" "io" "log" "os" ) func main () { fileName := "main/1.txt" file, err := os.OpenFile(fileName, os.O_RDWR, os.ModePerm) if err != nil { log.Fatal(err) } defer file.Close() bs := []byte {0 } file.Read(bs) fmt.Println(string (bs)) file.Seek(4 , io.SeekStart) file.Read(bs) fmt.Println(string (bs)) file.Seek(2 , 0 ) file.Read(bs) fmt.Println(string (bs)) file.Seek(3 , io.SeekCurrent) file.Read(bs) fmt.Println(string (bs)) file.Seek(0 , io.SeekEnd) file.WriteString("ABC" ) }
运行结果:
断点续传 首先思考几个问题 Q1:如果你要传的文件,比较大,那么是否有方法可以缩短耗时? Q2:如果在文件传递过程中,程序因各种原因被迫中断了,那么下次再重启时,文件是否还需要重头开始? Q3:传递文件的时候,支持暂停和恢复么?即使这两个操作分布在程序进程被杀前后。
通过断点续传可以实现,不同的语言有不同的实现方式。我们看看Go语言中,通过Seek()方法如何实现:
先说一下思路:想实现断点续传,主要就是记住上一次已经传递了多少数据,那我们可以创建一个临时文件,记录已经传递的数据量,当恢复传递的时候,先从临时文件中读取上次已经传递的数据量,然后通过Seek()方法,设置到该读和该写的位置,再继续传递数据。
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 package mainimport ( "fmt" "io" "log" "os" "strconv" "strings" ) func main () { srcFile := "main/1.txt" destFile := srcFile[strings.LastIndex(srcFile, "/" )+1 :] tempFile := destFile + "temp.txt" file1, err := os.Open(srcFile) file2, err := os.OpenFile(destFile, os.O_CREATE|os.O_WRONLY, os.ModePerm) file3, err := os.OpenFile(tempFile, os.O_CREATE|os.O_RDWR, os.ModePerm) defer file1.Close() defer file2.Close() file3.Seek(0 , io.SeekStart) bs := make ([]byte , 100 , 100 ) n1, err := file3.Read(bs) countStr := string (bs[:n1]) count, err := strconv.ParseInt(countStr, 10 , 64 ) file1.Seek(count, io.SeekStart) file2.Seek(count, io.SeekStart) data := make ([]byte , 1024 , 1024 ) n2 := -1 n3 := -1 total := int (count) for { n2, err = file1.Read(data) if err == io.EOF || n2 == 0 { fmt.Println("文件复制完毕。。" ) file3.Close() os.Remove(tempFile) break } n3, err = file2.Write(data[:n2]) total += n3 file3.Seek(0 , io.SeekStart) file3.WriteString(strconv.Itoa(total)) fmt.Printf("total:%d\n" , total) } } func HandleErr (err error ) { if err != nil { log.Fatal(err) } }
bufio包 bufio包原理 bufio 是通过缓冲来提高效率。
io操作本身的效率并不低,低的是频繁的访问本地磁盘的文件。所以bufio就提供了缓冲区(分配一块内存),读和写都先在缓冲区中,最后再读写文件,来降低访问本地磁盘的次数,从而提高效率。
简单的说就是,把文件读取进缓冲(内存)之后再读取的时候就可以避免文件系统的io 从而提高速度。同理,在进行写操作时,先把文件写入缓冲(内存),然后由缓冲写入文件系统。看完以上解释有人可能会表示困惑了,直接把 内容->文件 和 内容->缓冲->文件相比, 缓冲区好像没有起到作用嘛。其实缓冲区的设计是为了存储多次的写入,最后一口气把缓冲区内容写入文件。
bufio 封装了io.Reader或io.Writer接口对象,并创建另一个也实现了该接口的对象。
io.Reader或io.Writer 接口实现read() 和 write() 方法,对于实现这个接口的对象都是可以使用这两个方法的。
Reader对象 bufio.Reader 是bufio中对io.Reader 的封装
1 2 3 4 5 6 7 8 9 type Reader struct { buf []byte rd io.Reader r, w int err error lastByte int lastRuneSize int }
bufio.Read(p []byte) 相当于读取大小len(p)的内容,思路如下:
当缓存区有内容的时,将缓存区内容全部填入p并清空缓存区
当缓存区没有内容的时候且len(p)>len(buf),即要读取的内容比缓存区还要大,直接去文件读取即可
当缓存区没有内容的时候且len(p)<len(buf),即要读取的内容比缓存区小,缓存区从文件读取内容充满缓存区,并将p填满(此时缓存区有剩余内容)
以后再次读取时缓存区有内容,将缓存区内容全部填入p并清空缓存区(此时和情况1一样)
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 package mainimport ( "bufio" "fmt" "os" ) func main () { fileName := "main/1.txt" file, err := os.Open(fileName) if err != nil { fmt.Println(err) return } defer func (file *os.File) { err := file.Close() if err != nil { } }(file) b2 := bufio.NewReader(os.Stdin) s2, _ := b2.ReadString('\n' ) fmt.Println(s2) }
Writer对象 bufio.Writer 是bufio中对io.Writer 的封装
1 2 3 4 5 6 7 8 9 10 11 12 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的剩余内容直接写入文件
示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 package mainimport ( "bufio" "fmt" "os" ) func main () { fileName := "main/2.txt" file, err := os.OpenFile(fileName, os.O_CREATE|os.O_WRONLY, os.ModePerm) if err != nil { fmt.Println(err) return } defer func (file *os.File) { err := file.Close() if err != nil { } }(file) w1 := bufio.NewWriter(file) for i := 1 ; i <= 1000 ; i++ { _, err2 := w1.WriteString(fmt.Sprintf("%d:hello" , i)) if err2 != nil { return } } err = w1.Flush() if err != nil { return } }
bufio包 bufio包实现了有缓冲的I/O。它包装一个io.Reader或io.Writer接口对象,创建另一个也实现了该接口,且同时还提供了缓冲和一些文本I/O的帮助函数的对象。
bufio.Reader bufio.Reader 实现了如下接口: io.Reader io.WriterTo io.ByteScanner io.RuneScanner
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 func NewReaderSize (rd io.Reader, size int ) *Readerfunc NewReader (rd io.Reader) *Readerfunc (b *Reader) Peek(n int ) ([]byte , error )func (b *Reader) Read(p []byte ) (n int , err error )func (b *Reader) Buffered() int func (b *Reader) ReadBytes(delim byte ) (line []byte , err error )func (b *Reader) ReadString(delim byte ) (line string , err error )
bufio.Writer bufio.Writer 实现了如下接口: io.Writer io.ReaderFrom io.ByteWriter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func NewWriterSize (wr io.Writer, size int ) *Writerfunc NewWriter (wr io.Writer) *Writerfunc (b *Writer) WriteString(s string ) (int , error )func (b *Writer) WriteRune(r rune ) (size int , err error )func (b *Writer) Flush() error func (b *Writer) Available() int func (b *Writer) Buffered() int func (b *Writer) Reset(w io.Writer)