zoukankan      html  css  js  c++  java
  • 二进制学习

    二进制是计算机的基础,计算机只识别二进制数据,其基础运算是采用2进制。编程语言写好的程序经过编译后变成计算机能识别的2进制数据,人不可能直接写2进制数据,其中间需要通过编程语言进行协调,所以编程语言就是连接人类和计算机之间的桥梁,下面补充学习二进制基础知识。

    二进制

    (1)计算机内部只有2进制数据,别的一概没有。Java编程语言,利用算法支持10进制,使用户感受上可以使用10进制,比如System.out.println(50)运行输出了50,其实其底层调用了Integer.toString()方法进行了转换,将2进制110010转换成10进制50。底层算法是如下方法:

    (2)Java底层是有方法实现2进制数据和10进制数据的相互转换,具体如下:

       将10进制字符串转换为2进制int ---->Integer.parseInt()

       将2进制int转换为10进制的字符串 ---->Integer.toString()

     了解2进制规则时,需要配套了解16进制规则,因为两者可以相互转换,4位2进制可以简写为一个16进制数,可以通过排列组合就可以理解了。

    (3)2进制规则:

     ①逢二进一的计数规则,可以参考只有2个算珠的算盘,移动算珠到上面代表0位,移动算珠下来代表1位

     ②权:64,32,16,8,4,2,1 (1的2倍数)

     ③基数:2

     ④数字:01

    (4)16进制规则:

     ①逢十六进一的计数规则

     ②权:256,16,1(1的16倍数)

     ③基数:16

     ④数字:0123456789abcdef

    二进制补码

    计算机用来处理(有符号数)负数问题的算法

    补码算法:位数补码

    ①总的位数是4位数

    ②如果计算结果超过4位数,自动溢出舍弃

    先来看看只有4位的2进制数,发现范围是从0000~1111,代表10进制的0~15,如果1111加1,会变成10000,超过4位,最高位1自动溢出舍弃,变成0000,发现又回到起点,所以如果只是4位的2进制数字,采用补码规则,最多只能表示16个数,现在前人根据这样,将2进制高位为0的分成1半,2进制高位为1的分成1半,前者用来表示正数,代表0~7,后者用来表示负数,代表-1~-8。具体参考如下图片:

    其中0~7的对应的2进制数为0000~0111,其中-1~-8的对应2进制数为1111~1000,其实计算机原本只有正数,-1~-8的这几个,在计算机底层还是正数,通过算法Integer.toString(),变成负数展示给人看,让人感觉好像有负数,上面通过4位来表示一个数,取值范围就是-2^3~2^3-1 ,这些数是完全不能满足生活需求的,因此就扩大了位数,用来展现更多的数字,就有了如下几种数据类型:

    ①byte,通过8位来表示一个数,取值范围为-2^7~2^7-1,取值范围-128~127,单位为1个byte,即一个字节

    ②short,通过16位来表示一个数,取值范围为-2^15~2^15-1,取值范围-32768~32767,单位为2个字节,即2byte

    ③带符号int,通过32位来表示一个数,取值范围为-2^32~2^31-1,取值范围-2147483648~2147483647,单位为4个字节,即4byte

    ④无符号int,也是32位,取值范围0~2^32-1

    ⑤long,通过64位来表示一个数,取值范围-2^63~2^63-1,取值范围-9223372036854774808~9223372036854774807,单位为8个字节,即8byte

    以下为浮点型:
    (1)float,4byle,32位
    (2)double,8byte,64位
    字符型:
      (1) char,2个字节,16位
    布尔型:
    (1)boolean,可能就是一个bit,也可能是一个byte

    互补对称公式:-n=~n+1,意思就是一个数对应的另外一个符号对称数,等于2进制位数取反后加1。可以参考上图圆盘就一目了然。

    @Test
        public void testBinary8() {
            /**
             * 测试互补对称公式 -n=~n+1
             */
            System.out.println(~8+1);//-8
            System.out.println(Integer.toBinaryString(~8+1));//11111111111111111111111111111000
            System.out.println(~8);//-9
            System.out.println(Integer.toBinaryString(~8));//11111111111111111111111111110111
            System.out.println(~-8);//7
            System.out.println(Integer.toBinaryString(~-8));//111
            System.out.println(~-8+1);//8
            System.out.println(Integer.toBinaryString(~-8+1));//1000
        } 

    二进制运算符

    ① ~ 取反
    ② & 与运算 主要用于截取
    0&0=0
    0&1=0
    1&0=0
    1&1=1
    ③ | 或运算 主要用于合并
     0|0=0
     0|1=1
     1|0=1
     1|1=1
    ④ >>> 逻辑右移动运算
     将二进制数值总体往右边移动一定位数,低位溢出不要,高位不够用0补齐
     比如 n=00000000 00000000 00000000 11010001
     m=n>>>1运算后
     变成 (0)00000000 00000000 00000000 1101000(1)
     整体往右边移动了1位,低位的1去掉,高位补上0,变成如下形式:
     000000000 00000000 00000000 1101000
    ⑤ >> 数学右移动运算
    ⑥ << 数学左移动运算

    编码方案

    字符是16位的,而流(文件/互联网)按照byte(8位)进行处理,将文字进行传输必须拆分为byte,这个拆分方法,成为文字的编码。把文字拆开,变成8位8位的格式,即编码,反过来叫做解码。
    编码方案学习:
    首先需要了解一下几个概念,unicode,ASCII,UTF-8之间到底有啥区别?
    unicode:一个字符对应一个数,就是将全世界所有知道的字符统计进去,目前到了10万+
    UCS unicode3.2标准 0~65535 java char就是这个UCS
    UTF-8:变长编码,1-4字节,短的1字节,长的4字节,具体有以下几种:
    ① 0~127 1字节编码,这个范围的也叫做ASCII码,主要显示英文和其他西欧语言,格式为 0XXXXXXXX
    ② 128~2048? 2字节编码,格式为110XXXXX 10XXXXXX
    ③ 2048?~65535 3字节编码,中文,韩文日文等都在这个范围内,格式为1110XXXX 10XXXXXX 10XXXXXX
    ④ 65535~10万 4字节编码,格式11110XXX 10XXXXXX 10XXXXXX 10XXXXXX
    比如'中'这个字符,对应的unicode编码为0x4e2d,转化成二进制为0100 1110 0010 1101
    然后需要使用三个字节编码,将二进制格式按顺序填充到格式1110XXXX 10XXXXXX 10XXXXXX中,变成11100100 10111000 10101101,这个就是UTF-8编码,所以将字符的unicode索引添加到UTF-8的字节位置上,叫做编码,然后网络通过8位一个字节一个字节的传输,然后如果将网络上传输的字节一个一个的组合起来,反过来从其中取出对应位置的的数,再和unicode索引对照找到对应的字符,这个过程就做解码。

    编码实例分析 

    接下来用'中'字符进行编码 中字符 unicode编码为:0100 1110 0010 1101 ,在java中会将前面的位补齐,变成如下形式
     00000000 00000000 01001110 00101101
     然后需要编码的格式为:
     1110XXXX 10XXXXXX 10XXXXXX
     一般按照3部分进行编码,按照先后顺序分别是b1 b2 b3
     step 1
     先得到b3,b3需要得到中字符unicode编码的最后6位,需要使用与运算符,截取最后6位,使用如下字符进行与运算 00000000 00000000 00000000 00111111,运算先转换成16进制再进行运算,这个目的就是截取了unicode最后6位然后还需要将b3的前面两个已经规定好的10给添加到得到的结果的前面,使用或运算符 刚得到的与运算结果应该是00000000 00000000 00000000 00101101。使用或运算符,将10添加上,需要还进行或运算的值为 00000000 00000000 00000000 10000000,换算成16进制为0x80。
     所以b3的最后结果为(0x4e2d&0x3f)|0x80
     step2
     然后需要得到b2,b2部分也需要截取6位,但是截取的是unicode字符刚截取部分的前面6位,这个时候如果使用逻辑右移动运算符,可以将需要截取的又放到最后6位,又可以按照刚才的方法进行截取和拼接了,先逻辑右移动6位
     0x4e2d>>>6
     然后再截取最后6位,使用与运算
     (0x4e2d>>>6)&0x3f
     最后再将10拼接到前面,使用或运算符
     ((0x4e2d>>>6)&0x3f)|0x80
     step3
     最后得到b1,可以参考前面的方法, 先逻辑右移动12位,使用逻辑右移动运算
     0x4e2d>>>12
     然后再截取最后4位,使用与运算
     截取4位运算值为00000000 00000000 00000000 00001111
     (0x4e2d>>>12)&0xf
     再将1110拼接上去
     拼接运算值00000000 00000000 00000000 11100000
     ((0x4e2d>>>12)&0xf)|0xe0

    解码实例分析 

    如果是解码,就是把刚才编码得到的三个字节b1 b2 b3,取出编码时存入的部分,再重新按顺序拼接
     b1需要取出最后4位
     b1&0xf
     b2需要取出最后6位
     b2&0x3f
     b3需要取出最后6位
     b3&0x3f
     最后在将取出的部分拼接起来,注意,b1需要左移动12位,b2需要左移动6位,b3不要移动
     ((b1&0xf)<<12)|((b2&0x3f)<<6)|b3&0x3f
     最终将得到'中'字符对应的unicode编码

    以下是编码解码过程代码:

    @Test
        public void testBinary9() {
            /**
             * 打印出中这个字符的unicode索引的二进制形式
             */
            int i='中';
            System.out.println(Integer.toBinaryString(i));//100111000101101 省略了最前面的0
            //其实就是0100 1110 0010 1101,变成16进制为4e2d
        }
        @Test
        public void testBinary10() throws UnsupportedEncodingException {
            /**
             * 将'中'字进行编码,变成UTF-8编码
             * step1 先获得UTF-8的b3部分,使用&和|运算
             */
            int a='中';
            int m=0x3f;
            int n=0x80;
            int b3=((a&m)|n);
            System.out.println(Integer.toBinaryString(a));//100111000101101
            System.out.println(Integer.toBinaryString(a&m));//101101
            System.out.println(Integer.toBinaryString(b3));//10101101
            /**
             * step2 再获取UTF-8的b2部分 使用>>>,&,|运算
             */
            int k=a>>>6;
            System.out.println(Integer.toBinaryString(k));//100111000
            int b2=(k&m)|n;
            System.out.println(Integer.toBinaryString(b2));//10111000
            /**
             * step3 再获取UTF-8的b1部分,使用>>>,&,|运算
             */
            int s=a>>>12;
            int o=0xf;
            int p=0xe0;
            int b1=(s&o)|p;
            System.out.println(Integer.toBinaryString(b1));//11100100
           
            //JDK提供了UTF-8到char的解码
            byte[] bytes= {(byte) b1,(byte) b2,(byte)b3};
            String result=new String(bytes,"utf-8");
            System.out.println(result);//暂时输出乱码
            
            /**
             * 也可以手动解码,将刚才得到的三个字节按照编码方式,将放到对应位置的bit截取后重新拼接
             */
            //截取b3最后6位
            int c1=b3&0x3f;
            //截取b2最后6位,并左移动6位
            int c2=(b2&0x3f)<<6;
            //截取b1最后4位,并左移动12位
            int c3=(b1&0xf)<<12;
            //拼接
            int cResult=c3|c2|c1;
            System.out.println((char)(cResult));
            
        }

     移位运算符的数学意义 

     十进制
     移动小数点运算
     123456. 小数点向右移动
     1234560. 小数点向右移动1次,数字*10
     12345600. 小数点向右移动2次,数字*10*10
     如果小数点位置不变,数字向左移动
     123456. 数字向左移动
     1234560. 数字向左移动1次,数组*10
     12345600. 数字向左移动2次,数组*10*10

     二进制依次类推
     移动小数点运算
     000000000 00000000 00000000 00110010. 小数点向右移动
     00000000 00000000 00000000 001100100. 小数点向右移动1次,数字*2
     0000000 00000000 00000000 0011001000. 小数点向右移动2次,数字*2*2
     如果小数点位置不变,数字向左移动
     000000000 00000000 00000000 00110010. 数字向左移动
     00000000 00000000 00000000 001100100. 数字向左移动1次,数组*2
     0000000 00000000 00000000 0011001000. 数字向左移动2次,数组*2*2

     数学右移位运算
     1 相当如将原数据进行除法,结果向小数点方向取整数
     2 >>数学移位:正数高位补0,负数高位补1,有数学意义
     3 >>> 逻辑移位,不管正数负数,高位都是补0,没有数学意义

     n=11111111 11111111 11111111 11110111 使用互补对称公式得出为-9
     n>>1变成-5
     n=111111111 11111111 11111111 1111011 使用互补对称公式得出为-5
     如果n>>>1
     n=011111111 11111111 11111111 1111011 非常大的一个数

     部分经典面试题

    (1)经典面试题:
     int i=0x32;
     System.out.println(i); //3*16^1+2*16^0=50

    (2)经典面试题:
     int n=0xffffffff;
     System.out.println(n);//使用互补对称公式得出-1

    (3)经典面试题:
     正数的溢出为负数,负数的溢出为正数
     答案:错,根据圆盘转圈,只能说不一定,要看溢出多少了,所以都有可能

    (4)经典面试题:
     System.out.println(~8+1);
     答案:-n=~n+1公式,得到-8

    (5)经典面试题:
     如何优化n*8计算?
     答案:n<<3; //左移位比乘法快

    总结:二进制部分为计算机基础知识,有必要补充学习,加深对基础知识的理解。

    参考博客;

    (1)https://www.jianshu.com/p/53fbd4d91a31

  • 相关阅读:
    CF 461B Appleman and Tree
    POJ 1821 Fence
    NOIP 2012 开车旅行
    CF 494B Obsessive String
    BZOJ2337 XOR和路径
    CF 24D Broken robot
    POJ 1952 BUY LOW, BUY LOWER
    SPOJ NAPTIME Naptime
    POJ 3585
    CF 453B Little Pony and Harmony Chest
  • 原文地址:https://www.cnblogs.com/youngchaolin/p/10463887.html
Copyright © 2011-2022 走看看