Administrator
发布于 2023-01-04 / 39 阅读
0
0

Encode & Decode & Base64

Encode & decode & character & byte

encode:从字符到字节
decode:从字节到字符

一个字节:8个bit
一个字符:2个字节(具体看字符集)

客户端与服务器端传输数据:默认iso8859-1
客户端将数据按照iso8859-1字符集,将字符进行编码,成为字节
按照http协议将数据传输到服务器端,
服务器端按照iso8859-1字符集对接收的字节进行解码,成为字符

javase里的String类的byte[]getBytes(String charsetName) throws UnsupportedEncodingException方法,就是将字符串按照参数charsetName,对字符串进行编码为字节数组。

String的构造器方法:new String(byte[] bytes, String charsetName),会对bytes字节数组,按照参数charsetName进行解码为字符串,然后显示。

比如:现在有字符串A= “中国你好”,是iso8859-1编码的,因为iso8859-1不支持中文,所以按照iso8859-1解码成字符后肯定是乱码,我们想要按照utf-8
字符集进行显示字符串A,应该怎么搞?

方法很简单,首先用iso8859-1对字符串A编码为字节数组,然后按照utf-8进行对字节数组进行解码为字符串B,在tuf-8的语言环境下,字符串B就会显示为 中国你好。

用java api实现的话,

String A="中国";
byte[] a = A.getBytes("iso8859-1");
String B = new String(a,"UTF-8");
System.out.println(B);


UTF-8字符编码

基本概念:一个字节(byte) 为 包含8个bit,可以表示256种含义

256中含义太少了,不够用。所以需要多个字节组成 一个 单元,这样就能表示 更多的含义了。比如2个字节 组成一个单元,那么就能表示256*256 = 65536种含义了。

UTF-8 是一种编码方式,每一个编码单元 由 1-3个字节组成。怎么确定某个编码单元 包含几个字节呢?规则如下:

对于UTF-8编码中的任意字节 B:

如果B的第一位为0,那么代表当前字符为单字节字符,占用一个字节的空间。0之后的所有部分(7个位)代表在Unicode中的序号。

如果B以110开头,那么代表当前字符为双字节字符,占用2个字节的空间。110之后的所有部分(5个位)加上后一个字节的除10外的部分(6个位)代表在Unicode中的序号。且第二个字节以10开头

如果B以1110开头,那么代表当前字符为三字节字符,占用3个字节的空间。1110之后的所有部分(4个位)加上后两个字节的除10外的部分(12个位)代表在Unicode中的序号。且第二、第三个字节以10开头

如果B以11110开头,那么代表当前字符为四字节字符,占用4个字节的空间。11110之后的所有部分(3个位)加上后两个字节的除10外的部分(18个位)代表在Unicode中的序号。且第二、第三、第四个字节以10开头

如果B以10开头,则B为一个多字节字符中的其中一个字节(非ASCII字符)

UTF-16每个字符都直接使用两个字节存储。当然,当存储内容过多,每个编码单元 会包含更多的字节。

所以,对于同样的一段二进制代码,不同的编码方式,解析得到的含义是完全不同的,简单的认为,解析得到的字符串是不同的。

同样,同一个字符串,按照不同的编码方式,格式化得到的一段二进制代码也是完全不同的

Base64字节编码

产生原因:base64模块将一串字节码(二进制流)以6个bit为一组进行编码,(因为6个比特表示的字符在ascii码中是可见字符),所以base64的存在是将所有的字符(可见不可见)转化成可见字符。这样做的原因是避免传输过程中因为不可见字符的存在而容易出错。

编码方式:由于是6个bit为一组,而正常编码是8bit为一组,因此3个字节的字符可以编码出4个字符,这4个字符都可见。

因为是将字节码编码,所以base64.b64encode()只能处理字节流。

注意:base64编码,是针对字节,将字节进行转换,变成一种新的可见字符。

base64编码可以将二进制文件(如图片,音频等)编码成文本文件进行传输,到目的地后,再解码成二进制文件,这样有助于加快传输(因为不同类型的数据再网络上以不同的方式传送,耗时)。

概念区分

说起Base64编码可能有些奇怪,因为大多数的编码都是由字符转化成二进制的过程,而从二进制转成字符的过程称为解码。而Base64的概念就恰好反了,由二进制转到字符称为编码,由字符到二进制称为解码

为什么命名为base64呢?

因为6位(bit)的二进制数有2的6次方个,也就是二进制数(00000000-00111111)之间的代表0-63的64个二进制数。

字符选用了"A-Z、a-z、0-9、+、/" 64个可打印字符来代表(00000000-00111111)这64个二进制数

Java中使用base64

参考:https://www.cnblogs.com/softidea/p/6934972.html

如果JDK版本>=1.8建议使用 java.util.Base64,JDK1.8中的Base64与之前的版本不完全兼容,需要关注一下


import java.io.UnsupportedEncodingException;
import java.util.Base64;

/**
 * @Author fankunfeng
 * @Date 2019-01-22 14:39:23
 * @Describe Java8之后Util包Base64编码,比apache方式效率更高
 */
public class JavaBase64Util {
    public static final String UTF_8 = "UTF-8";
    public static Base64.Encoder encoder;
    //即为安全的编码方式,替换“+” “/” “-”为“_”
    public static Base64.Encoder urlEncoder;
    public static Base64.Decoder decoder;
    public static Base64.Decoder urlDecoder;

    static {
        encoder = Base64.getEncoder();
        urlEncoder = Base64.getUrlEncoder();
        decoder = Base64.getDecoder();
        urlDecoder = Base64.getUrlDecoder();
    }
    //encode
    public static byte[] encode(byte[] bytes) {
        return encoder.encode(bytes);
    }

    public static String encode(String string) {
        byte[] encode = encode(string.getBytes());
        try {
            return new String(encode, UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String encode2String(byte[] bytes) {
        return encoder.encodeToString(bytes);
    }
    public static byte[] encode2Byte(String string) {
        return encode(string.getBytes());
    }


	//decode
    public static byte[] decode(byte[] bytes) {
        return decoder.decode(bytes);
    }
    public static byte[] decode2Byte(String string) {
        return decoder.decode(string.getBytes());
    }
    public static String decode2String(byte[] bytes) {
        try {
            return new String(decoder.decode(bytes),UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String decode(String string) {
        byte[] decode = decode(string.getBytes());
        try {
            return new String(decode, UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
	
	
	
	
	
	
	
	

    //urlEncoder
    public static byte[] urlEncode(byte[] bytes) {
        return urlEncoder.encode(bytes);
    }
    public static String urlEncode(String string) {
        byte[] encode = urlEncode(string.getBytes());
        try {
            return new String(encode, UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String urlEncode2String(byte[] bytes) {
        return urlEncoder.encodeToString(bytes);
    }
    public static byte[] urlEncode2Byte(String string) {
        return urlEncode(string.getBytes());
    }
	
	
	
	
    //urlDecode
    public static byte[] urlDecode(byte[] bytes) {
        return urlDecoder.decode(bytes);
    }
    public static byte[] urlDecode2Byte(String string) {
        return urlDecode(string.getBytes());
    }
    public static String urlDecode2String(byte[] bytes) {
        try {
            return new String(urlDecode(bytes),UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String urlDecode(String string) {
        byte[] decode = urlDecode(string.getBytes());
        try {
            return new String(decode, UTF_8);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Base64多种实现的区别

JDK中Base64类有提供了三个encoder方法,分别是getEncoder,getUrlEncoder和getMimeEncoder:

public static Encoder getEncoder() {
     return Encoder.RFC4648;
}

public static Encoder getUrlEncoder() {
     return Encoder.RFC4648_URLSAFE;
}

public static Encoder getMimeEncoder() {
    return Encoder.RFC2045;
}

同样的,它也提供了三个对应的decoder,分别是getDecoder,getUrlDecoder,getMimeDecoder:

public static Decoder getDecoder() {
     return Decoder.RFC4648;
}

public static Decoder getUrlDecoder() {
     return Decoder.RFC4648_URLSAFE;
}

public static Decoder getMimeDecoder() {
     return Decoder.RFC2045;
}

从代码中可以看出,这三种编码分别对应的是RFC4648,RFC4648_URLSAFE和RFC2045。

这三种都属于base64编码的变体,我们看下他们有什么区别:

编码名称 编码字符 编码字符 编码字符
第62位 第63位 补全符
RFC 2045: Base64 transfer encoding for MIME + / = mandatory
RFC 4648: base64 (standard) + / = optional
RFC 4648: base64url (URL- and filename-safe standard) - _ = optional

可以看到base64和Base64url的区别是第62位和第63位的编码字符不一样,而base64 for MIME跟base64的区别是补全符是否是强制的。

另外,对于Basic和base64url来说,不会添加line separator字符,而base64 for MIME在一行超出76字符之后,会添加’\r’ 和 '\n’作为line separator。

最后,如果在解码的过程中,发现有不存于Base64映射表中的字符的处理方式也不一样,base64和Base64url会直接拒绝,而base64 for MIME则会忽略。

base64和Base64url的区别可以通过下面两个方法来看出:

    private 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', '-', '_'
    };

而对MIME来说,定义了一个一行的最大字符个数,和换行符:

    private static final int MIMELINEMAX = 76;
    private static final byte[] CRLF = new byte[] {'\r', '\n'};

Base64的高级用法

一般情况下我们用Base64进行编码的对象长度是固定的,我们只需要将输入对象转换成为byte数组即可调用encode或者decode的方法。

但是在某些情况下我们需要对流数据进行转换,这时候就可以用到Base64中提供的两个对Stream进行wrap的方法:

    public OutputStream wrap(OutputStream os) {
        Objects.requireNonNull(os);
        return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
                                   newline, linemax, doPadding);
    }
    public InputStream wrap(InputStream is) {
        Objects.requireNonNull(is);
        return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
    }

这两个方法分别对应于encoder和decoder。


评论