bit
bit
是计算机的最小的存储单元,一切数据最终都以bit
的形式存放在计算机之中。
一个bit
有且只有两种状态。要么是0
,要么是1
。像这样:
多个bit
组合在一起就可以构成更复杂的数据。例如,8个bit
组合在一起就构成了一个byte
:
0 1 1 1 1 1 1 1
byte
在Java中,byte
是基本的数据类型,是一个有符号,也就是有正负之分的整数。最大值是127
,最小值是-128
。
127
127
在计算机内部是如何存储的呢?当然是以二进制形式存储:
0 1 1 1 1 1 1 1
8个bit
,右边是低位,左边是高位。黄色的bit
是符号位,0
表示正数,1
表示负数。
-128
-128
呢?
看着挺好解释的,2的7次方 = 128,符号位是1
代表它是负数。是这样吗?
-1
-1
的二进制表示如下:
1 1 1 1 1 1 1 1
是不是什么神奇?怎么会是8个1
?要解释这个问题,就需要知道原码、反码、补码。
原码、反码、补码
原码
原码是数值对应的二进制表示形式,例如:
0: 0 0 0 0 0 0 0 0
1: 0 0 0 0 0 0 0 1
2: 0 0 0 0 0 0 1 0
对于正数,计算机存的就是原码。
-1
的原码表示如下:
1 0 0 0 0 0 0 1
可是,对于负数,计算机存的是其补码。
反码
反码是对原码中的bit
位取反,0
变1
,1
变0
。符号位除外,因为符号位表正负,不表数值。
原码:1 0 0 0 0 0 0 1
反码:1 1 1 1 1 1 1 0
补码
补码是在反码的基础上加1
。还拿-1
举例:
你是否理解补码了呢?考你一道题,-2
在计算机里是如何表示的?
位运算
Java支持取反
、与
、或
、异或
等运算,接下来我一一举例。
取反
取反操作的运算符号是~
,这个操作符可以将操作数的bit位逐一翻转。1
→ 0
,0
→ 1
。
例如:
a: 0 1 0 1 0 1 0 1
~a: 1 0 1 0 1 0 1 0
与
与运算的操作符号是&
,它将两个操作数的bit位一一比对。如果记录比对结果呢?——规则如下:
- 如果两个操作数的bit位同时为
1
,则记1
- 否则,记为
0
例如:
a: 0 0 0 0 1 1 1 1
b: 1 1 1 1 0 0 0 1
a&b: 0 0 0 0 0 0 0 1
或
或运算的操作符是|
,它将两个操作数的bit位一一比对。如果记录比对结果呢?——规则如下:
- 如果两个操作数的bit位有一个为
1
,则记为1
- 否则,记为
0
a: 0 0 0 0 1 1 1 0
b: 1 1 1 1 1 1 1 0
a|b: 1 1 1 1 1 1 1 0
亦或
亦或的操作符是^
,它也是一一比较两个操作数的bit位,规则如下:
- 如果两个bit位相同,则记为
0
- 否则,记为
1
a: 0 0 0 0 1 1 1 1
b: 1 1 1 1 1 1 1 0
a^b: 1 1 1 1 0 0 0 1
位移
byte
,short
,int
,long
等整数都支持位移,也就是将bit位整体向左或者向右移动。
左移
左移的运算符为<<
,它是这样操作输入数据的:
- 从左到右,每个bit都向左←移动
- 一个bit一个坑,左数第一个bit移出去,不要了
- 第2个bit占第1个bit的坑
- 第3个bit占第2个bit的坑,如此类推
- 最后会空出来一个bit位,用
0
补充
例如:
a: 0 0 0 0 0 0 0 1
a<<1: 0 0 0 0 0 0 1 0
<<
相当于对原数字做乘2
操作。
如果是负数呢?
-1: 1 1 1 1 1 1 1 1
-1 << 1: 1 1 1 1 1 1 1 0
还记得前面的考题吧?计算机是如何存储-2
的?
原码:1 0 0 0 0 0 1 0
反码:1 1 1 1 1 1 0 1
补码:1 1 1 1 1 1 1 0
-1 << 1
等于-2
。
右移(带符号位)
带符号位右移的操作符号是>>
,它是这样操作的:
- 从右边第一个bit开始,逐一向右侧移动
- 第1个bit向右移,没地儿了,舍弃它
- 第2个bit向右移动到第1个bit的坑
- 第3个bit向右移动到第2个bit的坑,以此类推
符号位
依旧向右移动- 用
符号位
补充空出来的左边第一个位置
例如:
a: 0 0 0 0 0 0 1 0
a>>1: 0 0 0 0 0 0 0 1
>>1
相当于把原数做除2
的操作。再举一个负数的例子:
a: 1 0 0 0 1 0 0 0
a>>1: 1 1 0 0 0 1 0 0
1 0 0 0 1 0 0 0
代表的是什么数呢?如果它是一个byte
,只有8个bit
,它就是-120
。在Java中,可以加上0b
前缀,告诉计算机这是二进制数(binary)。
System.out.println((byte) 0b10001000);
// -120
那如何用眼睛看出来呢?补码是怎么来的呢?
原码 → 反码 → 补码
逆转回去就知道原码了,也就是:补码 → 反码 → 原码:
- 既然补码是反码
+1
来的,那补码-1
就是反码了。 - 既然反码是原码取反得来的,那对反码取反就能得到原码。(不忘了符号位不变)
例如:
补码:1 0 0 0 1 0 0 0
反码:1 0 0 0 0 1 1 1
原码:1 1 1 1 1 0 0 0
右移(不带符号位)
>>>
和>>
的区别是,它是这样操作的:
- 从右边第一个bit开始,逐一向右侧移动
- 第1个bit向右移,没地儿了,舍弃它
- 第2个bit向右移动到第1个bit的坑
- 第3个bit向右移动到第2个bit的坑,以此类推
- 符号位依旧向右移动
- 用
0
补充空出来的左边第一个位置
例如:
a: 0 0 0 0 0 0 1 0
a>>1: 0 0 0 0 0 0 0 1
a>>>1: 0 0 0 0 0 0 0 1
再举个负数的例子:
a: 1 0 0 0 1 0 0 0
a>>1: 1 1 0 0 0 1 0 0
a>>>1: 0 1 0 0 0 1 0 0
上面讲的都是移动1个bit,也可以移动多个bit的,例如:
a: 0 0 0 0 0 0 0 1
a<<1: 0 0 0 0 0 0 1 0
a<<2: 0 0 0 0 0 1 0 0
a<<3: 0 0 0 0 1 0 0 0
a<<4: 0 0 0 1 0 0 0 0
a<<5: 0 0 1 0 0 0 0 0
a<<6: 0 1 0 0 0 0 0 0
a<<7: 1 0 0 0 0 0 0 0
a<<8: 0 0 0 0 0 0 0 0
a<<9: 0 0 0 0 0 0 0 0
a<<100: 0 0 0 0 0 0 0 0