java 自定义实现Base64编码

//TODO 修改Decoder的静态方法,禁止提供返回String的方法,原因:在AES加密时才发现自己一直的一个误区,new String()和getBytes()两者操作得到的字节数组不一定都是一样的,应该先getBytes(),才能去new String(),在测试AES加密时,用这个Base64工具类对AES产生的数组进行加密时没问题,解密时返回字符串就错了,因为这个数组不是getBytes()生成的,new String()时,数组中的字节和编码表并不能一一对应,那么你new String()后,再次getBytes()数组就会变化,所以如果有中间其他调用的时候,Decoder不能提供返回String的方法,可以看到所有java 实现的Base64解密的Decoder类都没有提供返回String的方法

修复不支持中文问题,原理在转字节数组的时候 & 0xff (1111 1111)实际上是将负数转为显示成不考虑符号位的负数的补码形式,正数不受影响

//编码实例:"foobar1",请按标注的数字从1-5看,1.将字符串转为字节数组,2将字节数组换成二进制字符串,3.处理字节数组转为使每个元素长度为8,4.拼接数组为一个大字符串,
// 然后每6个长度为一组,最后一个如果长度不够,后面补0,每两个0记一个"=",并放入数组,5.最后将二进制字符串转为十进制,当做base64表中的下标找对应的值拼接起来,如果后面补0,则在后面拼接上"=",
// 最后可以直接返回字符串,或返回字符串的字节数组
//解码过程就是倒着来


// 5 index列:[25, 38, 61, 47, 24, 38, 5, 50, 12, 16]
// 4 二进制字符串按6分组:[011001, 100110, 111101, 101111, 011000, 100110, 000101, 110010, 001100, 010000]
// 3 字符串转数组,按8补0后:[01100110, 01101111, 01101111, 01100010, 01100001, 01110010, 00110001]
// 2 字符串转数组:[1100110, 1101111, 1101111, 1100010, 1100001, 1110010, 110001]

// 1 待解码的文字的字节数组:[102, 111, 111, 98, 97, 114, 49]


/**
* Base 64 编码规则:
* 1. 将byte数组的值转为二进制并放入数组,将数组中的每个元素的长度变成8个bit位,(即如果长度是6,则在值前面补两个0),最后按顺序拼接成一个完整的字符串
* 2.将字符串以6位分组,不足的,要在末尾补0,达到6的倍数(记下补0的次数)
* 3.将 每个分组的字符串拿出来,转为十进制以这个为下标,去查表,取得所需的对应编码值
* 4.将值(可以拼接成字符串,也可以byte数组的形式返回)如果第二部在末尾补0了,每补"00",就在值后拼接一个'='
* @param binaryStrArr
* @return

*/

根据根据rfc 4648和rfc 2045

package com.util.math;

import java.util.Arrays;


import com.util.ArrayUtil;
import com.util.StringUtil;

/**
 * TODO 暂时不支持中文的编码转换
 * @author Administrator
 *
 */
public class Base64 {
	/**
	 * Base 64值参考表
	     值	编码 	    值	编码        值 编码	值     编码
	  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  +
	 12  M   29  d   46 u   63  /
	 13  N   30  e   47 v   
	 14  O   31  f   48 w  (pad)=
	 15  P   32  g   49 x   
	 16  Q   33  h   50 y   
	 */                     
	
	//根据rfc 4648和rfc 2045
	private static final byte[] STANDARD_ENCODE_TABLE = {
            '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', '+', '/'
    };
											
	public static void main(String[] args) {
		String encode = Encoder.encode("中");
		//System.out.println(encode);
		//String decode = Decoder.decode("dGVzdHRlc3R0ZXN0dGVzdA==");
		//System.out.println(decode);
		System.out.println(new String(Base64_2.getEncoder().encode("测试测试".getBytes())));
	}
	
	
//编码实例:"foobar1",请按标注的数字从1-5看,1.将字符串转为字节数组,2将字节数组换成二进制字符串,3.处理字节数组转为使每个元素长度为8,4.拼接数组为一个大字符串,
//	然后每6个长度为一组,最后一个如果长度不够,后面补0,每两个0记一个"=",并放入数组,5.最后将二进制字符串转为十进制,当做base64表中的下标找对应的值拼接起来,如果后面补0,则在后面拼接上"=",
//	最后可以直接返回字符串,或返回字符串的字节数组
	//解码过程就是倒着来

// 5	index列:[25, 38, 61, 47, 24, 38, 5, 50, 12, 16]
// 4	二进制字符串按6分组:[011001, 100110, 111101, 101111, 011000, 100110, 000101, 110010, 001100, 010000]
// 3	字符串转数组,按8补0后:[01100110, 01101111, 01101111, 01100010, 01100001, 01110010, 00110001]
// 2	字符串转数组:[1100110, 1101111, 1101111, 1100010, 1100001, 1110010, 110001]
// 1	待解码的文字的字节数组:[102, 111, 111, 98, 97, 114, 49]
	
	
	
	public static class Encoder{
		
		public static String encode(String str){
//			System.out.println("待编码的文字的字节数组:"+Arrays.toString(str.getBytes()));
			return encode(str.getBytes());
		}
		
		public static String encode(byte[] b){
			String strArr [] = new String[b.length];
			for (int i = 0; i < b.length; i++) {
				strArr[i] = DecimalConversion.decimalToOther(2, String.valueOf((byte)b[i] & 0xff));//& 0xff(1111 1111)实际上是将负数转为显示成不考虑符号位的负数的补码形式,正数不受影响
			}
			String encodeByte = encode0(strArr);
			return encodeByte;
			
		}
		
		public static byte[] encodeByte(String str){
			return encode(str).getBytes();
		}
		
		public static byte[] encodeByte(byte[] b){
			return encode(b).getBytes();
		}
		
		/**
		 * Base 64 编码规则:
		 * --:错误1.将byte数组中的值转为二进制,将所有二进制数的长度相加,看长度是否为8的倍数,如果不足,则在每个数组元素的二进制前面补0,直到长度满足8的倍数
		 * 1. 将byte数组的值转为二进制并放入数组,将数组中的每个元素的长度变成8个bit位,(即如果长度是6,则在值前面补两个0),最后按顺序拼接成一个完整的字符串
		 * 2.将字符串以6位分组,不足的,要在末尾补0,达到6的倍数(记下补0的次数)
		 * 3.将 每个分组的字符串拿出来,转为十进制以这个为下标,去查表,取得所需的对应编码值
		 * 4.将值(可以拼接成字符串,也可以byte数组的形式返回)如果第二部在末尾补0了,每补"00",就在值后拼接一个'='
		 * @param binaryStrArr
		 * @return
		 */
		private static String encode0(String [] binaryStrArr){//二进制字符串   //int类型最大是32位
			int len = 0;//标记第二部,以6分组加 0 的次数
			String binaryStr = "";
			int strArrLength = StringUtil.getStringArrLength(binaryStrArr);
//			System.out.println("字符串转数组:"+ Arrays.toString(binaryStrArr));
			//将二进制的编码,以8位为一组,//不足的前面补0
			for (int i = 0; i < binaryStrArr.length; i++) {
				binaryStrArr[i] = StringUtil.addZeroFirst(binaryStrArr[i], 8);
			}
			binaryStr = StringUtil.getLongStrToStrArr(binaryStrArr);
			//分成6 位一组的字符串,不足末尾补0
			
			int mod6 = binaryStr.length()%6;
			if(mod6 != 0){
				len = 6 - mod6;
				binaryStr = binaryStr.concat(StringUtil.addZero(len));
			}
			//按6个长度分组
			String[] splitForNum = StringUtil.splitForLength(binaryStr, 6);
			StringBuffer sbf = new StringBuffer();
			int lastLen = len/2;
			//byte [] result = new byte[splitForNum.length + lastLen];
			for (int i = 0; i < splitForNum.length; i++) {//将截取的二进制字符串 转十进制
				int index = Integer.valueOf( DecimalConversion.otherToDecimal(2, splitForNum[i]));
				sbf.append((char)STANDARD_ENCODE_TABLE[index]);
			}
			//每加 00;则输出结果 加 =
			for (int i = splitForNum.length; i < splitForNum.length + lastLen; i++) {
				sbf.append('=');
			}
			
//			System.out.println("字符串转数组,按8补0后:"+ Arrays.toString(binaryStrArr));
//			System.out.println("二进制字符串按6分组后:"+ Arrays.toString(splitForNum));
			return sbf.toString();
			
		}
		
	}
	
	
	public static class Decoder{
        
        /**
         *4. 将前面得到的字符串进行按8分组,并去除前面多余的0(不去也行)
         *5. 将每个二进制元素转为十进制,并放入byte数组,(可以直接返回数组,也可以拼接成字符串返回)
         * 
         * @param str
         */
        public static byte[] decodeByte(String str){
            String longStr = decode0(str);
            //按8分组
            String[] binaryEightStr = StringUtil.splitForLength(longStr, 8);
//            System.out.println("字符串转数组,按8补0后:"+Arrays.toString(binaryEightStr));
            byte [] b = new byte[binaryEightStr.length]; 
            for (int i = 0; i < binaryEightStr.length; i++) {
                binaryEightStr[i] = StringUtil.removeStartingNumStr(binaryEightStr[i], 0);//去除前面多余的0
                //转为十进制
                int decimalValue = Integer.valueOf(DecimalConversion.otherToDecimal(2, binaryEightStr[i]));
                b[i] = (byte) decimalValue;
            }
//            System.out.println("字符串转数组:"+Arrays.toString(binaryEightStr));
//            System.out.println("待解码的文字的字节数组:"+Arrays.toString(b));
            return b;
            //return new Strng(b);//TODO //不能随便new String,必须先getBytes(),然后在new String(),不然得到的字节数组前后会不一致,不然会出现异想不到的后果
        }
        
        
        
        public static byte[] decodeByte(byte[] b){
            return decodeByte(new String(b));
        }
        
        
        
        /**
         * 1. 去除字符串中的"=",并记录下"="出现的次数
         * 2. 去除后的字符串,分成字符串数组,每个值去查 base64标准表,得到对应的下标索引,放入到一个数组中
         * 3. 将数组拼接成一个完整的二进制字符串,并且去除末尾多余的0(通过"="出现的次数*2)
         * @param bytes
         * @return
         */
        private static String decode0(String str){
            int indexOf = str.indexOf("=");
            int equalsCount = 0;
            if(indexOf != -1){
                str = str.substring(0, indexOf);
                equalsCount = str.length() - indexOf;
            }
            
            byte[] bytes = str.getBytes();
            
            String[] strArr = new String[bytes.length]; 
            String[] binaryStrArr = new String[bytes.length]; 
            for (int i = 0; i < bytes.length; i++) {
                //返回 -1 的原因,二分查找,要求查找的数组是有序的....
                //strArr[i]= String.valueOf(Arrays.binarySearch(STANDARD_ENCODE_TABLE, bytes[i]));//查找对应编码的下标
                strArr[i] = String.valueOf(ArrayUtil.searchIndex(STANDARD_ENCODE_TABLE, bytes[i]));//
                String strIndex = DecimalConversion.decimalToOther(2, strArr[i]);
                binaryStrArr[i] = StringUtil.addZeroFirst(strIndex, 6);
            }
//            System.out.println("index列:"+Arrays.toString(strArr));
//            System.out.println("二进制字符串按6分组:"+Arrays.toString(binaryStrArr));
            String longStr = StringUtil.getLongStrToStrArr(binaryStrArr);
            longStr.substring(0,longStr.length() - (equalsCount << 1));//去除末尾添加的0//equals *  2
            return longStr;
        }
        
    }
    

	
}

用到的自定义String工具类:

package com.util;
import java.lang.String;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class StringUtil{
	
	
	/**
	 * 返回string 的单个字符的String 数组形式,而不是字符数组形式
	 * @param str
	 * @return
	 */
	public static String[] getStrings(String str){
		int j = str.length();
		String [] strArr = new String[j];
		for (int i = 0; i < j; i++) {
			strArr[i] = str.substring(i, i + 1);
		}
		return strArr;
	}
	
	/**
	 * 反转
	 * @param result
	 * @return
	 */
	public static String reverse(String result) {
		StringBuffer sbf  = new StringBuffer();
		for (int i = 0,j = result.length(); i < j; i++) {
			sbf.append(result.substring(j-i-1,j-i));
		}
		return sbf.toString();
	}
	
	/**
	 * 获得反转的单个字符的字符串数组
	 * @param str
	 * @return
	 */
	public static String[] getStringsReverse(String str){
		return getStrings(reverse(str));
	}
	
	/**
	 * 返回传入的数量的"0"字符串
	 * @param size
	 * @return
	 */
	public static String addZero(int size){
		StringBuffer sbf = new StringBuffer();
		for (int i = 0; i < size; i++) {
			sbf.append("0");
		}
		return sbf.toString();
	}
	
	/**
	 * 根据位数自动添加缺少的0,将0补在开头
	 * 补全二进制的0
	 * @param size
	 * @return	返回补全后的字符串
	 */
	public static String addZeroFirst(String str,int num){
		int mod = str.length() % num;
		int len = 0;
		if(mod == 0) return str;
		else if(mod != 0) len = num - mod;
		StringBuffer sbf = new StringBuffer();
		for (int i = 0; i < len; i++) {
			sbf.append("0");
		}
		return sbf.toString().concat(str);
	}
	
	/**
	 * 根据位数自动添加缺少的0,将0补在结尾
	 * 补全二进制的0
	 * @param size
	 * @return	返回补全后的字符串
	 */
	public static String addZeroEnd(String str,int num){
		return reverse(addZeroFirst(reverse(str), num));
	}
	
	/**
	 * 根据传入长度截取字符串,返回字符串数组
	 * 注意,位数不够的请自行补0
	 * @param str
	 * @param num
	 * @return
	 */
	public static String[] splitForLength(String str,int num){
		/*int numValue = str.length() % num;
		if(numValue != 0){
			str = str.concat(addzero(num - numValue));
		}*/
		String []  strArr = new String[str.length() / num];
		for (int i = 0; i < strArr.length; i++) {
			strArr[i] = str.substring((0 + i)*num,(1 + i)*num);// 0,6 ; 6,12
		}
		return strArr;
		
	}
	
	/**
	 * 获得string数组的数组各个元素的长度之和
	 * @param strArr
	 * @return
	 */
	public static int getStringArrLength(String[] strArr){
		int count = 0;
		for (String str : strArr) {
			count += str.length();
		}
		return count;
	}
	
	/**
	 * 获得字符串数组的各个下标拼接的长字符串
	 * @param strArr
	 * @return
	 */
	public static String getLongStrToStrArr(String[] strArr){
		return getLongStrToIndex(strArr,0,strArr.length);
	}
	
	public static String getLongStrToIndex(String[] strArr,int endIndex){//index 索引,下标
		return getLongStrToIndex(strArr, 0,endIndex);
	}
	
	public static String getLongStrToIndex(String[] strArr,int startIndex,int endIndex){
		String longStr = "";
		for (int i = startIndex; i < endIndex; i++) {
			longStr += strArr[i];
		}
		return longStr;
	}
	
	public static boolean checkEmpty(String str){
		if(null != str && !str.equals("")){
			return false;
		}
		return true;
	}
	
	/**
	 * 去除以特定数字开头的子字符串,直到后面跟着的是其他子字符串,
	 * 返回一个去除后的新字符串
	 * 
	 * @param str
	 * @param num	单个数字,范围:0-9
	 */
	public static String removeStartingNumStr (String str,int num){
		return removeStartingStr(str, String.valueOf(num));
	}

	/**
	 * 去除以特定数字结尾的子字符串,直到前面跟着的是其他字符串
	 * 返回一个去除后的新字符串
	 * @param str
	 * @param num
	 * @return
	 */
	public static String removeEndingNumStr(String str,int num){
		String removeStartingNumStr = removeStartingStr(reverse(str),String.valueOf(num));
		return reverse(removeStartingNumStr);
	}

	/**
	 * 去除以特定字符(注意:只能是单个字符)开头的子字符串,直到后面跟着的是其他子字符串,
	 * 返回一个去除后的新字符串
	 * @param str
	 * @param childrenStr
	 * @return
	 */
	public static String removeStartingStr (String str,String childrenStr){
		String[] strings = getStrings(str);
		Integer index = null;
		for (int i = 0; i < strings.length; i++) {
			if(strings[i].equals(childrenStr)){
				index = i;
			}else{
				break;
			}
		}
		if(null != index){
			str = str.substring(index+1);
		}
		return str;
	}
	
	/**
	 * 去除以特定字符(注意:只能是单个字符)结尾的子字符串,直到前面跟着的是其他子字符串,
	 * 返回一个去除后的新字符串
	 * @param str
	 * @param childrenStr
	 * @return
	 */
	public static String removeEndingStr(String str,String childrenStr){
		String removeStartingNumStr = removeStartingStr(reverse(str),childrenStr);
		return reverse(removeStartingNumStr);
	}

}

数组工具类:

package com.util;

public class ArrayUtil {
	
	/**
	 * 无序数组的查找
	 * 如果是有序的,Arrays.binarySearch()二分查找
	 * @param b
	 * @param key
	 * @return	返回该元素在数组中的下标,如果有数组中要查询的值有多个,则返回第一次出现的索引
	 * 			-1:表示数组中不存在该值
	 */
	public static int searchIndex(byte[] b,byte key){
		byte[] b2 = {key};
		String str = new String(b);
		String keyStr = new String(b2);
		if(str.contains(keyStr)){
			return str.indexOf(keyStr);
		}else{
			return -1;
		}
	}
	
	/**
	 * 无序数组的查找
	 * 如果是有序的,Arrays.binarySearch()二分查找
	 * @param b
	 * @param key
	 * @return	返回该元素在数组中的下标,如果有数组中要查询的值有多个,则返回第一次出现的索引
	 * 			-1:表示数组中不存在该值
	 */
	public static int searchIndex2(byte[] b,byte key){
		for (int i = 0; i < b.length; i++) {
			if(b[i] == key){
				return i;
			}
		}
		return -1;
	}

}

自定义实现的DecimalConversion工具类,在上篇进制转换类中有https://blog.csdn.net/kzcming/article/details/79649639

java Base64主要实现有:

1.java8.0 中 java.util.Base64 效率较高

2.java8 中的sun.misc.BASE64Decoder和Base64Encoder,因为支持不是很好,以后可能会废弃

3.java6中隐藏较深的javax.xml.bind.DatatypeConverter有两个静态方法:parseBase64Binary和 printBase64Binary

其他三方实现

4.apache的commons-codec  的jar包的org.apache.commons.codec.binary.Base64

5.便是Google Guava库里面的com.google.common.io.BaseEncoding.base64() 这个静态方法;

6是net.iharder.Base64,这个jar包就一个类;

7.号称Base64编码速度最快的MigBase64,


& 0xff 具体解释:

&上0xff(1111 1111)实际上是将负数转为显示成不考虑符号位的负数的补码形式,正数不受影响

举例: - 13 & 0xff = 243; 13 & 0xff = 13;

-13的原码为:1000 1101 (13的原码为:0000 1101,正数的补码原码反码相同,负数的原码为正数的原码将最高位符号位改为1)

-13的反码为:1111 0010 (负数的反码为原码取反,最高位不变)

-13的补码为:1111 0011(负数的补码为原码+1),将此二进制转为十进制:243

而 0xff 的二进制: 1111 1111

进行& 操作,1为true,0为false,两个数转为二进制,从高位开始比较,如果两个数都为1,则为1,否则为0


猜你喜欢

转载自blog.csdn.net/kzcming/article/details/79731711