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个字符,等号“=”用来作为后缀用途。

编码后的数据比原始数据略长,为原来的{\frac  {4}{3}}。在电子邮件中,根据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 FilterOutputStreammay 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('=');
}
}

如果只剩下一个字符,那么补两个 ‘=’号,如果剩下一个字符,补一个'='号


解码就是编码的逆过程了。。。。




智能推荐

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告