概要
昨日はJavaでBase64符号化・復号化を省メモリで行う記事を書いた。
上記はInputStream/OutputStreamで入出力しているが、メモリ上にデータを保持する必要がある場合は別の実装になるので、今回はStringからbyte[]への復号化について考えた。
実装
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 byte[] decode(String src) throws IOException {
byte[] result = new byte[getDecodedLength(src)];
try (Reader reader = new java.io.StringReader(src)) {
char[] buf = new char[T_LEN];
byte[] enc = new byte[T_LEN];
byte[] dec = new byte[B_LEN];
int r, offset = 0;
while ((r = reader.read(buf)) != -1) {
copy(buf, enc, r);
int len = Base64.getDecoder().decode((r == B_LEN) ? enc : Arrays.copyOf(enc, r), dec);
System.arraycopy(dec, 0, result, offset, len);
offset += len;
}
}
return result;
}
private static int getDecodedLength(String src) {
int len = src.length() * 3 / 4;
for (int i = 1; i <= 2; i++) {
if (src.charAt(src.length() - i) == '=') {
len--;
} else {
break;
}
}
return len;
}
private static void copy(char[] chars, byte[] bytes, int length) {
for (int i = 0; i < length; i++) {
bytes[i] = (byte) chars[i];
}
}
}
復号化対象のStringの長さおよび末尾の"="の数から復号化後のbyte数を算出し、必要な長さのbyte[]を事前に確保している。それ以降は前回記事と同様に少しずつ復号化することで、入出力となる復号化前Stringと復号化後byte[]以外には大きなオブジェクトを保持しないようにしている。
なお、入力となるStringが正しくBase64符号化されていることを前提とした実装なので、そうではない可能性がある場合には事前にチェックする必要がある。
アンチパターン
大きなbyte[]を生成する場合、ByteArrayOutputStreamは使わない方が良い。toByteArray()は、書き込まれた内部バッファからコピーして新たなbyte[]を生成するため、メモリ消費が大きくなるためである 1。
-
ByteArrayOutputStream#toByteArrayの内部でArrays#copyOfを呼び出している ↩