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 \""  
        }
   ]
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341