Base64编码与Java版本实现
Base64 readme ========================================
Base64是比较常用的一种能将二进位数据以可视字符串表达出的编码方式,使用了64个可见字符来编码,每个字节只利用了 6bits 信息,即2^6=64种状态对应64个可见字符。二进制转到字符称为编码,由字符转换到二进制称为解码。转换时,3个二进位字节分拆成四组6bits的数据,按取值索引找到码表对应的字符即可,当数据最后一组不足3字节,就使用padding填充,确保转换后的Base64编码数量是4字节的整数倍。Base64不只一种编码方案,基础的方案中使用了除号,编码后的内容不能用于文件名。在URL中,+/=三个符号要对应转换成 %2B %2F %3D,这会占用有长度要求URL。所以,后来推出有兼容URL与文件名的编码方案 Base64url,这个方案中使用了 - 和 _ 替换了基础方案中使用的 + 号和 / 号,对于 = 这个符号的处理,有些实现会省略,有些则以圆点替换。
除Base64外,还有Base16即Hex十六进编码也是使用较多的一种,这种编码刚好用两个字节编码一个二位字节数据。
Base64编码 - https://en.wikipedia.org/wiki/Base64
base64url in RFC 4648 - https://tools.ietf.org/html/rfc4648
Base32 - RFC 4648 alphabet - https://en.wikipedia.org/wiki/Base32
MIME编码 - https://en.wikipedia.org/wiki/MIME
Base16 Hexadecimal - https://en.wikipedia.org/wiki/Hexadecimal
(code point +:43 /:47 0:48 =:61 A:65 a:97)
Java 的Base64工具类提供了一套静态方法获取下面三种BASE64编解码器:
static Base64.Decoder getDecoder() 解码使用基本型 base64 编码方案。
static Base64.Encoder getEncoder() 编码使用基本型 base64 编码方案。
static Base64.Decoder getMimeDecoder() 解码使用 MIME 型 base64 编码方案。
static Base64.Encoder getMimeEncoder() 编码使用 MIME 型 base64 编码方案。
static Base64.Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) 可以指定每行的长度及行的分隔符。
static Base64.Decoder getUrlDecoder() 解码使用 URL 和文件名安全型 base64 编码方案。
static Base64.Encoder getUrlEncoder() 编码使用 URL 和文件名安全型 base64 编码方案。
基本:输出被映射到一组字符A-Za-z0-9+/,编码不添加任何行标,输出的解码仅支持A-Za-z0-9+/。
URL:输出映射到一组字符A-Za-z0-9+_
MIME: 输出隐射到MIME友好格式。输出每行不超过76字符,并且使用 \r\n 作为分割。编码输出最后没有行分割。
Base64的Java语言实现
http://www.herongyang.com/Encoding/Base64-Sun-Java-Implementation.html
http://www.runoob.com/java/java8-base64.html
第三方实现Base64的API有 Apache Commons Codec library 的org.apache.commons.codec.binary.Base64,还有 Google Guava 库里的 com.google.common.io.BaseEncoding.base64() 这个静态方法。最后一个,号称Base64编码速度最快的 MigBase64 而且是10年前的实现。
这里贴的是坚果的实现,好久不写Java,找个练习题做做以免荒废了:
import java.util.Base64;
import java.nio.charset.Charset;
public class coding {
static public void main(String args[]) throws Exception {
String a = new String("Base64的Java语言实现");
String cp = Charset.defaultCharset().name();
log("Default CodePage "+cp);
String b = Base64.getEncoder().encodeToString(a.getBytes());
String c = new String( Base64.getDecoder().decode(b) );
String d = Basee64encode(a.getBytes());
String e = new String(Basee64decode(d));
log("encode => "+a+" => "+b+" == "+d+"? "+(b.equals(d)?"PASS":"FAIL"));
log("decode => "+c+" == "+e+"? "+(c.equals(e)?"PASS":"FAIL"));
}
static public String Basee64encode(byte[] bin){
int i = bin.length%3;
int g = bin.length - i;
StringBuffer s = new StringBuffer();
String fix = new String();
if( i==1 ){
int b = (0x3f & (bin[g]<<4));
fix += map64(0x3F & bin[g]>>2)+""+map64(b)+"==";
}else if( i==2 ){
int b = (0x03 & bin[g])<<4 | (0xf0 & bin[g+1])>>4;
int c = (0x0f & bin[g+1])<<2; // !!! 最后四bits移到最高位
fix += map64(0x3f & bin[g]>>2) +""+ map64(b) +""+ map64(c)+'=';
}
for (i=0; i<g; i+=3 ) {
int a = (0xff & bin[i] )>>2; // bigger first
int b = (0x03 & bin[i] )<<4 | (0xF0 & bin[i+1])>>4;
int c = (0x0f & bin[i+1])<<2 | (0xC0 & bin[i+2])>>6;
int d = (0x3f & bin[i+2]);
s.append(map64(a));
s.append(map64(b));
s.append(map64(c));
s.append(map64(d));
}
return s.toString()+fix;
}
static public byte[] Basee64decode(String msg){
byte[] bytes = msg.getBytes();
byte[] res = new byte[bytes.length*3/4];
for(int i=0; i<bytes.length; i+=4){
byte a = unmap64(bytes[i]);
byte b = unmap64(bytes[i+1]);
byte c = unmap64(bytes[i+2]);
byte d = unmap64(bytes[i+3]);
res[i*3/4+0] = (byte)(a<<2 | b>>4);
res[i*3/4+1] = (byte)(b<<4 | c>>2);
res[i*3/4+2] = (byte)(c<<6 | d);
}
int l = bytes.length;
int pad = bytes[l-2]=='='? 2:bytes[l-1]=='='? 1:0;
if( pad>0 ){
byte[] ret = new byte[res.length-pad];
System.arraycopy(res, 0, ret, 0, res.length-pad);
return ret;
}
return res;
}
static public char map64(int i){
// +:43 /:47 0:48 =:61 A:65 a:97
if( i>63 ) return '=';
byte code = (byte)(i==62?'+':i==63?'/':i<26?'A'+i:i<52?'a'+i-26:'0'+i-52);
return (char)code;
}
static public byte unmap64(byte i){
// +:43 /:47 0:48 =:61 A:65 a:97
if( i=='=' ) return 0;
byte index = (byte)(i=='+'?62:i=='/'?63:i<'A'?i-'0'+52:i<'a'?i-'A':i-'a'+26);
return (byte)index;
}
static public void log(String t){
System.out.print(t+"\n");
}
}
字符串转字节处理
String.getBytes()方法可以将字串的字节数组导出,但特别要注意的是,本方法将返回该操作系统默认的编码格式的字节数组。在不同平台上,系统默认的代码页可能不一致,英文系统一般使用 iso-8859-1,中文系统有 GBK,不考虑到这一点软件就会有问题。例如如下示例代码,通过指定 UTF-16/UTF-8 来获取到字串的字节数据。注意 UTF-16 输出多了两个字节 0xFE 0xFF,这是BOM信息,字节顺序标记 Byte Order Mark,它有两个字节,值大的在后表示 BigEnding 大尾编码方式,通过 UTF-16BE/UTF-16LE 指定大端小端来去除这两额外的BOM字节。关于大尾小尾,前者表高有效位先编码,后者表示低有效位先编码,即对一个两字节的汉字来说,大尾表示高位的那个字节先编码输出。
String a = new String("的J");
byte[] as = a.getBytes("UTF-16");
for( int i=0; i<as.length; i++){
int c = as[i] & 0xFF;
log( "Byte Code "+c+ " 0x"+Integer.toHexString(c) + " 0b"+Integer.toBinaryString(c) );
}
// UTF-16 的 字使用4个字节编码,英文字母2个字节
Byte Code 254 0xfe 0b11111110
Byte Code 255 0xff 0b11111111
Byte Code 118 0x76 0b1110110
Byte Code 132 0x84 0b10000100
Byte Code 0 0x0 0b0
Byte Code 74 0x4a 0b1001010
// UTF-8 的 字使用3个字节编码,英文字母1个字节
Byte Code 231 0xe7 0b11100111
Byte Code 154 0x9a 0b10011010
Byte Code 132 0x84 0b10000100
Byte Code 74 0x4a 0b1001010
不同编码的字节顺序标记的表示编辑
编码 表示 (十六进制) 表示 (十进制)
UTF-8 EF BB BF 239 187 191
UTF-16 大端序 FE FF 254 255
UTF-16 小端序 FF FE 255 254
UTF-32 大端序 00 00 FE FF 0 0 254 255
UTF-32 小端序 FF FE 00 00 255 254 0 0
复习Java运算符优先级
优先级 描述 运算符
1 括号 () []
2 正负号 + -
3 自增自减非 ++ -- !
4 乘除取余 * / %
5 加减 + -
6 移位运算 < >> >>>
7 大小关系 > >= < <=
8 相等关系 = !=
9 按位与 &
10 按位异或 ^
11 按位或 |
12 逻辑与 &&
13 逻辑或 ||
14 条件运算 ?:
15 赋值运算 = += -= *= /= %=
16 位赋值运算 = |= <<= >>= >>>=
复习Java基础数据类型
Primitive type Size Minimum Maximum Wrapper type
boolean — — — Boolean
char 16 bits Unicode 0 Unicode 2^16-1 Character
byte 8 bits -128 +127 Byte
short 16 bits -2^15 +2^15-1 Short
int 32 bits -2^31 +2^31-1 Integer
long 64 bits -2^63 +2^63-1 Long
float 32 bits IEEE754 IEEE754 Float
double 64 bits IEEE754 IEEE754 Double
void — — — Void
范围大的向范围小的数据类型转换时,需要考虑符号位的影响。如无符号整数就不能直接转为byte,值>127的正数都不行,视为负数。Java所有数值都是带符号的,没有无符号数值。反过来,处理byte数据时,如何无符号化处理?按补码的规律,byte数据如果为负数,可以+256来实现,正数不用处理,也可以和 0xFF 进行位与运算,这个位运算操作可以去掉转换后的数值的符号位。例如,下例中byte的-1转换到int时,通过与运算将int的符号位清零,这样实现byte数据的无符号化。
byte myByte =(byte)0xff;
int myInt = myByte & 0xFF;
sytem.out.println( ""+(by & 0xff));
sytem.out.println( ""+(by + 256) );
取得某正数的负数补码表达规则是,按位取反加1。1的负数是-1,补码就是对正1的十进位 00000001 取反得到 11111110,加1就得到 11111111,用16进位表示就是 0xFF。
1的二进位表示 00000001 128的二进位表示 10000000
1的二进位取反 11111110 128的二进位取反 01111111
二进位取反加一 11111111 二进位取反后加一 10000000
即得到1的对应负数补码 0xFF 128的对应负数-128的补码为 0x80,注意和128一样
计算机在读取数据进行运算时,会根据最高位即符号位来应用加减法则进行计算。现在根据补码值 0x81 反解出这个原值,字面上如果无称号处理0x81就是129,按减1求反得出负数对应的正值,即127,所以0x81这个补码对应的负数就是-127。
byte by = (byte)129;
System.out.println(""+(by));
System.out.println(""+(by&0xff));
System.out.println(""+(by+256));
Base16码表
Index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Encode 0 1 2 3 4 5 6 7 8 9 A B C D E F
Base64码表
Index Char Index Char Index Char Index Char
0 A 16 Q 32 g 48 w
1 B 17 R 33 h 49 x
2 C 18 S 34 i 50 y
3 D 19 T 35 j 51 z
4 E 20 U 36 k 52 0
5 F 21 V 37 l 53 1
6 G 22 W 38 m 54 2
7 H 23 X 39 n 55 3
8 I 24 Y 40 o 56 4
9 J 25 Z 41 p 57 5
10 K 26 a 42 q 58 6
11 L 27 b 43 r 59 7
12 M 28 c 44 s 60 8
13 N 29 d 45 t 61 9
14 O 30 e 46 u 62 +
15 P 31 f 47 v 63 /
padding =
URL and Filename safe Base 64 Alphabet
Value Encoding Value Encoding Value Encoding Value Encoding
0 A 17 R 34 i 51 z
1 B 18 S 35 j 52 0
2 C 19 T 36 k 53 1
3 D 20 U 37 l 54 2
4 E 21 V 38 m 55 3
5 F 22 W 39 n 56 4
6 G 23 X 40 o 57 5
7 H 24 Y 41 p 58 6
8 I 25 Z 42 q 59 7
9 J 26 a 43 r 60 8
10 K 27 b 44 s 61 9
11 L 28 c 45 t 62 - (minus)
12 M 29 d 46 u 63 _ (underline)
13 N 30 e 47 v
14 O 31 f 48 w
15 P 32 g 49 x
16 Q 33 h 50 y (pad) .
Multipurpose Internet Mail Extensions (MIME)是Base64的另一种编码方案,广泛应用于文件的编码,MIME 消息能包含文本、图像、音频、视频以及其他应用程序专用的数据,如IE保存网页单文件MHT方式就是使用的MIME,Multipart message 编码信息参考:
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary=frontier
This is a message with multiple parts in MIME format.
--frontier
Content-Type: text/plain
This is the body of the message.
--frontier
Content-Type: application/octet-stream
Content-Transfer-Encoding: base64
PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg
Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==
--frontier--
Base32码表 - RFC 4648 alphabet
Value Symbol Value Symbol Value Symbol Value Symbol
0 A 8 I 16 Q 24 Y
1 B 9 J 17 R 25 Z
2 C 10 K 18 S 26 2
3 D 11 L 19 T 27 3
4 E 12 M 20 U 28 4
5 F 13 N 21 V 29 5
6 G 14 O 22 W 30 6
7 H 15 P 23 X 31 7
padding =
PS: 编程工具用的是Sublime Text 3,编译命令通过Build Tools调用Java SDK完成,很轻便的工具。
Sublime Text with Java Programming编译工具配置参考:
{
// "shell_cmd": "javac.exe \"$file\" | java.exe \"$file_base_name\"",
// "shell_cmd": "ECHO Compiling $file_base_name.java & javac -encoding UTF-8 \"$file\" & java \"$file_base_name\"",
"shell_cmd": "ECHO Compiling $file_base_name.java && javac -encoding UTF-8 \"$file\" && java \"$file_base_name\"",
"file_regex": "^(...*?):([0-9]*):?([0-9]*)",
"working_dir": "${file_path}",
"selector": "source.java",
"encoding":"gbk",
"variants":[
{
"name":"编译",
"shell_cmd": "ECHO Compiling $file_base_name.java & javac -d . -encoding UTF-8 \"$file\"",
},
{
"name":"运行当前类",
"shell_cmd":" java \"$file_base_name\" "
},
{
"name":"cmd中运行",
"shell_cmd":" start cmd /c \"javac -encoding UTF-8 \"$file\" & java \"$file_base_name\" & pause \""
}
]
}