Java位运算符详解
Posted lnj96
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java位运算符详解相关的知识,希望对你有一定的参考价值。
# 前言 之前了解过位运算符,左移`<<`等于乘以2,右移`>>`等于除以2。但是我在看jdk源码的时候发现了一个`>>>`三个符号的,不明白这是什么意思,就去搜了一下,发现还挺多的知识点的,就整理了一下。 首先我们知道,我们编写的程序最终都是在计算机底层进行的,计算机底层也仅支持0、1两种符号。所以当时网上有个键盘只有0、1两个键,那才是大佬用的键盘。扯远了。。。 先来复习一下java的基本类型都占多少字节,占多少位(1字节等于8位): | 类型 | 字节数 | 位数 | 大小范围 | | ------- | ------ | ---- | ---------------------------- | | 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中自行试验一下。 在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)`。 然后我们看下面这个例子: ```java 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位。打印移位后的结果和二进制。 ```java 左移前的二进制:1010 左移后的二进制:101000 左移后的十进制:40 ``` 可以看出,将原来的二进制向左移动了两位,后面进行了补零。40=10 * 2 * 2。所以一次左移等于将这个数扩大了两倍。再来看一个负数的左移: ```java int b = -8; System.out.println("左移前的二进制:" + Integer.toBinaryString(b)); b <<= 2; System.out.println("左移后的二进制:" + Integer.toBinaryString(b)); System.out.println("左移后的十进制:" + b); ``` 我们定义了一个负数(-8),打印出它的二进制,进行左移2位,左移后打印它的二进制,再将10进制打印出来查看。 ```java 左移前的二进制:11111111111111111111111111111000 左移后的二进制:11111111111111111111111111100000 左移后的十进制:-32 ``` 可以明显的看出二进制向左移动了两位,前面的位置丢弃,后面的位置补零。转换为10进制也符合我们之前的运算:-32 = -8 * 2 *2。 # 带符号右移操作符`>>` 刚才的左移中,它向左移动,高位进行了丢弃,低位进行补零。但是右移操作时有一个符号位,操作不当将造成答案与预期结果不同。 带符号右移就是在**向右移动若干位,低位进行丢弃,高位按照符号位进行填补。**对于正数做右移操作时,高位补充`0`;负数进行右移时,高位补充`1`。 再来用例子证明一下: ```java 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位。分别将它们的移动前后二进制和十进制打印出来查看。 ```java 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。现在不带符号的右移操作符大体与右移操作符一致,只不过不再区分正负数,结果都是**高位补零,低位丢弃。** 再用例子来证明一下: ```java 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); } ``` 还是刚才带符号右移的例子:这次我们仅仅把操作符换成无符号的右移操作符。 按照定义,其实在正数时不会有变化,因为在带符号的右移中正数也是高位补零。只不过当值为负数时会有变化,让我们看一下输出是不是符合猜想。 ```java a右移前的二进制:10000000000 a右移后的二进制:1000000 a右移后的十进制:64 b右移前的二进制:11111111111111101110110101000000 b右移后的二进制:1111111111111110111011010100 b右移后的十进制:268431060 ``` 确实正数没有变化,验证了我们的猜想。然后是负数,这次向右移动时高位进行了补零,低位丢弃。改变后的数值不再符合我们之前的规律。 在无符号右移中,当值为正数时,依然符合之前的规律移动一位相当于除以2。但是当值为负数时不再符合规律。 # 当移位的位数超过数值所占用的位数会怎么样? 这个问题很有意思,我们刚刚都仅仅移动了2位或者4位,如果我们超过了int的位数也就是32位后会怎么样?我们如果对一个正数左移32位,低位补零补充了32次就变成0了,就如同下面代码所写的一样,最终a的结果会是什么。会变成0吗? ```java 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类型。 ```java 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呢? ```java 操作前:-1 操作后:-1 ``` 运行结果与我们预期的结果不一致! 我们将它的二进制也一起打印出来看一下究竟: ```java 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); } ``` 这时再看一下运行结果 ```java 操作前十进制:-1 操作前二进制:11111111111111111111111111111111 操作后二进制:11111111111111111111111111111111 操作后十进制:-1 ``` 原来,**Java在对`byte`,`short`,`char`这三种类型进行移位操作前,会将其先转型为`int`类型,然后再进行位操作**。由于我们有进行了重新赋值将其赋值为原来的`byte`类型,所以又进行了从`int`到`byte`的先下转型,也就是截断。我们对上面的例子进行一下修改可以更直观的发现运行过程: ```java 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)); } ``` 在这里我没有使用`=`进行重新赋值,而是计算完成后直接打印十进制和二进制的结果。 ```java 操作前十进制:-1 操作前二进制:11111111111111111111111111111111 进行无符号右移6位后的十进制:67108863 操作后二进制:11111111111111111111111111 ``` 从打印结果中可以明显的看出是先转换为int类型,然后进行位运算,位运算结束后由于重新赋值所以进行的截断。 对于`long`类型,它是64位,不用先转换。 # 总结 移位符是Java中的基本操作符,实际支持的类型只有`int`和`long`。在对`byte`,`short`,`char`类型进行移位操作时,都会先将其转换为`int`类型再进行操作。左移`<<`操作符相当于乘以2。带符号右移操作符`>>`相当于除以2。在Java中使用位运算符会比乘`*`,除`/`运算符更高效一些。而无符号右移符`>>>`在移动时高位补零,低位丢弃,在正数时仍然相当于除以2,但是在负数时结果却是变大了(由负数变为正数)。 > 本文由博客一文多发平台 [OpenWrite](https://openwrite.cn?from=article_bottom) 发布! 博主邮箱:liunaijie1996@163.com,有问题可以邮箱交流。<`操作符相当于乘以2。带符号右移操作符`><=><` 左移操作符`<<`是将数据转换为二进制后,**向左移动若干位,高位丢弃,低位补零**。 首先我们可以利用java中的方法获取一个数的二进制:`Integer.toBinaryString(int><`左移运算符,`><`等于乘以2,右移`>以上是关于Java位运算符详解的主要内容,如果未能解决你的问题,请参考以下文章
Java的位运算符详解实例——与(&)非(~)或(|)异或(^)
Java的位运算符详解实例——与(&)非(~)或(|)异或(^)