以前了解过位运算符,左移<<
等于乘以2,右移>>
等于除以2。可是我在看jdk源码的时候发现了一个>>>
三个符号的,不明白这是什么意思,就去搜了一下,发现还挺多的知识点的,就整理了一下。java
首先咱们知道,咱们编写的程序最终都是在计算机底层进行的,计算机底层也仅支持0、1两种符号。因此当时网上有个键盘只有0、1两个键,那才是大佬用的键盘。扯远了。。。ide
先来复习一下java的基本类型都占多少字节,占多少位(1字节等于8位):code
类型 | 字节数 | 位数 | 大小范围 |
---|---|---|---|
byte | 1 | 8 | -2^8^~2^8^-1 |
short | 2 | 16 | -2^16^~2^16^-1 |
int | 4 | 32 | -2^32^~2^32^-1 |
long | 8 | 64 | -2^64^~2^64^-1 |
float | 4 | ||
double | 8 | ||
char | 2 | 16 | 一个char类型能够存储一个汉字 |
boolean | 1 | true or false |
移位操做是把数据看做二进制数,而后将其向左或向右移动若干位的运算。在Java中,移位操做符包含三种:<<
左移运算符,>>
带符号右移运算符,>>>
无符号右移运算符。这三种操做符都只能做用于long
,int
,short
,byte
这四种基本整形类型上和char
类型上。其余类型如double都没法使用位运算符,你们能够在ide中自行试验一下。get
在java中,第一位用来表示数字的正负,第一位为零时表示正数,第一位为1时表示负数。咱们拿最简单的8位byte类型举例:0000 0000
表示0,0111 1111
这个表示最大值(2^8^-1),再进行加一后就变成了1000 0000
这时就变成了最小值(-2^8^)。再加一后变成1000 0001
这时的值为-127。也就是从0到最大值而后转为最小值,而后再从最小值向零靠近。源码
<<
左移操做符<<
是将数据转换为二进制后,向左移动若干位,高位丢弃,低位补零。博客
首先咱们能够利用java中的方法获取一个数的二进制:Integer.toBinaryString(int val)
。it
而后咱们看下面这个例子:table
public static void main(String[] args) { int a = 10; System.out.println("左移前的二进制:"+Integer.toBinaryString(a)); a <<= 2; System.out.println("左移后的二进制:"+Integer.toBinaryString(a)); System.out.println("左移后的十进制:"+a); }
首先定义一个数,值为10,打印它的二进制(1010),而后进行左移操做2位。打印移位后的结果和二进制。class
左移前的二进制:1010 左移后的二进制:101000 左移后的十进制:40
能够看出,将原来的二进制向左移动了两位,后面进行了补零。40=10 * 2 * 2。因此一次左移等于将这个数扩大了两倍。再来看一个负数的左移:变量
int b = -8; System.out.println("左移前的二进制:" + Integer.toBinaryString(b)); b <<= 2; System.out.println("左移后的二进制:" + Integer.toBinaryString(b)); System.out.println("左移后的十进制:" + b);
咱们定义了一个负数(-8),打印出它的二进制,进行左移2位,左移后打印它的二进制,再将10进制打印出来查看。
左移前的二进制:11111111111111111111111111111000 左移后的二进制:11111111111111111111111111100000 左移后的十进制:-32
能够明显的看出二进制向左移动了两位,前面的位置丢弃,后面的位置补零。转换为10进制也符合咱们以前的运算:-32 = -8 * 2 *2。
>>
刚才的左移中,它向左移动,高位进行了丢弃,低位进行补零。可是右移操做时有一个符号位,操做不当将形成答案与预期结果不一样。
带符号右移就是在向右移动若干位,低位进行丢弃,高位按照符号位进行填补。对于正数作右移操做时,高位补充0
;负数进行右移时,高位补充1
。
再来用例子证实一下:
public static void main(String[] args) { int a = 1024; System.out.println("a右移前的二进制:" + Integer.toBinaryString(a)); a >>= 4; System.out.println("a右移后的二进制:" + Integer.toBinaryString(a)); System.out.println("a右移后的十进制:"+a); int b = -70336; System.out.println("b右移前的二进制:" + Integer.toBinaryString(b)); b >>= 4; System.out.println("b右移后的二进制:" + Integer.toBinaryString(b)); System.out.println("b右移后的十进制:"+b); }
定义了两个变量,a=1024,而后向右移动4位。b=-70336也向右移动4位。分别将它们的移动先后二进制和十进制打印出来查看。
a右移前的二进制:10000000000 a右移后的二进制:1000000 a右移后的十进制:64 b右移前的二进制:11111111111111101110110101000000 b右移后的二进制:11111111111111111110111011010100 b右移后的十进制:-4396
a原来的二进制向右移动后,低位被丢弃,高位补充符号位也就是0。b原来的二进制向右移动后,低位被丢弃,高位补充符号位1。这也符号咱们以前的运算规律:
1024 / 2^4^ =16 ;-70336/ 2^4^ = -4396。
>>>
刚才的带符号右移操做符,咱们在向右移动时带着高位的符号,正数填充0,负数填充0。如今不带符号的右移操做符大致与右移操做符一致,只不过再也不区分正负数,结果都是高位补零,低位丢弃。
再用例子来证实一下:
public static void main(String[] args) { int a = 1024; System.out.println("a右移前的二进制:" + Integer.toBinaryString(a)); a >>>= 4; System.out.println("a右移后的二进制:" + Integer.toBinaryString(a)); System.out.println("a右移后的十进制:"+a); int b = -70336; System.out.println("b右移前的二进制:" + Integer.toBinaryString(b)); b >>>= 4; System.out.println("b右移后的二进制:" + Integer.toBinaryString(b)); System.out.println("b右移后的十进制:"+b); }
仍是刚才带符号右移的例子:此次咱们仅仅把操做符换成无符号的右移操做符。
按照定义,其实在正数时不会有变化,由于在带符号的右移中正数也是高位补零。只不过当值为负数时会有变化,让咱们看一下输出是否是符合猜测。
a右移前的二进制:10000000000 a右移后的二进制:1000000 a右移后的十进制:64 b右移前的二进制:11111111111111101110110101000000 b右移后的二进制:1111111111111110111011010100 b右移后的十进制:268431060
确实正数没有变化,验证了咱们的猜测。而后是负数,此次向右移动时高位进行了补零,低位丢弃。改变后的数值再也不符合咱们以前的规律。
在无符号右移中,当值为正数时,依然符合以前的规律移动一位至关于除以2。可是当值为负数时再也不符合规律。
这个问题颇有意思,咱们刚刚都仅仅移动了2位或者4位,若是咱们超过了int的位数也就是32位后会怎么样?咱们若是对一个正数左移32位,低位补零补充了32次就变成0了,就如同下面代码所写的同样,最终a的结果会是什么。会变成0吗?
public static void main(String[] args) { int a = 10; a <<= 32; System.out.println(a); }
通过咱们运行后发现a的结果最终没变仍是10。咱们若是改为左移33位,它的结果会变成20
。那么它的运算规律会不会是当超过位数后仅仅移动对位数的余数呢?好比对int作操做,它实际是运算 位数%32
次。
通过屡次试验发现答案确实就是这个猜测,当对int类型处理时,右移x
位的运算为x%32
位。
咱们刚才都是用的int类型,那么对于byte
,short
,char
,long
都同样吗?
先看一下byte类型。
public static void main(String[] args) { byte b = -1; System.out.println("操做前:"+b); b >>>= 6; System.out.println("操做后:"+b); }
定义了byte的值为-1,即1111 1111
,而后无符号右移6位,高位补零,低位丢弃,那么应该变成0000 0011
也就是是3。让咱们运行一下这段代码看一下打印出来的信息是否是3呢?
操做前:-1 操做后:-1
运行结果与咱们预期的结果不一致!
咱们将它的二进制也一块儿打印出来看一下究竟:
public static void main(String[] args) { byte b = -1; System.out.println("操做前十进制:"+b); System.out.println("操做前二进制:"+Integer.toBinaryString(b)); b >>>= 6; System.out.println("操做后二进制:"+Integer.toBinaryString(b)); System.out.println("操做后十进制:"+b); }
这时再看一下运行结果
操做前十进制:-1 操做前二进制:11111111111111111111111111111111 操做后二进制:11111111111111111111111111111111 操做后十进制:-1
原来,Java在对byte
,short
,char
这三种类型进行移位操做前,会将其先转型为int
类型,而后再进行位操做。因为咱们有进行了从新赋值将其赋值为原来的byte
类型,因此又进行了从int
到byte
的先下转型,也就是截断。咱们对上面的例子进行一下修改能够更直观的发现运行过程:
public static void main(String[] args) { byte b = -1; System.out.println("操做前十进制:"+b); System.out.println("操做前二进制:"+Integer.toBinaryString(b)); System.out.println("进行无符号右移6位后的十进制:"+(b>>>6)); System.out.println("操做后二进制:"+Integer.toBinaryString(b>>>6)); }
在这里我没有使用=
进行从新赋值,而是计算完成后直接打印十进制和二进制的结果。
操做前十进制:-1 操做前二进制:11111111111111111111111111111111 进行无符号右移6位后的十进制:67108863 操做后二进制:11111111111111111111111111
从打印结果中能够明显的看出是先转换为int类型,而后进行位运算,位运算结束后因为从新赋值因此进行的截断。
对于long
类型,它是64位,不用先转换。
移位符是Java中的基本操做符,实际支持的类型只有int
和long
。在对byte
,short
,char
类型进行移位操做时,都会先将其转换为int
类型再进行操做。左移<<
操做符至关于乘以2。带符号右移操做符>>
至关于除以2。在Java中使用位运算符会比乘*
,除/
运算符更高效一些。而无符号右移符>>>
在移动时高位补零,低位丢弃,在正数时仍然至关于除以2,可是在负数时结果倒是变大了(由负数变为正数)。
本文由博客一文多发平台 OpenWrite 发布! 博主邮箱:liunaijie1996@163.com,有问题能够邮箱交流。