概要
Java 8からjava.util.Base64が導入されてBase64符号化・復号化が標準ライブラリで可能になった。
しかし、一般的な実装だと符号化前データの数倍のメモリが必要になるため、大きなデータを符号化・復号化したい場合にはメモリ使用量の問題が生じる。そこで標準ライブラリによる省メモリなBase64符号化・復号化の実装を考えた。
実装
public class Base64Sample {
private static final int T_LEN = 16 * 1024;
private static final int B_LEN = T_LEN * 3 / 4;
// 符号化
public static void encode(Path src, Path dst) throws IOException {
try (InputStream is = Files.newInputStream(src);
OutputStream os = Files.newOutputStream(dst)) {
byte[] txt = new byte[T_LEN];
byte[] bin = new byte[B_LEN];
int r;
while ((r = is.read(bin)) != -1) {
int i = Base64.getEncoder().encode((r == B_LEN) ? bin : Arrays.copyOf(bin, r), txt);
os.write(txt, 0, i);
}
}
}
// 復号化
public static void decode(Path src, Path dst) throws IOException {
try (InputStream is = Files.newInputStream(src);
OutputStream os = Files.newOutputStream(dst)) {
byte[] txt = new byte[T_LEN];
byte[] bin = new byte[B_LEN];
int r;
while ((r = is.read(txt)) != -1) {
int i = Base64.getDecoder().decode((r == T_LEN) ? txt : Arrays.copyOf(txt, r), bin);
os.write(bin, 0, i);
}
}
}
}
入出力がInputStream/OutputStreamであることが前提だが、固定のbyte配列で符号化・復号化することで、全データを一度に保持することを回避している。
注意すべきは復号化対象データの途中に"="が含まれる場合で、byte配列の末尾に来てしまうとエラーにならない。復号化対象が外部入力データなどの場合は別途チェックが必要。
アンチパターン
あくまで大きなデータを符号化・復号化しなければならない場合のアンチパターンであることに留意。
メモリ消費
// 符号化
String encodedData = Base64.getEncoder().encodeToString(data);
// 復号化
byte[] decodedData = Base64.getDecoder().decode(encodedData);
一般的な実装なのだが、データ全体の符号化前byte[]、符号化後byte[]とStringをメモリに保持してしまう 1。もちろん、処理対象データがヒープサイズに対して十分小さければ全く問題ない。
処理速度
// 符号化
OutputStream os = Base64.getEncoder().wrap(rawos)
// 復号化
InputStream is = Base64.getDecoder().wrap(rawis)
InputStream/OutputStreamで符号化・復号化するので省メモリではあるが、処理速度が遅い 2。メモリ消費を気にする必要があるほど大きいデータを処理するなら実用的でない。