Java 实现 Base64 算法

Java 实现 Base64 算法

一、Base64 编码简介

将3个连续的8bit字节拆分为4个连续6bit字节,每个6bit字节前两位补零,6位二进制数可以表示2^6,64位有效值,可用编码表进行加密。

编码表

public static final 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 static final char[] toBase64URL = {
        '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', '-', '_'
};

二、编码实现原理

#1.总数能被3整除
#例如 byte[24]

#3个连续字节
01111001 01101111 01110101

#拆分为4个字节,前两位补零
00011110 00010110 00111101 00110101
30 22 61 53 #这是编码表的索引,但实际是字节真正表示的值
#对照编码表
d V 8 0 #编码的结果

#2.总数不被3整除 剩余 1 or 2 个字节
#例如 byte[19] 剩余 1
#最后一个字节
01111110 
#拆分为2个字节,最后补上两个'=',使拆分之后的总数能被四整除
00011111 00100000
31 32
e f = =

#例如 byte[20] 剩余 2
#最后2个字节
01111110 00110011
#拆分为3个字节,#最后再补上一个'=',使拆分之后的总数能被四整除
00011111 00100011 00001100
31 35 12 
e i L =

三、JAVA Base64编码实现

Java 代码如下:

 /**
 * base64编码
 * 
 * @param binaryData 需要编码的字节
 * @param isURL true:表示URL编码,false:正常编码
 */
public static String encode(byte[] binaryData,boolean isURL){
    char[] base64 = isURL ? toBase64URL : toBase64;

    if(binaryData==null){
        return null;
    }
    if (binaryData.length==0){
        return "";
    }
    //3个字节为1组,计算分组组数
    int base64GroupCount=binaryData.length/3;

    //以3个字节为1组,剩余的字节数量
    int res=binaryData.length%3;
    //如果字节数量不被3整除,则再加一组
    int base64GroupCountAll=(res==0)?base64GroupCount:base64GroupCount+1;
    //编码的字符总数=组数*每组数量(4)
    char baseChars[]=new char[base64GroupCountAll*4];
    //编码
    int index=0;//binaryData下标
    int baseIndex=0;//编码数组的下标
    for (int i=0;i<base64GroupCount;i++){

        //int 32位,高位存放一个字节,低位存放两个字节
        //每一组的第一个字节,放在高位后半段,第二个,第三个,依次放在低位上
        //例如 00000000 01010110 00010110 00010110
        //&0xff,将byte<0,转换为>0,即将byte (-127,128)=>(0,256)
        int bit=(binaryData[index++]&0xff)<<16|
                (binaryData[index++]&0xff)<<8|
                (binaryData[index++]&0xff);
        //依次取出4个6bit的字节,&0x3f (0011111)保留后6位
        baseChars[baseIndex++]=base64[(bit>>>18)&0x3f];
        baseChars[baseIndex++]=base64[(bit>>>12)&0x3f];
        baseChars[baseIndex++]=base64[(bit>>>6)&0x3f];
        baseChars[baseIndex++]=base64[bit&0x3f];
    }
    //分组剩余字节 1 or 2
    if(res>0){
      //取出第一个字节  
      int b1=(binaryData[index++]&0xff);
        //将第一个字节拆分为一个6bit字节
        baseChars[baseIndex++]=base64[b1>>2];
      if (res==1){//剩余 1 字节
          //将第一个字节剩余2bit取出
          baseChars[baseIndex++]=base64[(b1<<4)&0x3f];
          baseChars[baseIndex++]='=';
          baseChars[baseIndex++]='=';
      }else{//剩余 2 字节
          //取出第二个字节
          int b2=(binaryData[index++]&0xff);
          //将第一个字节剩余2bit和第二个字节前4bit拼接
          baseChars[baseIndex++]=base64[(b1<<4)&0x3f|b2>>4];
          //取出第二个字节剩余4bit
          baseChars[baseIndex++]=base64[(b2<<2)&0x3f];
          baseChars[baseIndex++]='=';
      }
    }
    return new String(baseChars);
}

案例如下:

#三个字节x,y,z
x 01111001 y  01101111 z 01110101
&0xff #将<0的byte,转换成>0的
=======================================
x 01111001 
x 00000000 00000000 00000000 01111001 int
<<16
00000000 01111001 00000000 00000000 
=======================================
y 01101111
y 00000000 00000000 00000000 01101111 int
<<8
00000000 00000000 01101111 00000000
=======================================
z 01110101 
z 00000000 00000000 00000000 01110101 int
=======================================
#bit= x|y|z x放在高位,yz放在低位
bit 00000000 01111001 01101111 01110101 
>>>18
c1 00000000 00000000 00000000 00011110
&0x3f
00000000 00000000 00000000 00111111
---------------------------------------
c1 00000000 00000000 00000000 00011110 #编码c1
=======================================
00000000 01111001 01101111 01110101 
>>>12
c2 00000000 00000000 00000111 10010110
&0x3f
00000000 00000000 00000000 00111111
---------------------------------------
c2 00000000 00000000 00000000 00010110 #编码c2
=======================================
00000000 01111001 01101111 01110101 
>>>6
c3 00000000 00000001 11100101 10111101
&0x3f
00000000 00000000 00000000 00111111
---------------------------------------
c3 00000000 00000000 00000000 00111101 #编码c3
=======================================
00000000 01111001 01101111 01110101
&0x3f
00000000 00000000 00000000 00111111 
---------------------------------------
c4 00000000 00000000 00000000 00110101 #编码c4

#x,y,z 编码为c1,c2,c3,c4

四、解码实现原理

#c1 c2 c3 c4
00011110 00010110 00111101 00110101

#合并为3个字节
#x y z
01111001 01101111 01110101

五、JAVA Base64解码实现

解码表

private static final byte[] decodingTable;

private static final byte[] decodingURLTable; 
//初始化解码表,
//编码表的索引下标实际上是字节进行编码之后所表示的真实的值
//解码表,则存储编码表的下标即可
static {
    decodingTable = new byte[128];
    for (int i = 0; i < toBase64.length; i++) {
        decodingTable[toBase64[i]] = (byte) i;
    }
    //解码表的索引对应编码的字母的AscII码,值对应编码表的索引
}
//初始化解码表,
static {
    decodingURLTable = new byte[128];
    for (int i = 0; i < toBase64URL.length; i++) {
        decodingURLTable[toBase64URL[i]] = (byte) i;
    }
    //解码表的索引对应编码的字母的AscII,值对应编码表的索引
}

Java 解码如下:

/**
 * base64编码
 *
 * @param encode 需要解码的字节
 * @param isURL true:表示URL编码,false:正常编码
 */
public static byte[] decode(byte[] encode,boolean isURL){
    byte[] decodeTable = isURL ? decodingURLTable : decodingTable;
    if(encode==null||encode.length==0){
        return null;
    }
    //以3个字节为1组的组数
    int base64GroupCount=encode.length/4-1;//-1是因为末尾可能存在‘=’,所以最后一组需要单独解析
    //解析为8bit的字节数目,除最后一组以外
    int base64ByteCount=base64GroupCount*3;
    //解析为8bit的字节总数目,包含最后一组
    int base64ByteCountAll=base64ByteCount;
    //末尾两个‘=’,则分组剩余1个字节
    if(encode[encode.length-2]=='=')
    {
        base64ByteCountAll+=1;
    }
    //末尾1个‘=’,则分组剩余2个字节
    else if(encode[encode.length-1]=='=')
    {
        base64ByteCountAll+=2;
    }
    //末尾没有‘=’,则分组剩余3个字节
    else{
        base64ByteCountAll+=3;
    }
    //初始化解析的字节数组
    byte[] decode=new byte[base64ByteCountAll];

    int encodeIndex=0;//编码数组的下标
    int decodeIndex=0;//解析数组的下标
    while (decodeIndex<base64ByteCount){//还剩最后一组时,循环解释
        //和编码类似,将四个6bit字节,合成一个int 32位
        //decodeTable['A']取出编码之前真实的值。
        int bit=decodeTable[encode[encodeIndex++]]<<18|
                decodeTable[encode[encodeIndex++]]<<12|
                decodeTable[encode[encodeIndex++]]<<6|
                decodeTable[encode[encodeIndex++]];
        //将int拆分为3个字节
        decode[decodeIndex++]=(byte)(bit>>>16);
        decode[decodeIndex++]=(byte)((bit>>>8)&0xff);
        decode[decodeIndex++]=(byte)((bit&0xff));
    }
    //解析最后一组
    //取出前2个字符
    int b1=decodeTable[encode[encodeIndex++]];
    int b2=decodeTable[encode[encodeIndex++]];
    if(encode[encode.length-2]=='='){//存在2个'=',表示剩下一个8bit字节
        //将第1个字符的6位,和第2个字符的2位拼接
        decode[decodeIndex++]=(byte)((b1<<2)|(b2>>4));
    } else if(encode[encode.length-1]=='=')
    {//存在1个'=',表示剩下2个8bit字节
        int b3=decodeTable[encode[encodeIndex++]];
        decode[decodeIndex++]=(byte)((b1<<2)|(b2>>4));
        //将第2个字符的4位,和第3个字符的4位拼接
        decode[decodeIndex++]=(byte)((b2<<4)|(b3>>2));
    }else{//剩下3个8bit字节
        int b3=decodeTable[encode[encodeIndex++]];
        int b4=decodeTable[encode[encodeIndex++]];
        decode[decodeIndex++]=(byte)((b1<<2)|(b2>>4));
        decode[decodeIndex++]=(byte)((b2<<4)|(b3>>2));
        //将第3个字符的2位,和第4个字符的6位拼接
        decode[decodeIndex++]=(byte)((b3<<6)|b4);
    }
    return decode;
}

测试结果

@Test
void testBase642(){
    String str = "test测试@123$u";
    System.out.println("\n原字符串:"+str);
    System.out.println("==========================");
    System.out.println("JDK Base64编码:");
    String encodeStr1 = Base64.getEncoder().encodeToString(str.getBytes(StandardCharsets.UTF_8));
    System.out.println(encodeStr1);
    System.out.println("\n自定义 Base64编码:");
    String encodeStr2 = Base64Utils.encode(str.getBytes(StandardCharsets.UTF_8));
    System.out.println(encodeStr2);
    System.out.println("==========================");
    System.out.println("JDK Base64解码:");
    String decodeStr1=new String(Base64.getDecoder().decode(encodeStr1),StandardCharsets.UTF_8);
    System.out.println(decodeStr1);
    System.out.println("\n自定义 Base64解码:");
    String decodeStr2=Base64Utils.decodeToString(encodeStr2);
    System.out.println(decodeStr2);
}

image-20220615165313360

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值