在成功将 mac 由 10.10 升级到 10.12 后,我发现除了新增一个并不怎么好用的 Siri 外,原来支持 NTFS 硬盘的驱动居然也成功失效了。我那块 500 GB 的东芝硬盘,虽不至于成砖,但一块只能读不能写的硬盘,实在让人欲哭无泪。巧的是,最近需要频繁地将一些数据文件( GB 级别)拷贝到其他电脑,而手头又仅剩一些小容量 U 盘。于是,我突然萌生了写一个文件分割器的想法,将大的压缩文件分片后,再用这些小 U 盘搬到到其他电脑上去。
有人会问,这样的软件明明网上有的是,何必自己写呢?没错,我就是这么无聊的人。
需求分析
其实也不用怎么分析,功能非常简单。我需要两个函数(分别用于分割和合成),分割函数的输入是:一个文件、分片数量,输出是:分片文件、一个配置文件(记录分片文件的顺序);合成函数的输入是:配置文件,输出是:完整的数据文件(根据配置,程序会寻找分片文件用于合成)。
基于此,其实要实现的是两个这样的函数:
1 | // 分割文件的函数,第三个参数指定配置文件名称 |
配置文件的格式,我使用了 json
(其实用简单的字符串记录一下也是可以的)。
另外,为了方便使用,最好再用一个类将两个方法封装一下。
难点分析
这么小的程序会有难点?!其实还是有一丢丢,就是切割文件的时候,由于文件可能太大,因此不能一口气读入内存中,所以这里采用分块的方法,读一小块写一小块。当然啦,速度方面的优化,这里先不考虑了。
程序实现
首先,我们把所有功能放在一个类FileSegment
里面实现,对外只暴露上面的两个函数接口。
segment
上面的难度分析已经指出,我们需要分块读取文件,然后分块写入。
首先需要定义分块大小:const int FileSegment::kBlockSize = 1024 * 1024;
,这里设定一个块大小为1 MB。
我们再定义两个辅助函数,用来分块读文件、写文件:
1 | // 从input流中读取size(默认大小kBlockSize)大小的字节到data里面 |
这两个函数因为要经常用到,所以把它们作为内联函数使用。
综合这两个辅助函数,我们定义另一个辅助函数,用于从输入文件中将大批量的数据写入到输出文件中:
1 | // 将input流中读取input_size大小的字节内容,写入到output流中 |
有了上面的辅助函数后,我们可以聚焦于segment()
函数的核心代码部分了。
我们只需要利用copy_file()
函数,将源文件分片写入到几个分片文件中即可。
1 | // 分片文件名 |
另外,我们需要将分片文件的文件名和分割顺序等信息写入配置文件中,这里使用json
格式,并用这个第三方库来操纵json
对象。
1 | const string FileSegment::kSegmentFileNum = "SegmentNum"; |
下面给出segment()
函数的完整代码:
1 | void FileSegment::segment(string file_name, int segment_num, string json_file) { |
merge
有了前面的辅助函数后,merge()
函数的实现基本是依葫芦画瓢。首先需要从配置文件中读取出json
对象,根据配置信息去合成文件:
1 | json j; |
之后,根据分片文件来合成大文件:
1 | // 合并文件 |
接下来照例给出merge()
函数完整实现:
1 | void FileSegment::merge(string json_file) { |
<br>
main
在main()
中,直接实例化FileSegment
类,通过segment()
和merge()
函数分割或者合成文件。
1 | int main(int argc, char const *argv[]) { |
另外,为了方便使用,我特意写了一个解析命令的类InputParser
,然后,我们可以按照如下方式使用该程序:
分割文件
./main -s data.zip 4 config.json
合成文件
./main -m config.json
完整工程代码,请看:https://github.com/Jermmy/file_segmentation
测试结果
在我的 mac (双核,2.7 GHz Intel Core i5) 上,将一个 7.35 G 的 zip 文件分割为 10 片,所用时间为 37.7 s。
同样的机器,将上面的 10 片文件合成原来的大文件,所用时间为 31.8 s。