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。