Base64 编解码原理及源码解析

Base64的实现原理**

1)将给定的字符串转换成对应的字符编码(如:GBK、UTF-8)
  2)将获得该字符编码转换成二进制码
  3)对获得的二进制码进行分组操作
  第一步:每3个字节(8位二进制)为一组,一共24个二进制位
  第二步:将这个24个二进制位分成4组,每个组有6个二进制位,不足6位的,后面补0。
  第三步:在每个组前面加两个0,这样每个组就又变成了8位,即每个组一个字节,4个组就4个字节了。
第四步:根据Base64的转码表找到每个字节对应的符号,这个符号就是Base64的编码值

Base64编码表

在这里插入图片描述

Base64编码规则:

经过Base64编码后的字符串的字符数一定是4的整数倍。在使用Base64编码时,如果得到的字符数不为4的整数倍,则后面使用等号 ‘=’补足
  举例说明:
  (1) 举一个具体的实例,演示英语单词Man如何转成Base64编码。

第一步,“M”、“a”、“n”的ASCII值分别是77、97、110,对应的二进制值是01001101、01100001、01101110,将它们连成一个24位的二进制字符串010011010110000101101110。
  第二步,将这个24位的二进制字符串分成4组,每组6个二进制位:010011、010110、000101、101110。
  第三步,在每组前面加两个00,扩展成32个二进制位,即四个字节:00010011、00010110、00000101、00101110。它们的十进制值分别是19、22、5、46。
  第四步,根据上表,得到每个值对应Base64编码,即T、W、F、u。
  因此,Man的Base64编码就是TWFu。
  如果字节数不足三,则这样处理:
  a)二个字节的情况:将这二个字节的一共16个二进制位,按照上面的规则,转成三组,最后一组除了前面加两个0以外,后面也要加两个0。这样得到一个三位的Base64编码,再在末尾补上一个“=”号。
  比如,“Ma”这个字符串是两个字节,可以转化成三组00010011、00010110、00010000以后,对应Base64值分别为T、W、E,再补上一个“=”号,因此“Ma”的Base64编码就是TWE=。
  b)一个字节的情况:将这一个字节的8个二进制位,按照上面的规则转成二组,最后一组除了前面加二个0以外,后面再加4个0。这样得到一个二位的Base64编码,再在末尾补上两个“=”号。
  比如,“M”这个字母是一个字节,可以转化为二组00010011、00010000,对应的Base64值分别为T、Q,再补上二个“=”号,因此“M”的Base64编码就是TQ==。

源码解读

(1)编码

2/**
 * 编码为Base64<br>
 * 如果isMultiLine为<code>true</code>,则每76个字符一个换行符,否则在一行显示
 * 
 * @param arr 被编码的数组
 * @param isMultiLine 在76个char之后是CRLF还是EOF
 * @param isUrlSafe 是否使用URL安全字符,一般为<code>false</code>
 * @return 编码后的bytes
 */
public static byte[] encode(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
   if (null == arr) {
      return null;
   }

   int len = arr.length;
   if (len == 0) {
      return new byte[0];
   }
     //3个字符一组进行分组
   int evenlen = (len / 3) * 3;
   //获取转码后的base64字符的个数(获取按3个分组加上剩余的一组乘4)
   int cnt = ((len - 1) / 3 + 1) << 2;
   //需要换行的话,获取换行操作次数
   int destlen = cnt + (isMultiLine ? (cnt - 1) / 76 << 1 : 0);
   byte[] dest = new byte[destlen];

   byte[] encodeTable = isUrlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE;

   for (int s = 0, d = 0, cc = 0; s < evenlen;) {
   //第一个对& 0xff(对11111111 进行与获取低8位)左移16
  //、第二个与获取低8位(保证在 base64 范围内),左移8、第三个获取最低8 位然后进行或运算得到24 位  
      int i = (arr[s++] & 0xff) << 16 | (arr[s++] & 0xff) << 8 | (arr[s++] & 0xff);
	  //右移18 位、取高6位
      dest[d++] = encodeTable[(i >>> 18) & 0x3f];
      //右移12 位、取高6位
      dest[d++] = encodeTable[(i >>> 12) & 0x3f];
      //右移6 位、取高6位
      dest[d++] = encodeTable[(i >>> 6) & 0x3f];
      // 取低6位
      dest[d++] = encodeTable[i & 0x3f];
      //是否进行换行输出
      if (isMultiLine && ++cc == 19 && d < destlen - 2) {
         dest[d++] = '\r';
         dest[d++] = '\n';
         cc = 0;
      }
   }

   int left = len - evenlen;// 剩余位数
   if (left > 0) {
     //处理余数为1 和为2 的情况,为1 左移10 到18 位,如果是两个的话,第二个左移2补位数 , 2个字节转3个 base64 字节
      int i = ((arr[evenlen] & 0xff) << 10) | (left == 2 ? ((arr[len - 1] & 0xff) << 2) : 0);
      // 对最后的四个字节的前面面两个进行赋值,
      dest[destlen - 4] = encodeTable[i >> 12];
      dest[destlen - 3] = encodeTable[(i >>> 6) & 0x3f];

      if (isUrlSafe) {
         // 在URL Safe模式下,=为URL中的关键字符,不需要补充。空余的byte位要去掉。
         int urlSafeLen = destlen - 2;
         if (2 == left) {
            dest[destlen - 2] = encodeTable[i & 0x3f];
            urlSafeLen += 1;
         }
         byte[] urlSafeDest = new byte[urlSafeLen];
         System.arraycopy(dest, 0, urlSafeDest, 0, urlSafeLen);
         return urlSafeDest;
      } else {
      //后面字节进行补符号,2 个的话补一个“=”一个补2个等号
         dest[destlen - 2] = (left == 2) ? encodeTable[i & 0x3f] : (byte) '=';
         dest[destlen - 1] = '=';
      }
   }
   return dest;
}

(2)解码
原理:与转码的原理相反,把输入的base64值 4个转成3个,并且去掉编码时添加的“=”

/**
 * 解码Base64
 * 
 * @param in 输入
 * @param pos 开始位置
 * @param length 长度
 * @return 解码后的bytes
 */
public static byte[] decode(byte[] in, int pos, int length) {
   if (ArrayUtil.isEmpty(in)) {
      return in;
   }

   final IntWrapper offset = new IntWrapper(pos);

   byte sestet0;
   byte sestet1;
   byte sestet2;
   byte sestet3;
//获取最大索引位置
   int maxPos = pos + length - 1;
   int octetId = 0;
//定义二进制数组的大小
   byte[] octet = new byte[length * 3 / 4];// over-estimated if non-base64 characters present
   while (offset.value <= maxPos) {
//每次获取4个base64 值
      sestet0 = getNextValidDecodeByte(in, offset, maxPos);
      sestet1 = getNextValidDecodeByte(in, offset, maxPos);
      sestet2 = getNextValidDecodeByte(in, offset, maxPos);
      sestet3 = getNextValidDecodeByte(in, offset, maxPos);

      if (PADDING != sestet1) {
     // 第一个左移2(去掉前面的补零)与第二个右移4 进行与运算,获得第一个8位二进制
         octet[octetId++] = (byte) ((sestet0 << 2) | (sestet1 >>> 4));
      }
      if (PADDING != sestet2) {
         //第二个左移4 与第三个右移2进行与运算,获得第二个8位二进制字节
         octet[octetId++] = (byte) (((sestet1 & 0xf) << 4) | (sestet2 >>> 2));
      }
      if (PADDING != sestet3) {
       //第三个和3进行与操作取低四位,左移6获取后面两个字节然后与第四个进行与运算获取第三个字符
         octet[octetId++] = (byte) (((sestet2 & 3) << 6) | sestet3);
      }
   }

   if (octetId == octet.length) {
      return octet;
   } else {
      // 如果有非Base64字符混入,则实际结果比解析的要短,截取之
      //去掉因“=”多定义的数组长度
      return (byte[]) ArrayUtil.copy(octet, new byte[octetId], octetId);
   }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值