自作OSM(OpenStreetMap)地図では、1万を超えるような多数のOSMバイナリレコードファイルから タイル地図画像データを作成(レンダリング)する。
現在はこれらのファイルを階層ディレクトリで管理しているが、 全体(370フォルダ、12,646ファイル、6.33GB[6,799,916,209B])を一つのZIPファイル(3.52GB)として 個々のファイル(エントリ)をZIPファイルから読みだす場合のパフォーマンスを調べる。
ここでは、全体としては1万を超えるような多数のファイルを一つのひとまとめにした ZIPファイルから 個々のファイルを読みだす。
メモリ使用量および読み込み時間が問題なければ、次のようにするのが一番簡単であろう。
File zipFile = new File("path/to/archive.zip");
try (ZipFile zip = new ZipFile(zipFile)) {
// 階層パスを指定する (スラッシュ '/' を使用)
ZipEntry entry = zip.getEntry("folder1/subfolder/target.txt");
if (entry != null) {
// 入力ストリームを取得して読み込む
// InputStream is = zip.getInputStream(entry);
// ...
}
} catch (Exception e) {
e.printStackTrace();
}
地図データでは階層パスは japan-high12/3458/1762.dat のようになる。
大きなファイル(22MB)と平均的なファイル(1.1MB)について、読み込み時間の比較を行った。
初回はディスクキャッシュ効果はないため、両者の差は小さい。 二回目以降は平均的にはZIPファイルの方が3倍程度時間がかかっている。
地図アプリでは読み込み時間よりもその後の処理に数倍の時間がかかる。 また、読み込んだデータはメモリに保存され、何度も使用されるため、 読み込み時間が全体の処理時間に占める比重はごく小さくなる。
また、地図アプリでは同じ datファイルを繰り返し読み込むことは稀であり、 大抵が一度読み込まれるだけである。したがって、キャッシュディスク効果は殆ど期待できない。 このため、ZIPファイルと個別ファイルの読み込み時間の差はさほど大きくないであろう。
ZIP 22.35MB 348ミリ秒 japan-high12/3637/1614.dat 個別 22.35MB 347ミリ秒 /storage/9C33-6BBD/Map/dat/japan-high12/3637/1614.dat ZIP 1.12MB 29ミリ秒 japan-high12/3600/1626.dat 個別 1.12MB 20ミリ秒 /storage/9C33-6BBD/Map/dat/japan-high12/3600/1626.dat ZIP 22.35MB 192ミリ秒 japan-high12/3637/1614.dat 個別 22.35MB 47ミリ秒 /storage/9C33-6BBD/Map/dat/japan-high12/3637/1614.dat ZIP 1.12MB 28ミリ秒 japan-high12/3600/1626.dat 個別 1.12MB 3ミリ秒 /storage/9C33-6BBD/Map/dat/japan-high12/3600/1626.dat ZIP 22.35MB 187ミリ秒 japan-high12/3637/1614.dat 個別 22.35MB 49ミリ秒 /storage/9C33-6BBD/Map/dat/japan-high12/3637/1614.dat ZIP 1.12MB 31ミリ秒 japan-high12/3600/1626.dat 個別 1.12MB 11ミリ秒 /storage/9C33-6BBD/Map/dat/japan-high12/3600/1626.dat
同様のテストを関東地方のOSMデータについて行った。 japan.zip は 3.52GB、kanto.zip は 710MB である。 /kanto-high12/3600/1626.dat は存在しないので代わりに /kanto-high12/3630/1618.dat を選んだ。
1回目の個別ファイルの読み込み時間が短いのは、サイズの小さいファイルを選びなおしたことから、 直近で大きいファイルを読み込んだことが影響していると思われる。 後日、テストをやり直してみよう。
ZIPファイルの大きさは個々のエントリの読み込み時間に殆ど影響がないようである。
ZIP 22.35MB 339ミリ秒 kanto-high12/3637/1614.dat 個別 22.35MB 155ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3637/1614.dat ZIP 1.21MB 35ミリ秒 kanto-high12/3630/1618.dat 個別 1.21MB 37ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3630/1618.dat ZIP 22.35MB 180ミリ秒 kanto-high12/3637/1614.dat 個別 22.35MB 47ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3637/1614.dat ZIP 1.21MB 18ミリ秒 kanto-high12/3630/1618.dat 個別 1.21MB 5ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3630/1618.dat ZIP 22.35MB 184ミリ秒 kanto-high12/3637/1614.dat 個別 22.35MB 46ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3637/1614.dat ZIP 1.21MB 17ミリ秒 kanto-high12/3630/1618.dat 個別 1.21MB 4ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3630/1618.dat
1日後にテストをやり直した。昨日の初回、個別ファイルの読み込み時間が短かったのはやはり ディスクキャッシュ効果であったと言えよう。
ZIPファイルから初回読み込みがこれまでより2割ほど速かった。 22MBの個別ファイルにディスクキャッシュ効果は見られないので、 710MB の kanto.zip にディスクキャッシュ効果が残っているとは考えにくい。 2回目、3回目の結果は昨日と殆ど同じである。
ZIP 22.35MB 266ミリ秒 kanto-high12/3637/1614.dat 個別 22.35MB 340ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3637/1614.dat ZIP 1.21MB 36ミリ秒 kanto-high12/3630/1618.dat 個別 1.21MB 35ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3630/1618.dat ZIP 22.35MB 185ミリ秒 kanto-high12/3637/1614.dat 個別 22.35MB 46ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3637/1614.dat ZIP 1.21MB 17ミリ秒 kanto-high12/3630/1618.dat 個別 1.21MB 4ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3630/1618.dat ZIP 22.35MB 191ミリ秒 kanto-high12/3637/1614.dat 個別 22.35MB 46ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3637/1614.dat ZIP 1.21MB 18ミリ秒 kanto-high12/3630/1618.dat 個別 1.21MB 4ミリ秒 /storage/9C33-6BBD/Map/dat/kanto-high12/3630/1618.dat
static void test() {
readFromZipFile(Tile.Source.japan, Tile.Range.high, 12, 3637, 1614);
readFromIndividualFile(Tile.Source.japan, Tile.Range.high, 12, 3637, 1614);
readFromZipFile(Tile.Source.japan, Tile.Range.high, 12, 3600, 1626);
readFromIndividualFile(Tile.Source.japan, Tile.Range.high, 12, 3600, 1626);
}
static short[] readFromZipFile(Tile.Source src, Tile.Range range, int zoom, int x, int y) {
long start = System.currentTimeMillis();
byte[] bytes = new byte[1024*1024*4];
String zipPath = Map.DIR + src.name() + ".zip";
File zipFile = new File(zipPath);
try (ZipFile zip = new ZipFile(zipFile)) {
String entryname = src.name() + "-" + range.name() + zoom + "/" + x + "/" + y + ".dat";
ZipEntry entry = zip.getEntry(entryname);
if (entry != null) {
int size = (int)entry.getSize(); // 単位:バイト
BufferedInputStream bis = new BufferedInputStream(zip.getInputStream(entry));
short[] vals = new short[size/2];
int read, ix = 0;
while ((read = bis.read(bytes, 0, bytes.length)) != -1) {
for (int n = 0; n < read; n += 2) {
vals[ix++] = (short) ((bytes[n] << 8) | bytes[n + 1] & 0xff);
}
}
System.out.printf("ZIP %.2fMB %dミリ秒 %s\n", size/1024.0/1024,
System.currentTimeMillis()-start, entryname);
return vals;
}
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
static short[] readFromIndividualFile(Tile.Source src, Tile.Range range, int zoom, int x, int y) {
long start = System.currentTimeMillis();
String path = Map.DIR + "dat/" + src.name() + "-" + range.name() + zoom + "/" + x + "/" + y + ".dat";
byte[] bytes = new byte[1024*1024*4];
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path))) {
int fileSize = (int)Files.size(Paths.get(path));
short[] vals = new short[fileSize/2];
int read, ix = 0;
while ((read = bis.read(bytes, 0, bytes.length)) != -1) {
for (int n = 0; n < read; n += 2) {
vals[ix++] = (short) ((bytes[n] << 8) | bytes[n + 1] & 0xff);
} // 割り当てられたメモリに short配列としてコピーする
} // 配列bytesには、インデックスreadまでデータが読みこまれている
System.out.printf("個別 %.2fMB %dミリ秒 %s\n", fileSize/1024.0/1024,
System.currentTimeMillis()-start, path);
return vals;
} catch (Exception ex) {
ex.printStackTrace();
return null;
}
}
public String readTextFromZipFile(File zipFile, String entryName) {
StringBuilder sb = new StringBuilder();
try (ZipFile zip = new ZipFile(zipFile)) {
ZipEntry entry = zip.getEntry(entryName);
if (entry != null) {
try (InputStream is = zip.getInputStream(entry);
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) {
sb.append(line).append("\n");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return sb.toString();
}
File zipFile = new File("path/to/archive.zip");
try (ZipFile zip = new ZipFile(zipFile)) {
// 階層パスを指定する (スラッシュ '/' を使用)
ZipEntry entry = zip.getEntry("folder1/subfolder/target.txt");
if (entry != null) {
// 入力ストリームを取得して読み込む
// InputStream is = zip.getInputStream(entry);
// ...
}
} catch (Exception e) {
e.printStackTrace();
}
// ZIPエントリのイテレーション例
try (ZipFile zipFile = new ZipFile(new File(zipPath))) {
Enumeration extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
// entry.getName()などで処理
}
} catch (IOException e) { e.printStackTrace(); }
try (ZipFile zipFile = new ZipFile(zipFilePath)) {
Enumeration extends ZipEntry> entries = zipFile.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
// Check if entry is a directory
if (!entry.isDirectory()) {
try (InputStream inputStream = zipFile.getInputStream(entry)) {
// Read and process the entry contents using the inputStream
}
}
}
}
try (ZipFile zipFile = new ZipFile(filePath)) {
ZipEntry entry = zipFile.getEntry("target_file.txt"); // 目次から直接検索
if (entry != null) {
InputStream is = zipFile.getInputStream(entry);
// 読み取り処理...
}
}
Enumaration の代わりに Iterator を使うべきか?
// Iteratorを取得
Iterator iterator = vector.iterator();
// 要素を列挙
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}