解凍する先にあるファイルを格納しないことで圧縮を高速・縮小できるツールを作った

2017年7月3日

どうも、ラルフです。

今回は表題の通り、あるツールを作ったお話です。

総計4GB、ファイル数3000くらいのデータを近くにある別PCに移そうと思ったとき、普通であればUSBメモリや外付けHDDなどを使用してファイルをコピーすると思います。

今回は同一ネットワーク・インターネットに必ずしもつながっていない前提なので、共有フォルダ経由は考えません。

1台のPCから別のPCにだけであればまだいいのですが、10台近いPCから集約させようとすると、ファイルのコピーに非常に時間がかかってしまいます。

フォルダのままやろうとするとファイル数の多さで速度が発揮できず、ZIPにまとめようとするとZIPの圧縮解凍でそこそこいい時間がかかります。

そこでどうにかしようとツールを作ることにしました。

今回コピーしたいデータはアプリのプロジェクトデータで、ファイルの半数以上がアセットデータなどの書き換えが低い頻度でしか起こらないファイルでした。

解凍先で既にあるファイルは圧縮時にバイナリを含めず、ファイルがあったという情報のみを含めることで、変化しないファイルに関してはファイルサイズをごく僅かにできるのではないかと考えました。

そこで色々いちゃこらして6時間程度で作ったのがDiffPackerというツール群です。

https://github.com/r-ralph/DiffPacker

解凍を行うPC側で事前に用意しているファイルのリストを作成し、それを圧縮時に参照することでパッケージングから除外することで高速化とファイルサイズの縮小が期待されます。

事前に用意しているファイル(共有ファイル)のデータは、SHA-256のハッシュ値のリストを用意することで実現しています。

GithubのReleaseから動くバイナリを取得できます。


言語はKotlin、内部はJavaのSwingを使用した簡易GUIと、実際に処理を行う部分の2パーツからできています。

起動時のモードは2つあり、圧縮を行うモードと解凍・共有ファイルの登録を行えるモードです。

区別はjarファイルと同じディレクトリにEXTRACTという名前のファイルが存在しているかどうかという原始的な方法です。

存在すれば解凍・共有ファイルの登録、なければ圧縮モードです。

解凍モードの画面の登録ボタンを押し、表示された画面に共有ファイルとして登録したいファイルをドラッグ・アンド・ドロップすることで記録されます。

フォルダをドラッグ・アンド・ドロップした場合はサブディレクトリの中も含めてすべてのファイルが登録されます。

登録を行うと、jarと同じフォルダにshared.dpssharedフォルダが作成されます。

shared.dpsには登録されたSHA-256のリストが、sharedフォルダには登録されたファイルがSHA-256の名前に変わって保管されます。

圧縮時には、jarと同じフォルダに上で生成されたshared.dpsを置いておくことで、同じSHA-256のデータは存在だけを記録するようになります。

圧縮モードの画面にフォルダをドラッグ・アンド・ドロップすることで処理が行われ、dpfファイルが作られます。

このdpfファイルをshared.dpssharedフォルダが存在するPCで解凍モードの画面にドラッグ・アンド・ドロップすることで、元通りのフォルダに解凍されます。


今回内部でのデータの(デ)シリアライズにMessagePackを使用しました。

Javaのシリアライズよりは他言語での読み取りがしやすいからとの判断です。

圧縮時のデータストアの形式もシンプルで、1つのファイルにつき

String Array: ファイルが格納されているパスを、1フォルダごとに区切ったStringの配列
Boolean: 共有ファイルにあるものかどうか
|-true -:String: SHA-256の文字列
|-false-:Binary: 実際のファイルのバイナリ

のデータが繰り返されているだけになります。

解凍時は、Booleanによってsharedフォルダから共有ファイルをコピーするか、Binaryを読み出すかを切り分けています。

MessagePackは軽量で高速なので、ここも速度が早くなる1つの要因かもしれません。


このアルゴリズムの性能は、共有できるファイルがどれくらいあるかによって大きく変動しますが、自分のところでは4GBあったものが500MBまで圧縮でき、ファイルの転送にもあまり時間がかからなくなりました。

用途が非常に限定的ですが、作ってそのままなのももったいないので、OSSとして公開しちゃえという感じです笑

では。