1
0

More than 1 year has passed since last update.

JavaでBase64符号化・復号化を省メモリに行う

Posted at

概要

Java 8からjava.util.Base64が導入されてBase64符号化・復号化が標準ライブラリで可能になった。
しかし、一般的な実装だと符号化前データの数倍のメモリが必要になるため、大きなデータを符号化・復号化したい場合にはメモリ使用量の問題が生じる。そこで標準ライブラリによる省メモリなBase64符号化・復号化の実装を考えた。

実装

Base64Sample.java
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配列の末尾に来てしまうとエラーにならない。復号化対象が外部入力データなどの場合は別途チェックが必要。

アンチパターン

あくまで大きなデータを符号化・復号化しなければならない場合のアンチパターンであることに留意。

メモリ消費

AntiPattern1.java
// 符号化
String encodedData = Base64.getEncoder().encodeToString(data);
// 復号化
byte[] decodedData = Base64.getDecoder().decode(encodedData);

一般的な実装なのだが、データ全体の符号化前byte[]、符号化後byte[]とStringをメモリに保持してしまう 1。もちろん、処理対象データがヒープサイズに対して十分小さければ全く問題ない。

処理速度

AntiPattern2.java
// 符号化
OutputStream os = Base64.getEncoder().wrap(rawos)
// 復号化
InputStream is = Base64.getDecoder().wrap(rawis)

InputStream/OutputStreamで符号化・復号化するので省メモリではあるが、処理速度が遅い 2。メモリ消費を気にする必要があるほど大きいデータを処理するなら実用的でない。


  1. Encoder/Decoderも内部でnew StringやString#getBytesを使用する 

  2. InputStream/OutputStreamの性質上1バイトずつ符号化・復号化するのでオーバヘッドが大きい 

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0