天天看點

Base64編碼實作(Java)

最近還在讀Cay Horstmann的Core Java這本書。在第二卷第三章第四小節中講到了一個Base64編碼。讀了許久,終于把它讀懂了。特此記錄下來,以便将來用得到!

首先看一下Wikipedia上關于Base64編碼原理的解釋

http://zh.wikipedia.org/wiki/Base64

Base64是一種基于64個可列印字元來表示二進制資料的表示方法。由于2的6次方等于64,是以每6個位元為一個單元,對應某個可列印字元。三個位元組有24個位元,對應于4個Base64單元,即3個位元組需要用4個可列印字元來表示。它可用來作為電子郵件的傳輸編碼。在Base64中的可列印字元包括字母A-Z、a-z、數字0-9,這樣共有62個字元,此外兩個可列印符号在不同的系統中而不同。一些如uuencode的其他編碼方法,和之後binhex的版本使用不同的64字元集來代表6個二進制數字,但是它們不叫Base64。

Base64常用于在通常處理文本資料的場合,表示、傳輸、存儲一些二進制資料。包括MIME的email,email via MIME,在XML中存儲複雜資料.

在MIME格式的電子郵件中,base64可以用來将binary的位元組序列資料編碼成ASCII字元序列構成的文本。使用時,在傳輸編碼方式中指定base64。使用的字元包括大小寫字母各26個,加上10個數字,和加号“+”,斜杠“/”,一共64個字元,等号“=”用來作為字尾用途。

編碼後的資料比原始資料略長,為原來的

Base64編碼實作(Java)

。在電子郵件中,根據RFC 822規定,每76個字元,還需要加上一個回車換行。可以估算編碼後資料長度大約為原長的135.1%。

轉換的時候,将三個byte的資料,先後放入一個24bit的緩沖區中,先來的byte占高位。資料不足3byte的話,于緩沖區中剩下的bit用0補足。然後,每次取出6個bit,按照其值選擇

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/

中的字元作為編碼後的輸出。不斷進行,直到全部輸入資料轉換完成。

當原資料長度不是3的整數倍時, 如果最後剩下兩個輸入資料,在編碼結果後加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('\n');
				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('\n');
			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('=');
		}
	}
}
           

Base64OutputStream 這個類繼承了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字段而已。這個字段很重要,因為Base64OutputStream這個類的構造函數中要用到他。我的了解這有點像裝飾模式,包裝了一下,加了一些功能而已。

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','+','/'};
           

這就是base64的編碼表,如果我們要定義自己的編碼表就可以通過更改這個表中的内容。比如用中文,那麼到時候編碼之後就全部是中文了。

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('\n');
				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;

		}
	}
           

這就是Base64編碼的關鍵代碼了。把三個字元變成四個字元。每6個位元組為一組,前邊補2個0.那些位操作就是做這件事情的。

最後還有一點是如果最後剩下的位元組不足三個怎麼辦?

這就是flush方法了

public void flush() throws IOException {
		if(i > 0 && col >= 76){
			super.write('\n');
			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('=');
		}
	}
           

如果隻剩下一個字元,那麼補兩個 ‘=’号,如果剩下一個字元,補一個'='号

解碼就是編碼的逆過程了。。。。