Base 64エンコーディング実装(Java)


最近はCay HorstmannのCore Javaという本も読んでいます.第2巻第3章第4節では、Base 64符号化について述べる.長い間読んでいたが,やっとそれを読んだ.将来使えるように記録します!
まずWikipediaのBase 64符号化原理の解釈を見てみましょう
http://zh.wikipedia.org/wiki/Base64
Base 64は、64個の印刷可能文字に基づいてバイナリデータを表す表現方法である.2の6乗が64に等しいため、6ビットごとに1つのユニットであり、印刷可能な文字に対応する.3バイトには24ビットがあり、4つのBase 64ユニットに対応している.すなわち、3バイトは4つの印刷可能な文字で表す必要がある.Eメールの送信コードとして使用できます.Base 64における印刷可能文字には、62文字のアルファベットA−Z、a−z、0−9の数字が含まれ、他の2つの印刷可能記号は異なるシステムで異なる.uuencodeのようないくつかの他の符号化方法、およびその後のbinhexのバージョンは、6つのバイナリ数字を表すために異なる64文字セットを使用するが、Base 64とは呼ばない.
Base 64は、通常、テキストデータを処理する場合に、バイナリデータを表す、転送する、格納するためによく用いられる.MIMEを含むemail,email via MIMEは、XMLに複雑なデータを格納.
MIME形式の電子メールにおいて、base 64は、binaryのバイトシーケンスデータをASCII文字シーケンスからなるテキストに符号化するために使用することができる.使用時には、伝送符号化方式でbase 64を指定します.使用する文字には、大文字と小文字の各26文字、10文字の数字、プラス記号「+」、スラッシュ「/」の合計64文字、等号「=」が接尾辞として使用されます.
符号化されたデータは元のデータよりやや長く,元のものである.電子メールでは、RFC 822の規定に従って、76文字ごとに、折り返し改行を加える必要があります.符号化後のデータ長は、元の長さの約135.1%と推定できる.
変換するときは、3つのbyteのデータを、前後して24 bitのバッファに入れ、先に来たbyteが上位を占めます.データが3 byte未満の場合、バッファに残っているbitは0で補完します.そして、6 bitずつ取り出し、その値に従ってABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/の文字を符号化後の出力として選択する.すべての入力データ変換が完了するまで続行します.
元のデータ長が3の整数倍でない場合、最後に2つの入力データが残っている場合は、符号化結果に「=」を1つ加算する.最後に入力データが1つ残っている場合は、符号化結果に「=」を2つ追加します.データが残っていなければ、何も追加しないでください.そうすれば、資料の復元の正確性を保証することができます.
作者のコードをもう一度見てみましょう
class Base64OutputStream extends FilterOutputStream
{
	private static char[] toBase64 = {'A','B','C','D','E','F','G','H','I','J','K','L',
		                  'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a',
		                  'b','c','d','e','f','g','h','i','j','k','l','m','n','o','p',
		                  'q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5',
		                  '6','7','8','9','+','/'};
	private int col = 0;
	private int i = 0;
	private int[] inbuf = new int[3];

	public Base64OutputStream(OutputStream out){
		super(out);

	}

	@Override
	public void write(int b) throws IOException {
		inbuf[i] = b;
		i++;
		if(i == 3){
			if(col >= 76){
				super.write('
'); col = 0; } super.write(toBase64[(inbuf[0] & 0xFC) >> 2]); super.write(toBase64[(inbuf[1] & 0x03) << 4 | ((inbuf[1] & 0xF0) >> 4)]); super.write(toBase64[(inbuf[2] & 0xF) << 2 | ((inbuf[2] & 0xC0) >> 6)]); super.write(toBase64[(inbuf[2] & 0x3F)]); col += 4; i = 0; } } @Override public void flush() throws IOException { if(i > 0 && col >= 76){ super.write('
'); col = 0; } if(i == 1){ super.write(toBase64[(inbuf[0] & 0xFC) >> 2]); super.write(toBase64[(inbuf[0] & 0x03) << 4]); super.write('='); super.write('='); }else if(i == 2){ super.write(toBase64[(inbuf[0] & 0xFC) >> 2]); super.write(toBase64[(inbuf[0] & 0x03) << 4 | ((inbuf[1] & 0xF0) >> 4)]); super.write(toBase64[(inbuf[1] & 0x0F) << 2]); super.write('='); } } }

Base 64 OutputStreamというクラスはFilterOutputStreamというクラスを継承していますが、なぜこのクラスを継承するのでしょうか.
ヘルプドキュメントのこのクラスの説明を見てみましょう
This class is the superclass of all classes that filter output streams. These streams sit on top of an already existing output stream (the underlying output stream) which it uses as its basic sink of data, but possibly transforming the data along the way or providing additional functionality.
The class  FilterOutputStream  itself simply overrides all methods of  OutputStream  with versions that pass all requests to the underlying output stream. Subclasses of  FilterOutputStream may further override some of these methods as well as provide additional methods and fields.
このクラスは親のOutputStreamと変わらないが、writeメソッドとoutフィールドをいくつか多く提供しているだけだ.このフィールドは、Base 64 OutputStreamというクラスのコンストラクション関数で彼を使用するため重要です.私の理解では、これは少し装飾モードに似ていて、包装して、いくつかの機能を加えただけです.
private static char[] toBase64 = {'A','B','C','D','E','F','G','H','I','J','K','L',
		                  'M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a',
		                  'b','c','d','e','f','g','h','i','j','k','l','m','n','o','p',
		                  'q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5',
		                  '6','7','8','9','+','/'};

これがbase 64の符号化テーブルであり、独自の符号化テーブルを定義する場合は、このテーブルの内容を変更することができます.例えば中国語を使うと、その時にコードしてからすべて中国語になります.
 
 
public Base64OutputStream(OutputStream out){
		super(out);

	}

を し、 された をこのOutputStreamに します.
public void write(int b) throws IOException {
		inbuf[i] = b;
		i++;
		if(i == 3){
			if(col >= 76){
				super.write('
'); col = 0; } super.write(toBase64[(inbuf[0] & 0xFC) >> 2]); super.write(toBase64[(inbuf[1] & 0x03) << 4 | ((inbuf[1] & 0xF0) >> 4)]); super.write(toBase64[(inbuf[2] & 0xF) << 2 | ((inbuf[2] & 0xC0) >> 6)]); super.write(toBase64[(inbuf[2] & 0x3F)]); col += 4; i = 0; } }

これがBase 64 のキーコードです.3 を4 にする.6バイトごとにグループ し、 に2つ します.それらのビット はこのことをします.
にもう つ、 に ったバイトが3つ だったらどうしますか?
これがflushの です
public void flush() throws IOException {
		if(i > 0 && col >= 76){
			super.write('
'); col = 0; } if(i == 1){ super.write(toBase64[(inbuf[0] & 0xFC) >> 2]); super.write(toBase64[(inbuf[0] & 0x03) << 4]); super.write('='); super.write('='); }else if(i == 2){ super.write(toBase64[(inbuf[0] & 0xFC) >> 2]); super.write(toBase64[(inbuf[0] & 0x03) << 4 | ((inbuf[1] & 0xF0) >> 4)]); super.write(toBase64[(inbuf[1] & 0x0F) << 2]); super.write('='); } }

1 しか っていない は「=」の2 を、1 が っている は「=」の1 を します.
は の である...