Shredder & Gluer
Posted on
有时候我们需要向服务器上传一个很大的文件,但是无奈网络不稳定,再加上使用的软件不支持断点续传,经常会出现文件上传到一半出错,无奈只能从头再来,可以说是非常烦恼了。还有的时候服务器明确规定了单次上传文件的大小,手头又没有啥好的软件可以帮助我们进行文件的分割和合并操作,只好自己写一个。
要是一个人被砍了头,手,脚,再把这几个肢体合并起来大概率这人也是废了(在医学水平没有极大进步的情况下),但是对于一个文件来说则不同,无论什么格式的文件,其实本质上都是一大堆字节,如果我们在固定的地方进行切割,最后再在被切割的地方合并的话,这个文件还是可以被正常使用的。
Get Basic Info
在对文件切割之前,我们需要获得文件的基本信息,即文件的大小或者说是字节数,还需要决定切割后每个单位的大小,知道了这两个参数之后,就可以算出所有分割块的数量了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| f, err := os.Open("some_very_big_file") if err != nil { panic(err) } defer f.Close() fileInfo, err := f.Stat() if err != nil { panic(err) } fileSize := fileInfo.Size() partSize := 1 << 28 concurrency := int(fileSize / int64(partSize)) if remainder := fileSize % int64(partSize); remainder != 0 { concurrency++ }
|
Shredder
利用 io.NewSectionReader
可以读取文件特定偏移的数据,然后利用buffo.NewReader
来缓冲读数据,利用 bufio.NewWriter
来缓冲写数据提高效率,最后利用 io.Copy(bw, br)
将文件特定部分复制到新文件中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| var wg sync.WaitGroup wg.Add(concurrency) for i := 0; i < concurrency; i++ { go func(i int) { defer wg.Done() sr := io.NewSectionReader(f, int64(i * partSize), int64(partSize)) br := bufio.NewReader(sr) partFile, err := os.Create("partFile_" + strconv.Itoa(i)) if err != nil { panic(err) } bw := bufio.NewWriter(partFile) io.Copy(bw, br) }(i) } wg.Wait()
|
Gluer
合并与分割互为逆操作,因为我们对上面分割后的文件采取了顺序命名操作,所以可以根据编号的顺序,利用 Seek
方法指定合并文件的写偏移地址,通过 bufio.NewReader
对分割文件进行缓冲读操作,同理利用 bufio.NewWriter
对合并文件进行缓冲写操作,通过 io.Copy(bw, br)
将分割文件放入最终合并文件的正确位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| matches, err := filepath.Glob("partFile_*") if err != nil { panic(err) }
mergedFile, err := os.Create("mergedFile") if err != nil { panic(err) } for _, v := range matches { ss := strings.Split(v, "_") i, err := strconv.Atoi(ss[1]) if err != nil { panic(err) } mergedFile.Seek(int64(i * partSize), os.SEEK_SET) f, err := os.Open(v) if err != nil { panic(err) } br := bufio.NewReader(f) bw := bufio.NewWriter(mergedFile) io.Copy(bw, br) }
|