java.langでBase64実装

AndroidアプリでBase64を使う必要があったのですが、どうやらAndroidには入っていないようです。
(ドキュメントには存在するんだけどなー Base64 | Android Developers)

ないなら作るか。というわけで、Base64を実装してみました。
先に白状しておくと、非常にわかりにくいコードです。もっと良い実装があるんだろうけど、ギブアップでした。
(一応もう一つ実装したんですが、速度テストしてみると遅かったです)

public class Base64 {
  
  private static final String TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  
  public static String encode(String data){
    try{
      return encode(data.getBytes("UTF-8"));
    }catch(Exception e){
      return "";
    }
  }

  public static String encode(byte data[]){
    if(data.length == 0){ return ""; }
    
    int i;
    int index[] = new int[1 + data.length * 4 / 3];
    int count = 0;
    int limit = data.length - 3;
    int mod;
    int padding;
    StringBuilder result = new StringBuilder("");
    
    for(i = 0; i < limit; i+=3){
      index[count++] = (data[i] & 0xfc) >>> 2;
      index[count++] = ((data[i] & 0x03) << 4) + ((data[i+1] & 0xf0) >>> 4);
      index[count++] = ((data[i+1] & 0x0f) << 2) + ((data[i+2] & 0xc0) >>> 6);
      index[count++] = ((data[i+2]) & 0x3f);
    }
    
    mod = data.length % 3;
    if(mod == 0){
      index[count++] = (data[i] & 0xfc) >>> 2;
      index[count++] = ((data[i] & 0x03) << 4) + ((data[i+1] & 0xf0) >>> 4);
      index[count++] = ((data[i+1] & 0x0f) << 2) + ((data[i+2] & 0xc0) >>> 6);
      index[count++] = ((data[i+2]) & 0x3f);
    }
    else if(mod == 1){
      index[count++] = (data[i] & 0xfc) >>> 2;
      index[count++] = (data[i] & 0x03) << 4;
    }
    else if(mod == 2){
      index[count++] = (data[i] & 0xfc) >>> 2;
      index[count++] = ((data[i] & 0x03) << 4) + ((data[i+1] & 0xf0) >>> 4);
      index[count++] = (data[i+1] & 0x0f) << 2;
    }

    for(i = 0 ; i < count; i++){
      result.append(TABLE.charAt(index[i]));
    }
    
    padding = (4 - result.length() % 4) % 4;
    for(i = 0 ; i < padding ; i++){
      result.append("=");
    }
    
    return result.toString();
  }
  
  public static String decodeToString(String base64){
    return new String(decode(base64));
  }

  public static byte[] decode(String base64){
    int i;
    int length = base64.length();
    int data[] = new int[length];
    byte result[] = new byte[1 + length * 3 / 4];
    int mod;
    int limit = length - 4;
    int count = 0;
    
    for(i = 0; i < length; i++){
      int c = base64.charAt(i);

      //TABLE.indexOf()は遅い
      if('A' <= c && c <= 'Z'){
        data[i] = c - 'A';
      }
      else if('a' <= c && c <= 'z'){
        data[i] = c - 'a' + 26;
      }
      else if('0' <= c && c <= '9'){
        data[i] = c - '0' + 52;
      }
      else if(c == '+'){
        data[i] = 62;
      }
      else if(c == '/'){
        data[i] = 63;
      }
    }
    
    for(i = 0; i < limit; i+=4){
      result[count++] = (byte)(  ((data[i] & 0x03f) << 2)  + ((data[i+1] & 0x30) >>> 4)  );
      result[count++] = (byte)(  ((data[i+1] & 0x0f) << 4) + ((data[i+2] & 0x3c) >>> 2)  );
      result[count++] = (byte)(  ((data[i+2] & 0x03) << 6) +  (data[i+3] & 0x3f)         );
    }
    
    mod = length % 4;
    if(mod == 0){
      result[count++] = (byte)(  ((data[i] & 0x03f) << 2)  + ((data[i+1] & 0x30) >>> 4)  );
      result[count++] = (byte)(  ((data[i+1] & 0x0f) << 4) + ((data[i+2] & 0x3c) >>> 2)  );
      result[count++] = (byte)(  ((data[i+2] & 0x03) << 6) +  (data[i+3] & 0x3f)         );
    }
    else if(mod == 2){
      result[count++] = (byte)(  ((data[i] & 0x03f) << 2)  + ((data[i+1] & 0x30) >>> 4)  );
    }
    else if(mod == 3){
      result[count++] = (byte)(  ((data[i] & 0x03f) << 2)  + ((data[i+1] & 0x30) >>> 4)  );
      result[count++] = (byte)(  ((data[i+1] & 0x0f) << 4) + ((data[i+2] & 0x3c) >>> 2)  );
    }
    
    return result;
  }
}

エンコード速度テスト

public static void encodeTime(){
  String data = "The_quick_brown_fox_jumps_over_the_lazy_dog";
  byte[] originalData = data.getBytes();
  String enc = "";
  
  long t = System.currentTimeMillis();
  
  for (int i = 0; i < 10000000; i++) {
    //sun mail util
    //enc = new String( com.sun.mail.util.BASE64EncoderStream.encode(originalData) );   

    //apache commons codec
    //enc = org.apache.commons.codec.binary.Base64.encodeBase64String(originalData);

    //自作Base64
    enc = Base64.encode(originalData);
  }
  
  long t2 = System.currentTimeMillis();
  
  System.out.println(t2 - t);
}

デコード速度テスト

public static void decodeTime(){
  String data = "The_quick_brown_fox_jumps_over_the_lazy_dog";
  String enc = Base64.encode(data);
  byte dec[];
  
  long t = System.currentTimeMillis();
  
  for (int i = 0; i < 10000000; i++) {
    //sun mail util
    //dec = com.sun.mail.util.BASE64DecoderStream.decode(enc.getBytes());  
    
    //apache commons codec
    //dec = org.apache.commons.codec.binary.Base64.decodeBase64(enc);
    
    //自作Base64
    dec = Base64.decode(enc);
  }
  
  long t2 = System.currentTimeMillis();
  
  System.out.println(t2 - t);
}

結果

エンコード

パッケージ 時間
sun mail util 4166
apache commons codec 9556
自作Base64 8042


デコード

パッケージ 時間
sun mail util 4461
apache commons codec 9284
自作Base64 7762

というわけで、速度的には一応使えそうです。ただ、なんだかバグがありそうな匂いがぷんぷんするので、要検証ですね。

  • 参考

Base64 - Wikipedia
トラシスラボ 技術ブログ: ページが見つかりません。




javaのビット演算が非常に面倒でした。byte型をビット演算すると勝手にintに変換されるので要注意です。