编码和解码

目录

1.数据不一致?

2.编码解码

3.字符编码

3.1 字符集和字符编码编码

3.2 ASCII

3.3 GB2312

3.3 GBK

3.4 Unicode

3.4.1. UTF-32

3.4.2. UTF-16

3.4.3. UTF-8

3.4.5乱码原因

4.base64编码

4.1Base64介绍

4.2Base64原理

4.3使用

5.url编码

5.1URL编码介绍

5.2URL编码规则

5.3使用


1.数据不一致?

如下代码所示,先将byte数组转String,然后将String再转回byte数组,发现结果和原始值不一致

public static void main(String[] args) {
        byte[] bytes = {12, -56 - 18, -121, -119, 12};
        System.out.println(Arrays.toString(bytes));

        String str = new String(bytes);
        System.out.println(str);
        System.out.println(Arrays.toString(str.getBytes()));
    }

上述程序结果如下

[12, -74, -121, -119, 12]
���
[12, -17, -65, -67, -17, -65, -67, -17, -65, -67, 12]

要弄清楚为何不一样的原因,还需要从编码和解码说起

具体原因可看3.4.5

2.编码解码

编码是信息从一种形式或格式转换为另一种形式的过程,也称为计算机编程语言代码,简称编码。用预先规定的方法将文字、数字或其它对象编成数码,或将信息、数据转换成规定的电脉冲信号。编码在电子计算机电视、遥控和通讯等方面广泛使用。编码是信息从一种形式或格式转换为另一种形式的过程。解码,是编码的逆过程

 

3.字符编码

3.1 字符集和字符编码编码

字符集是多个字符的集合,可以理解为二进制数,与字符的映射关系,唯一的一个编号就代表一个字

字符编码是计算机中对字符的表示方式
字符集与字符编码区别:

  • 字符集是书写系统字母与符号的集合
  • 字符编码则是将字符映射为一特定的字节或字节序列,是一种规则

字符集:ASCII、GBK,Big5,Unicode 等

字符编码:EUC-CN,Big5,utf8,utf16,utf32 等

ascii和unicode字符集 及 utf-8、utf-16等

3.2 ASCII

ASCII (American Standard Code for Information Interchange):美国信息交换标准代码是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准 ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符

标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符 [1]  

  • 0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符)
  • 在英语中,用128个符号编码便可以表示所有,但是用来表示其他语言,128个符号是不够的,这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号
  • 不管怎样,所有这些编码方式中,0--127表示的符号是一样的,不一样的只是128--255的这一段
  • ASCII中一个字节对应一个字符

常见ASCII码的大小规则:数字< 大写字母 < 小写字母。

  1. 数字比字母要小。如 “7”<“F”;
  2. 数字0比数字9要小,并按0到9顺序递增。如 “3”<“8” ;
  3. 字母A比字母Z要小,并按A到Z顺序递增。如“A”<“Z” ;
  4. 同个字母的大写字母比小写字母要小32。如“A”<“a” 。
  5. 几个常见字母的ASCII码大小: “A”为65;“a”为97;“0”为 48

3.3 GB2312

GB2312编码适用于汉字处理、汉字通信等系统之间的信息交换,通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312

基本集共收入汉字6763个和非汉字图形字符682个。整个字符集分成94个区,每区有94个位。每个区位上只有一个字符,因此可用所在的区和位来对汉字进行编码,称为区位码

GB2312标准把ASCII码表127号之后的扩展字符集去掉,并规定,小于127(0x7F)的编码按照ASCII标准进行解码,当出现连续两个大于127(0x7F)的编码时,这两个连续的大于0x7F的编码表示一个汉字,第一二个字节都是用0xA1~0xFE进行编码。其中,ASCII码中原有的数字字符、英文字符、标点等称为半角字符,大于0x7F的相应字符编码称为全角字符

GB 2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码

  • 01-09区为特殊符号
  • 16-55区为一级汉字,按拼音排序。
  • 56-87区为二级汉字,按部首/笔画排序。
  • 10-15区及88-94区则未有编码。
  • 举例来说,“啊”字是GB2312之中的第一个汉字,它的区位码就是1601

在使用GB2312的程序中,通常采用EUC储存方法,以便兼容于ASCII。浏览器编码表上的“GB2312”,通常都是指“EUC-CN”表示法

每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”(也称“区字节)”,第二个字节称为“低位字节”(也称“位字节”)

“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上 0xA0)。 由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0-0xF7,“低位字节”的范围是0xA1-0xFE,占用的码位是 72*94=6768。其中有5个空位是D7FA-D7FE

例如“啊”字在大多数程序中,会以两个字节,0xB0(第一个字节) 0xA1(第二个字节)储存。区位码=区字节+位字节(与区位码对比:0xB0=0xA0+16,0xA1=0xA0+1)

  • ASCII的字符完整地包含在GB2312里,编码不变,仍然是以0开头,用一个字节来表示一个字符;对于ASCII没有的字符,就用1开头来区分,用两个字节合起来表示一个字符
  • 这样,在解码的时候,遇到字节是以0开头的,就知道这一个字节就表示了一个字符;遇到字节是以1开头的,就知道要加上下一个字节合起来表示一个字符。这样就在GB2312中既把ASCII的字符包含了进来,又能将它们区分出来,能达到兼容的效果了

代码验证:

public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println(Arrays.toString("A".getBytes("GBK")));
        System.out.println(Arrays.toString("啊".getBytes("GBK")));
        System.out.println(Arrays.toString("啊".getBytes("GB2312")));
        System.out.println(Arrays.toString("你好啊".getBytes("GB2312")));
    }

结果如下:

[65]
[-80, -95]
[-80, -95]
[-60, -29, -70, -61, -80, -95]

3.3 GBK

GBK编码在GB2312的基础上又增加了14240个汉字、生僻字和符号。按照GB2312的编码方式,两个字节已经不够用了,这时,GBK编码制定了新的标准:只要出现一个大于0x7F的字节,那么这个字节和它后面一个字节共两个字节就表示一个汉字(GB2312规定两个字节都大于0x7F才表示一个汉字),这样做的好处就是,GBK编码兼容了ASCII编码和GB2312编码。

GBK解码规则:当使用GB2312编码标准时,给定一串字符编码,按照字节进行检测,首先检测每个字节的大小,如果字节值小于0x7F,就用ASCII标准解码,如果遇到一个大于0x7F的字节,就把该字节和它后面一个字节连在一起用GBK标准进行解码,然后从第三个字节开始继续遍历检测

GBK和GB2312异同:

  • GB2312和GBK是采用单双字节变长编码,英文使用单字节编码,完全兼容ASCII字符编码,中文部分采用双字节编码
  • GB2312为GBK的子集
  • 在进行双字节编码时,GB2312“高位字节”使用了0xA1-0xF7(把01-87区的区号加上0xA0),“低位字节”使用了0xA1-0xFE(把01-94加上 0xA0),GBK双字节范围为8140-FEFE(首字节在 81-FE 之间,尾字节在 40-FE 之间)
public static void main(String[] args) throws UnsupportedEncodingException {
        System.out.println(Arrays.toString("A".getBytes("GBK")));
        System.out.println(Arrays.toString("啊".getBytes("GBK")));
        System.out.println(Arrays.toString("啊".getBytes("GBK")));
        System.out.println(Arrays.toString("你好啊".getBytes("GBK")));
        // 编码有误 ,"癡" 不在 GB2312 字符集
        System.out.println(Arrays.toString("癡".getBytes("GB2312")));
        System.out.println(Arrays.toString("癡".getBytes("GBK")));
    }

结果为

[65]
[-80, -95]
[-80, -95]
[-60, -29, -70, -61, -80, -95]
[63]
[-80, 86]

3.4 Unicode

每个国家和地区都有一套自己的文字,不同的文字系统就要使用不同的编码标准,这就出现这样一个问题,同一个二进制编码在不同的编码标准中可能代表了不同的字符,比如0xB0A1,在GBK编码标准中为“啊”,而在Big5编码标准中就不是这个字了。这样,各个编码标准之间的不兼容就导致使用起来非常不方便。国际标准化组织ISO,将全球所有的语言所使用的字母、符号、文字进行统一编号,每个字符指定唯一一个标号与之对应(ASCII码编号不变),字符的编号从0x000000~0x10FFFF,该编号集称为Universal Multiple-Octet coded Character Set,简称UCS,一般也叫做Unicode。Unicode字符集仅仅是对所有字符进行了编号,并没有指定这些编号的编码规则,所以,后来才出现了各种Unicode的编码规则Unicode Transformation Format,典型的Unicode编码规则如UTF-8,UTF-16,UTF-32等

统一码(Unicode),也叫万国码、单一码,是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求

3.4.1. UTF-32


Unicode Transformation Format 32,用32位(4字节)对Unicode字符集进行编码。编码时,Unicode字符集中的每一个字符都用4字节表示,直接把字符对应的Unicode编号转换为二进制数进行存储。而正因为UTF-32用4字节为每个字符编码,所以,UTF-32不兼容ASCII编码,使用ASCII编码标准写的程序,通过UTF-32编码方式打开会显示乱码。

解码时,直接按四个字节检测,编码与Unicode标号一一对应。

字符:A    ASCII编码:0x41    Unicode编号:0x0000 0041    UTF-32编码:0x0000 0041
字符:啊    GBK编码:0xB0A1    Unicode编号:0x0000 554A    UTF-32编码:0x0000 554A


3.4.2. UTF-16


Unicode Transformation Format 16,用16位(2字节)或32位(4字节)对Unicode字符集进行编码。对Unicode字符编号在0~65535的字符使用2字节编码,将每个字符的编号直接转换为2字节的二进制数0x0000~0xFFFF。而Unicode字符集在0xD800~0xDBFF区间内的编号不表示任何字符,UTF-16用这段编号与Unicode字符集中大于0xFFFF的字符编号进行映射,得到扩展的4字节编码。UTF-16也不兼容ASCII编码。

UTF-16解码时,按两个字节去检测,如果这两个字节都不在0xD800~0xDFFF之间,就说明是双字节编码的字符,使用双字节解码;如果这两个字节在0xD800~0xDFFF之间,说明是4字节编码的字符,以4字节解码。

3.4.3. UTF-8

UTF-8(8位元,Universal Character Set/Unicode Transformation Format)是针对Unicode的一种可变长度字符编码。它可以用来表示Unicode标准中的任何字符,而且其编码中的第一个字节仍与ASCII相容,使得原来处理ASCII字符的软件无须或只进行少部分修改后,便可继续使用。因此,它逐渐成为电子邮件网页及其他存储或传送文字的应用中,优先采用的编码


Unicode Transformation Format 8,用1,2,3,4个字节对Unicode字符集进行编码,每个字符根据自己的编号范围进行相应编码。它的编码规则是这样的:

对于UTF-8单字节的编码,该字节最高位设为0,剩余位填入字符的Unicode编号,对于Unicode编号在0x00000000~0x0000007F的字符,UTF-8编码只要一个字节,兼容ASCII编码。对于N字节的编码,第一字节最高位开始,前N位置为1,第N+1位设0,剩余字节最高位设为10,这N个字节的其余空位填充该字符的Unicode编号,高位补0。具体可见下表:

UTF8, 16进制编码表

UTF8-1

0x00-0x7F

UTF8-2

0xC2-0xDF 0x80-0xBF

UTF8-3

0xE0 0xA0-0xBF 0x80-0xBF

0xE1-0xEC 0x80-0xBF 0x80-0xBF

0xED 0x80-0x9F 0x80-0xBF

0xEE-0xEF 0x80-0xBF 0x80-0xBF

UTF8-4

0xF0 0x90-0xBF 0x80-0xBF 0x80-0xBF

0xF1-0xF3 0x80-0xBF 0x80-0xBF 0x80-0xBF

0xF4 0x80-0x8F 0x80-0xBF 0x80-0xBF


解码时,看第一个字节

0开头:单字节解码;

110开头:双字节解码;

1110开头:三字节解码;

11110开头:四字节解码;

UTF-8编码的时候,汉字一般是占三个字节的

3.4.5乱码原因

综上所述,不同字符编码/解码规则不一样,字节范围也不一样,如果随便指定byte数组进行编码然后解码,则最终结果不一定如预期

[12, -74, -121, -119, 12]中-74,-121,-119连在一起不符合utf-8规范,因而将其置为了默认值-17, -65, -67。如果需要传输字节数组,业界一般采用base64编码后进行传输,或者可直接用ByteArrayOutputStream写出数据

4.base64编码

4.1Base64介绍

Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。可查看RFC2045~RFC2049,上面有MIME的详细规范。

Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。采用Base64编码具有不可读性,需要解码后才能阅读。

Base64由于以上优点被广泛应用于计算机的各个领域,然而由于输出内容中包括两个以上“符号类”字符(+, /, =),不同的应用场景又分别研制了Base64的各种“变种”。为统一和规范化Base64的输出,Base62x被视为无符号化的改进版本

base64的初衷,是为了满足电子邮件中不能直接使用非ASCII码字符的规定.现在最常用的应用场景就是在http协议上传输较长的文本信息。它是将用户输入的二进制数据,打包成一种安全格式,将其作为http首部字段的值发送出去,而无须担心其中包含会破坏HTTP分析程序的冒号、换行符或二进制值。

除此以外,也有其他重要的意义:

a)所有的二进制文件,都可以因此转化为可打印的文本编码,使用文本软件进行编辑;

b)能够对明文文本进行简单的处理,只能防肉眼,就是一个君子加密方式。

最重要的特点

1.便于网络传输。

2.不可见性

4.2Base64原理

Base64要求把每三个8Bit的字节转换为四个6Bit的字节(3*8 = 4*6 = 24),然后把6Bit再添两位高位0,组成四个8Bit的字节,也就是说,转换后的字符串理论上将要比原来的长1/3。

关于这个编码的规则:

  1. 把3个字节变成4个字节
  2. 每76个字符加一个换行符
  3. 最后的结束符也要处理
  4. 其实还有一个垫字的"=",实际上是65个字符

Table 1: The Base64 Alphabet

索引

对应字符

索引

对应字符

索引

对应字符

索引

对应字符

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

15

P

32

g

49

x

16

Q

33

h

50

y

例子

举一个具体的实例,演示英语单词Man如何转成Base64编码。

Text contentMan
ASCII7797110
Bit pattern010011010110000101101110
Index1922546
Base64-EncodedTWFu

第一步,"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== 

4.3使用

private static Base64.Encoder BASE64_ENCODE = Base64.getEncoder();
    private static Base64.Decoder BASE64_DECODE = Base64.getDecoder();

    public static void main(String[] args) {
        // 转成字符串
        byte[] manByte = "Man".getBytes();
        byte[] maByte = "Ma".getBytes();
        byte[] mByte = "M".getBytes();
        System.out.println(Arrays.toString(manByte));
        System.out.println(Arrays.toString(maByte));
        System.out.println(Arrays.toString(mByte));

        // 编码
        String manStr = BASE64_ENCODE.encodeToString(manByte);
        String maStr = BASE64_ENCODE.encodeToString(maByte);
        String mStr = BASE64_ENCODE.encodeToString(mByte);
        System.out.println(manStr);
        System.out.println(maStr);
        System.out.println(mStr);

        // 解码
        System.out.println(Arrays.toString(BASE64_DECODE.decode(manStr)));
        System.out.println(Arrays.toString(BASE64_DECODE.decode(maStr)));
        System.out.println(Arrays.toString(BASE64_DECODE.decode(mStr)));
    }

结果

[77, 97, 110]
[77, 97]
[77]
TWFu
TWE=
TQ==
[77, 97, 110]
[77, 97]
[77]

5.url编码

5.1URL编码介绍

url编码是一种浏览器用来打包表单输入的格式。浏览器从表单中获取所有的name和其中的值 ,将它们以name/value参数编码(移去那些不能传送的字符,将数据排行等等)作为URL的一部分或者分离地发给服务器。不管哪种情况,在服务器端的表单输入格式样子象这样:

theName=Ichabod+Crane&gender=male&status=missing& ;headless=yes

使用 URL 在整个因特网中定位资源,就需要满足在不同协议间都相互兼容的能力。因此需要一种统一的 URL 命名规范,以避免 URL 在传输过程发生丢失数据的问题。比如一些特殊的字符,如果部分协议不识别,那么该部分内容就会丢失。为了避免这种情况,URL 设计了一种编码规则

5.2URL编码规则

URL编码遵循下列规则: 每对name/value由&;符分开;每对来自表单的name/value由=符分开。如果用户没有输入值给这个name,那么这个name还是出现,只是无值。任何特殊的字符(就是那些不是简单的七位ASCII,如汉字)将以百分符%用十六进制编码,当然也包括象 =,&;,和 % 这些特殊的字符。其实url编码就是一个字符ascii码的十六进制。不过稍微有些变动,需要在前面加上“%”。比如“\”,它的ascii码是92,92的十六进制是5c,所以“\”的url编码就是%5c

5.3使用

public static void main(String[] args) {
        String encode = URLEncoder.encode("theName=Ichabod+Crane&gender=male&status=missing& ;headless=yes");
        System.out.println(encode);
        System.out.println(URLDecoder.decode("theName%3DIchabod%2BCrane%26gender%3Dmale%26status%3Dmissing%26%20%3Bheadless%3Dyes%0A"));
    }

结果

theName%3DIchabod%2BCrane%26gender%3Dmale%26status%3Dmissing%26+%3Bheadless%3Dyes
theName=Ichabod+Crane&gender=male&status=missing& ;headless=yes

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值