美文网首页程序员
Base64编码与Java版本实现

Base64编码与Java版本实现

作者: 坚果jimbowhy | 来源:发表于2018-12-17 18:35 被阅读0次

    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 \""  
            }
       ]
    }
    

    相关文章

      网友评论

        本文标题:Base64编码与Java版本实现

        本文链接:https://www.haomeiwen.com/subject/jqwckqtx.html