Shredder & Gluer

有时候我们需要向服务器上传一个很大的文件,但是无奈网络不稳定,再加上使用的软件不支持断点续传,经常会出现文件上传到一半出错,无奈只能从头再来,可以说是非常烦恼了。还有的时候服务器明确规定了单次上传文件的大小,手头又没有啥好的软件可以帮助我们进行文件的分割和合并操作,只好自己写一个。

要是一个人被砍了头,手,脚,再把这几个肢体合并起来大概率这人也是废了(在医学水平没有极大进步的情况下),但是对于一个文件来说则不同,无论什么格式的文件,其实本质上都是一大堆字节,如果我们在固定的地方进行切割,最后再在被切割的地方合并的话,这个文件还是可以被正常使用的。

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)
}
Pieces of Valuable Programming Knowledges