字节 字符 字符集 编码解码 进制

概念澄清

字节

是存储数据的基本单元(必须至少用1个字节的空间来表示一个内容),所以它也是计算存储容量的一种计量单位。

我们知道计算机只能识别1和0组成的二进制位。一个数就是1位(bit),为了方便计算,我们规定8位就是一个字节

例如 :00001111 这个8位二进制数就占了一个字节的存储容量。

 

字符


1 任何一个文字或符号都是一个字符,但所占字节不一定

2 同一个字符不同的编码导致一个字符所占的内存不同。

例如:标点符号+是一个字符,汉字我们是两个字符,在GBK编码中一个汉字占2个字节,在UTF-8编码中一个汉字占3个字节。

 

字符集

多个字符的集合,就是字符集。一般来说字符集都是和特定编码方式对应的,不同 编码方式 关联了不同的字符集。

 

字符集和字符编码的关系 :


字符集是书写系统字母与符号的集合,而字符编码则是将字符映射为一特定的字节或字节序列,是一种规则。通常特定的字符集采用特定的编码方式(即一种字符集对应一种字符编码(例如:ASCII、IOS-8859-1、GB2312、GBK,都是即表示了字符集又表示了对应的字符编码,但Unicode不是,它采用现代的模型)),因此基本上可以将两者视为同义词。

通用字符集UCS(Universal Character Set) 对应两种编码:对每一个字符采用四个8比特字节编码的称为UCS-4,对每一个字符采用两个8比特字节编码的称为UCS-2。

UNICODE字符集 有多个编码方式,分别是UTF-8,UTF-16,UTF-32编码。

 

编码 解码

编码:一串字符,我们可以选用任意类型的编码方式转换成一串二进制数,这个过程就是编码,我们也可以称之为加密过程,无论使用哪一种编码方式进行编码,最终都是产生计算机可识别的二进制数,但如果编码规范的字库表不包含目标字符,则无法在字符集中找到对应的二进制数。这将导致不可逆的乱码!例如:像ISO-8859-1的字库表中不包含中文,因此哪怕将中文字符使用ISO-8859-1进行编码,再使用ISO-8859-1进行解码,也无法显示出正确的中文字符。

解码: 一串二进制数,使用一种编码方式,转换成字符,这个过程我们称之为解码。就像解开密码一样,程序员可以选用任意的编码方式进行解码,但往往只有一种编码方式可以解开密码显示出正确的文字,而使用错误的编码方式,产生其他不合理的字符,这就是我们通常说的————乱码!编码
 

编码规范(编码方式)

出现原因:随着时代的发展,程序员们希望在计算机中显示字符,但计算机只能识别0和1的二进制数,于是就有了编码规范。

编码规范:  把特定的一组字符,用不同的二进制数分别在计算机中表示出来,这样电脑就可以根据二进制数来显示其对应的字符

 

 

常见编码规范
 

1.ASCII码    (只编码了128个字符)


ASCII码,是最早产生的编码规范,一共包含00000000~01111111共128个字符,可以表示阿拉伯数字和大小写英文字母,以及一些简单的符号。后被称为(American Standard Code for Information Interchange,美国信息交换标准代码)。

因为只有128个字符,所以只用了1个字节的存储空间,最高位为0,实际只使用了7位。

 

2.GBK /GK2312/GB18030/BIG5
GBK全称《汉字内码扩展规范》,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字

GBK字符集字符较多,所以每个字符占2个字节,不论中文英文都是2个字节 ,相当于字符集最多支持 65536个字符。

GB2312,GBK,GB18030,是兼容的,包含的字符个数,以此更多,可以写成:GB2312 < GBK < GB18030

 

面的GB2312,GBK,GB18030,都是针对于中国大陆的,简体中文;

对于台湾等地区,都是用的是繁体中文,其和我们的编码不同;

繁体中文一般都是用的是BIG5,中文一般称为大五码,其对应者微软的CP950

 

3.ISO-8859-1
ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号

ISO-8859-1 共计256个字符,占用1个字节空间存储。

 

ISO8859,其下分了很多分支,从1到15,分别叫做ISO8859-1,ISO8859-2,……,ISO8859-15

可以说:ISO8859,包含了整个欧洲所用的各种字符;

其中,最最常用的,就是:ISO8859-1,又常被称为Latin-1,表示的就是西欧字符;

 

4.Unicode字符集


出现原因: 从以上几种编码规范可以看出,各种编码规范互不兼容,且只能表示自己需要的字符,于是,国际标准化组织(ISO)决定制定一套全世界通用的编码规范.

需要注意的是,Unicode 只是一个字符集,在制定的时候并没有考虑编码的问题,每个字符有一个位码

后来Unicode 可以使用的编码有三种,分别是:

UFT-8:一种变长的编码方案,使用 1~6 个字节来存储;
UFT-32:一种固定长度的编码方案,不管字符编号大小,始终使用 4 个字节来存储;
UTF-16:介于 UTF-8 和 UTF-32 之间,使用 2 个或者 4 个字节来存储,长度既固定又可变。

UTF 是 Unicode Transformation Format 的缩写,意思是“Unicode转换格式”,后面的数字表明至少使用多少个比特位(Bit)来存储字符。

 

unicode  与中文互转 :http://www.msxindl.com/tools/unicode16.asp

字符串转化为 UTF-8 ,GBK, BIG5编码 之后的10进制,16进制表示:

http://www.mytju.com/classcode/tools/encode_utf8.asp

4.1 UTF-8编码

UTF-8的编码规则很简单,只有二条: 
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。 
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。 


下表总结了编码规则,字母x表示可用编码的位。 

Unicode位码范围 | UTF-8编码方式 
(十六进制)            | (二进制) 
--------------------+--------------------------------------------- 
0000 0000-0000 007F | 0xxxxxxx 
0000 0080-0000 07FF | 110xxxxx 10xxxxxx 
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx 
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 
下面,还是以汉字“严”为例,演示如何实现UTF-8编码。 

已知“严”的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此“严”的UTF-8编码需要三个字节,即格式是“1110xxxx 10xxxxxx 10xxxxxx”。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是“11100100 10111000 10100101”,转换成十六进制就是E4B8A5。 
对于常用的字符,它的 Unicode 编号范围是 0 ~ FFFF,用 1~3 个字节足以存储,只有及其罕见,或者只有少数地区使用的字符才需要 4~6个字节存储。

4.2 UTF-32
UTF-32 是固定长度的编码,始终占用 4 个字节,足以容纳所有的 Unicode 字符,所以直接存储 Unicode 编号即可,不需要任何编码转换。浪费了空间,提高了效率。

4.3 UTF-16
UFT-16 比较奇葩,它使用 2 个或者 4 个字节来存储。

对于 Unicode 编号范围在 0 ~ FFFF 之间的字符,UTF-16 使用两个字节存储,并且直接存储 Unicode 编号,不用进行编码转换,这跟 UTF-32 非常类似。

对于 Unicode 编号范围在 10000~10FFFF 之间的字符,UTF-16 使用四个字节存储,具体来说就是:将字符编号的所有比特位分成两部分,较高的一些比特位用一个值介于 D800~DBFF 之间的双字节存储,较低的一些比特位(剩下的比特位)用一个值介于 DC00~DFFF 之间的双字节存储。

如果你不理解什么意思,请看下面的表格:

Unicode 编号范围(十六进制)         UTF-16 编码                                             编码后的字节数
0000 0000 ~ 0000 FFFF                  xxxxxxxx xxxxxxxx    xxxxxxxx xxxxxxxx    2
0001 0000---0010 FFFF                   yyyy yyyy yyxx xxxx xxxx    110110yy yyyyyyyy 110111xx xxxxxxxx    4

 

位于 D800~0xDFFF 之间的 Unicode 编码是特别为四字节的 UTF-16 编码预留的,所以不应该在这个范围内指定任何字符。如果你真的去查看 Unicode 字符集,会发现这个区间内确实没有收录任何字符。

UTF-16 要求在制定 Unicode 字符集时必须考虑到编码问题,所以真正的 Unicode 字符集也不是随意编排字符的。

 

总结
只有 UTF-8 兼容 ASCII,UTF-32 和 UTF-16 都不兼容 ASCII,因为它们没有单字节编码。

如果你希望查看完整的 Unicode 字符集,以及各种编码方式,请猛击:https://unicode-table.com/cn/

 

URL编码

参考 https://blog.csdn.net/xiaoliuliu2050/article/details/103126900

 

1 : urlencode 和urldecode 的 示例讲解:

前端form表单提交,默认是application/x-www-form-urlencoded,因此会对参数进行urlencode

http请求传输参数 编码前后对比

urlencode之前:www.baidu.com?name=zhang san
urlencode之后:www.baidu.com?name=zhang+san

urlencode之前:www.baidu.com?name=zhang+san
urlencode之后:www.baidu.com?name=zhang%2san

由上可见,

空格:url编码后,会用+号替换原来位置的空格

加号(+):url编码后,会用%2替换原来位置的+

 

问题点: 请求参数值   zhang+san 如果没有进行urlencode ,直接进行请求,

而后台进行了urldecode ,则会把参数值 解析为  zhang san,这样就获取到了错误的值。

 

2 base64 作为一种常用的数据传输格式,也会出现这个问题

Base64加密后的数据通过http传输后,后台接收到的数据会出现空格原因就是

前端没有进行urlencode 直接将 + 传给后台,而后台 urldecode 把+ 变成了空格

 

在线url 编码 https://tool.oschina.net/encode?type=4

 

JAVA 示例

 

1 字符串使用不同编码方式的转化

 

以java语言为例,我们先将一串中文字符串使用UTF-8 编码方式进行编码变成字节数组,然后将字节数组打印出来。


String chinese="汉";
//使用UTF-8编码方式进行编码。
byte[] bs = chinese.getBytes("UTF-8");
for (byte b : bs) {
	System.out.print(b+" ");
}
结果:-26 -79 -119

可以看出,1个汉字变成了3个字节,证明了1个汉字在UTF-8 编码方式下占3个字节。

我们继续,将字节数组,使用UTF-8 编码方式进行解码
//使用UTF-8编码方式进行解码。
String utf8 = new String(bs,"UTF-8");
System.out.println(utf8);

结果:
汉

解码后正确的显示了中文字符汉字。
但如果我们使用GBK进行解码。
结果:姹?

说明使用错误的解码方式就会乱码。



但如果将汉字使用ISO-8859-1进行编码 ,然后解码


String chinese = "我是帅哥";
//使用ISO-8859-1编码方式进行编码。
byte[] bs = chinese.getBytes("ISO-8859-1");
for (byte b : bs) {
	System.out.println(b + " ");
}
//使用ISO-8859-1编码方式进行解码。
String iso = new String(bs, "ISO-8859-1");
System.out.println("\n"+iso);

结果:
63 63 63 63 
????
可以看出,无论是哪个汉字,使用ISO-8859-1编码后,都变成了63。这样一来,计算机彻底无法识别是哪一个字符了。
哪怕再用ISO-8859-1进行解码,也无法正确显示中文字符。


2 java 获取 unicode 位码 ,unicode位码和汉字的比较

 

        //根绝字符串获取unicode码
        char[] chars = "中国".toCharArray();
        for (char aChar : chars) {
            System.err.println("char字符:"+aChar);
            String x = Integer.toHexString(aChar);
            System.err.println(x);
            if (x.length() <= 2) {
                x = "\\u00" + x;
            } else {
                x = "\\u" + x;
            }
            System.err.println("unicode码" + x);
        }



        // unicode 码 和  汉字比较
        String n="\u4e25";
        if(n.equals("严"))
           System.out.println("true");
        else
            System.out.println("false");
        
        //结果:true

 

3 : java 获取字符串 utf8 编码后的16进制字符串,其他编码请替换编码方式

   String text = "齐";

   byte[] b_utf8 = text.getBytes("UTF-8"); //utf-8

   System.out.println(bytesToHex(b_utf8));
   System.out.println(bytes2hex01(b_utf8));
   System.out.println(bytes2hex03(b_utf8));


    public static String bytesToHex(byte[] bytes) {
        StringBuffer sb = new StringBuffer();
        for(int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if(hex.length() < 2){
                sb.append(0);
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    public static String bytes2hex01(byte[] bytes)
    {
        /**
         * 第一个参数的解释,记得一定要设置为1
         *  signum of the number (-1 for negative, 0 for zero, 1 for positive).
         */
        BigInteger bigInteger = new BigInteger(1, bytes);
        return bigInteger.toString(16);
    }

    public static String bytes2hex03(byte[] bytes)
    {
        final String HEX = "0123456789abcdef";
        StringBuilder sb = new StringBuilder(bytes.length * 2);
        for (byte b : bytes)
        {
            // 取出这个字节的高4位,然后与0x0f与运算,得到一个0-15之间的数据,通过HEX.charAt(0-15)即为16进制数
            sb.append(HEX.charAt((b >> 4) & 0x0f));
            // 取出这个字节的低位,与0x0f与运算,得到一个0-15之间的数据,通过HEX.charAt(0-15)即为16进制数
            sb.append(HEX.charAt(b & 0x0f));
        }

        return sb.toString();
    }

 

4 java对 二进制 八进制 十六进制的书写区分

二进制以 0b 开头
八进制以 0 开头
十六进制以 0x 开头

 

 int   a   =   100;  
  我们也可以这样写:  
  int   a   =   0144;   //0144是八进制的100;

 

  int   a   =   0x100F;  
  int   b   =   0x70   +   a;  

 

        System.out.println(Integer.toBinaryString(0b10));
        System.out.println(Integer.toHexString(0b10));
        System.out.println(Integer.toOctalString(0b10));

结果  
10
2
2

 

5 字符串和 二进制串的转化

public class TestMap {
    public static void main(String[] args) {
        
        /***
         * 1.字符串转二进制
         * **/
        String str = "阿达撒";
        char[] strChar=str.toCharArray();
        String result="";
        for(int i=0;i<strChar.length;i++){
            result +=Integer.toBinaryString(strChar[i])+ " ";
        }
        System.out.println(result);
        
        /**
         *2. 二进制转字符串
         * */

        String[] tempStr=result.split(" ");
        char[] tempChar=new char[tempStr.length];
        for(int i=0;i<tempStr.length;i++) {
            tempChar[i]=BinstrToChar(tempStr[i]);
        }
        System.out.println(String.valueOf(tempChar));


    }
    
    //将二进制转换成字符
    public static char BinstrToChar(String binStr){
        int[] temp=BinstrToIntArray(binStr);
        int sum=0;
        for(int i=0; i<temp.length;i++){
            sum +=temp[temp.length-1-i]<<i;
        }
        return (char)sum;
    }
    //将二进制字符串转换成int数组
    public static int[] BinstrToIntArray(String binStr) {
        char[] temp=binStr.toCharArray();
        int[] result=new int[temp.length];
        for(int i=0;i<temp.length;i++) {
            result[i]=temp[i]-48;
        }
        return result;
    }

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值