【计算机基础】原码, 反码, 补码以及二进制运算

7 篇文章 0 订阅
4 篇文章 0 订阅

我的原则:先会用再说,内部慢慢来。
学以致用,根据场景学源码


一、概念

       		7       	-7                        
原码:  0000 0111    1000 0111           
反码:  0000 0111    1111 1000        
补码:  0000 0111    1111 1001           
  1. 正数原码、反码、补码都是一样的。
  2. 原码的第一位是符号位, 0表示非负数+(包括正数 + 0),1表示负数-
  3. (针对负数)反码就是对原码逐位取反(0变成1,1变成0),但是第一位符号位除外
  4. (针对负数)补码就是反码+1
  5. 补码 = 原码取反 + 1

=== 点击查看top目录 ===

二、为什么会有反码?

切记:计算机只会加法
  • 假如只有原码,那么下面运算是错误的。
    1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2

  • 此时有了反码,用反码进行运算
    1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

=== 点击查看top目录 ===

三、为什么会有补码?

  • 用反码计算减法, 结果的真值部分是正确的。
  • 唯一的问题其实就出现在"0"这个特殊的数值上。+0和-0是一样的, 但是0带符号是没有任何意义的。而且会有[0000 0000]原和[1000 0000]原两个编码表示0。
  • 于是补码出现了
3.1 用补码解决 0 的符号以及两个编码的问题

1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原

3.2 用补码相比原码可以多保存一个数字(+0 与 -0 在补码中占1个坑)
  • 8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].
  • 32位int类型, 可以表示范围是: [-2 ^ 31, 2 ^ 31-1]
切记:计算机都是用补码进行运算

=== 点击查看top目录 ===

四、二进制运算

4.1 位运算

位运算符包括: 与(&)、非(~)、或(|)、异或(^)

与(&):当两边操作数的位同时为1时,结果为1,否则为0。如1100&1010=1000   

或(|) :当两边操作数的位有一边为1时,结果为1,否则为0。如1100|1010=1110   

非(~):0 变 1,1 变 0   

异或(^):两边的位不同时,结果为1,否则为0。 如1100^1010=0110

=== 点击查看top目录 ===

4.2 移位运算

<<(左移)、>>(带符号右移)和>>>(无符号右移)。

  • 如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模。如对int型移动33位,实际上只移动了33%32=1位。
  • 右移一位相当于除2,右移n位相当于除以2的n次方。 左移反之
  • 正数的左移与右移,负数的无符号右移,就是相应的补码移位所得,在高位补0即可。
  • 负数的右移,就是补码高位补1,然后按位取反加1即可。
<<(左移): 左移的规则只记住一点:丢弃最高位,0补最低位 
>>(带符号右移)和>>>(无符号右移)。 

 举例1:

                   -100带符号右移4位。

                   -100原码:   10000000    00000000    00000000   01100100

                   -100补码:    保证符号位不变,其余位置取反加1

                                         11111111    11111111    11111111   10011100

                   右移4位   :   在高位补1

                                         11111111    11111111    11111111    11111001

                                 补码形式的移位完成后,结果不是移位后的结果,要根据补码写出原码才是我们所求的结果。其方法如下:

                    保留符号位,然后按位取反

                                         10000000    00000000    00000000     00000110

                    然后加1,即为所求数的原码:

                                                   10000000    00000000    00000000    00000111

                   所有结果为:-7

举例2:

                   
                   -100无符号右移4位。

                   -100原码:   10000000    00000000    00000000   01100100

                   -100补码:    保证符号位不变,其余位置取反加1

                                         11111111    11111111    11111111   10011100

                   无符号右移4位   :   在高位补0

                                         00001111    11111111    11111111    11111001

                  即为所求:268435449

=== 点击查看top目录 ===

4.3 计算题:short a = 128; byte b = (byte) a ; 经过强制类型转换以后,b的值为( )
  • 答案: b = -128
八位 0000 0000:能存储2^8 - 1 = 255,
对于有符号的类型:例如byte型有正负(-128 - 127),那就第一位符号位。(以下两行为补码)
0000 0000 - 0111 1111 即     0  -> 127
1000 0000 - 1111 1111 即 -128 -> -1
(注:无符号位的8位 1000 0000 为128)
补码:1000 0000 ---> 原值: -128 (8位的有符号位,对于128来说,反码和原码没法显示)
补码:1111 1111 --->  原码:1000 0001   ---> 原值: -1 

上述是8位的情况,同理32位的情况是 :
补码:11111111 11111111 11111111 11111111 --->  原码:10000000  00000000 00000000 00000001   ---> 原值: -1 ,不理解就看下面的图。

在这里插入图片描述
=== 点击查看top目录 ===

4.4 用与&符号进行二进制取余

c = a % b
b 必须是2的N次方,
c = a & ( b - 1)

17 % 16 = 1
-->
17 & ( 16 -1 ) = 17 & 15


	0001 0001
&	0000 1111
	0000 0001

余数为 1.

&符号实现取余数原理是什么?
  • 如何对8(111)取余?
除法,除与2的冥可以用无符号右移来表示,比如说除与8,就是右移3位。
9 (1001) 除与 8,右移3位结果为 0001,商为1。(移走了001,这个就是余数,1)
8 (1000) 除与 8,右移3位结果为 0001,商为1。(移走了000,这个就是余数,0)
7 (0111) 除与 8,右移3位结果为 0000,商为0。(移走了1111,这个就是余数,7)
  • 那么求余数也就是要看我们移走了什么,那么& 111…111 即可实现这个目的。而 111…111 就是2的N次方 - 1.

=== 点击查看top目录 ===

五、位运算在JAVA中的实际应用

HashMap.tableSizeFor 方法

位于构造方法中:

    public HashMap(int initialCapacity, float loadFactor) {
  • 目的是为了初始化threshold,求出你的传入容量大小的最小的2的幂,确保能够装下。
  • 也就是不管你的值给多少,求出一个比你给出的数值大的最小二进制数字。

=== 点击查看top目录 ===

5.1 jdk1.7实现

先 * 2,再处理


private void inflateTable(int toSize) {
    //根据toSize计算容量,即大于toSize的最小的2的n次方
    int capacity = roundUpToPowerOf2(toSize);
    ………
}
 
private static int roundUpToPowerOf2(int number) {
    // assert number >= 0 : "number must be non-negative";
    return number >= MAXIMUM_CAPACITY
            ? MAXIMUM_CAPACITY
            : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
}
 
public static int highestOneBit(int i) {
    // HD, Figure 3-1
    i |= (i >>  1);
    i |= (i >>  2);
    i |= (i >>  4);
    i |= (i >>  8);
    i |= (i >> 16);
    return i - (i >>> 1);
}
  • 重点看 highestOneBit 方法
  • 假如传入的 cap = 17 (0001 0001) ,那么会得到 32 (0010 0000)
  • Integer.highestOneBit((number - 1) << 1)
		//tmp = number - 1 = 0001 0000 ,i = tmp << 1 = 0010 0000
		i |= (i >>  1); // tmp = i >>  1 = 0001 0000 , i | tmp = 0011 0000
	    i |= (i >>  2); // tmp = i >>  2 = 0000 1100 , i | tmp = 0011 1100  
	    i |= (i >>  4); // tmp = i >>  4 = 0000 0001 , i | tmp = 0011 1111  
	    i |= (i >>  8); // tmp = i >>  8 = 0000 0000 , i | tmp = 0011 1111
	    i |= (i >> 16); // tmp = i >>  16 = 0000 0000 , i | tmp = 0011 1111
	    return i - (i >>> 1);// tmp = i >>> 1 = 0001 1111 ,i - tmp = 0010 0000 
  • 假如传入的 cap = 01000000 00000000 00000000 00000001 ,那么会得到 10000000 00000000 00000000 00000000
		//tmp = number - 1 = 01000000 00000000 00000000 00000000 ,i = tmp << 1 = 10000000 00000000 00000000 00000000
		i |= (i >>  1); // tmp = i >>  1 = 01000000 00000000 00000000 00000000 , i | tmp = 11000000 00000000 00000000 00000000
	    i |= (i >>  2); // tmp = i >>  2 = 00110000 00000000 00000000 00000000 , i | tmp =  11110000 00000000 00000000 00000000
	    i |= (i >>  4); // tmp = i >>  4 = 00001111 00000000 00000000 00000000 , i | tmp = 11111111 00000000 00000000 00000000 
	    i |= (i >>  8); // tmp = i >>  8 = 00000000 11111111 00000000 00000000 , i | tmp = 11111111 11111111 00000000 00000000
	    i |= (i >> 16); // tmp = i >>  16 = 00000000 00000000 11111111 11111111 , i | tmp = 11111111 11111111 11111111 11111111
	    return i - (i >>> 1);// tmp = i >>> 1 = 01111111 11111111 11111111 11111111 ,i - tmp = 10000000 00000000 00000000 00000000 

=== 点击查看top目录 ===

5.2 jdk1.8 实现
static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
  • 上面的思路就是把全部位数都占满成1,变成11111…1111, 然后最后再 + 1。
  • 假如传入的 cap = 17 (0001 0001),那么会得到 32 (0010 0000)
		int n = cap - 1; // n = 0001 0000
		n |= n >>> 1; // tmp = n >>> 1 = 0000 1000 ,n |= tmp = 0001 1000 (补1,原先n的1都在)
		n |= n >>> 2; // tmp = n >>> 2 = 0000 0110 ,n |= tmp = 0001 1110 (补1,原先n的1都在)
		n |= n >>> 4; // tmp = n >>> 4 = 0000 0001 ,n |= tmp = 0001 1111 (补1,原先n的1都在)
		n |= n >>> 8; // tmp = n >>> 8 = 0000 0000 ,n |= tmp = 0001 1111 (补1,原先n的1都在)
		n |= n >>> 16;// tmp = n >>> 16 = 0000 0000 ,n |= tmp = 0001 1111 (补1,原先n的1都在)
		return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // return 0010 0000 = 32
  • 假如传入的 cap = 01000000 00000000 00000000 00000001 ,那么会得到 10000000 00000000 00000000 00000000
		int n = cap - 1; // n = 01000000 00000000 00000000 00000000
		n |= n >>> 1; // tmp = n >>> 1 = 00100000 00000000 00000000 00000000 ,n |= tmp = 01100000 00000000 00000000 00000000 (1个1复制成2个1)
		n |= n >>> 2; // tmp = n >>> 2 = 00011000 00000000 00000000 00000000 ,n |= tmp = 01111000 00000000 00000000 00000000 (2个1复制成4个1)
		n |= n >>> 4; // tmp = n >>> 4 = 00000111 10000000 00000000 00000000 ,n |= tmp = 01111111 10000000 00000000 00000000 (4个1复制成8个1)
		n |= n >>> 8; // tmp = n >>> 8 = 00000000 01111111 10000000 00000000 ,n |= tmp = 01111111 11111111 10000000 00000000 (8个1复制成16个1)
		n |= n >>> 16;// tmp = n >>> 16 = 00000000 00000000 01111111 11111111 ,n |= tmp = 01111111 11111111 11111111 11111111 (16个1复制满32位)
		return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // return 10000000 00000000 00000000 00000000  = 2 ^ 31
  • 用二进制看
initialCapacity计算出来的 threshold
0000 00000000 0001
0000 00010000 0010
0000 00110000 0100
0001 00000010 0000
  • 用十进制看
initialCapacity计算出来的 threshold
01
12
34
1732

=== 点击查看top目录 ===

5.3 jdk11 实现

原值: -1 —> 补码:11111111 11111111 11111111 11111111

 static final int tableSizeFor(int cap) {
        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
 @HotSpotIntrinsicCandidate
    public static int numberOfLeadingZeros(int i) {
        // HD, Count leading 0's
        if (i <= 0)
            return i == 0 ? 32 : 0;
        int n = 31;
        if (i >= 1 << 16) { n -= 16; i >>>= 16; }
        if (i >= 1 <<  8) { n -=  8; i >>>=  8; }
        if (i >= 1 <<  4) { n -=  4; i >>>=  4; }
        if (i >= 1 <<  2) { n -=  2; i >>>=  2; }
        return n - (i >>> 1);
    }
  • 假如传入的 cap = 17 (0001 0001),那么会得到 32 (0010 0000)
    public static int numberOfLeadingZeros(int i) {
		// i = cap - 1 = 0001 0000(二进制) = 16(十进制)
        if (i <= 0)
            return i == 0 ? 32 : 0;
        int n = 31; // n = 31 = 0001 1111
        if (i >= 1 << 16) { n -= 16; i >>>= 16; } // 每一步都需要判断
        if (i >= 1 <<  8) { n -=  8; i >>>=  8; }
        if (i >= 1 <<  4) { n -=  4; i >>>=  4; } //命中这个, 1 << 4 = 0001 0000, n = n - 4 = 0001 1111 - 0000 0100 = 0001 1011 , i = i >>> 4 = 0000 0001
        if (i >= 1 <<  2) { n -=  2; i >>>=  2; }
        return n - (i >>> 1); // tmp = i >>> 1 = 0000 0000,n - tmp = 0001 1011 = 27
    }

  static final int tableSizeFor(int cap) {
  		// -1 补码:11111111 11111111 11111111 11111111
        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);  // -1 >>> 27 ,右移动高位补0 ,        00000000 00000000 00000000 00011111

        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // 00000000 00000000 00000000 00100000 = 32
    }   
  • 假如传入的 cap = 01000000 00000000 00000000 00000001 ,那么会得到 10000000 00000000 00000000 00000000
    public static int numberOfLeadingZeros(int i) {
		// i = cap - 1 =  01000000 00000000 00000000 00000000
        if (i <= 0)
            return i == 0 ? 32 : 0;
        int n = 31; // n = 31 = 0001 1111
        if (i >= 1 << 16) { n -= 16; i >>>= 16; } // 命中这个, n = n - 16 =  0001 1111 - 0001 0000 = 0000 1111, i = i >>> 16 =  00000000 00000000 01000000 00000000,
        if (i >= 1 <<  8) { n -=  8; i >>>=  8; } // 继续命中这个, n = n - 8 = 0000 1111 - 0000 1000 = 0000 0111, i = i >>> 8 = 00000000 00000000 00000000 01000000 
        if (i >= 1 <<  4) { n -=  4; i >>>=  4; } // 继续命中这个, n = n - 4 = 0000 0111 - 0000 0100 = 0000 0011 , i = i >>> 4 = 00000000 00000000 00000000 00000100
        if (i >= 1 <<  2) { n -=  2; i >>>=  2; } // 继续命中这个,n = n - 2 = 0000 0011 - 0000 0010 = 0000 0001 , i = i >>> 2 = 00000000 00000000 00000000 00000001
        return n - (i >>> 1); // tmp = i >>> 1 = 00000000 00000000 00000000 00000000 ,n - tmp = 0000 0001 = 1
    }

  static final int tableSizeFor(int cap) {
  		// -1 补码:11111111 11111111 11111111 11111111
        int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1);  // -1 >>> 1 ,右移动高位补0 ,        01111111 11111111 11111111 11111111

        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; // 10000000 00000000 00000000 00000000 = 2 ^ 31
    }   
  • 7
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值