Java位运算学习

概述

在学习位运算之前,先说下几个概念:

  1. 机器数:一个数字在计算机中的二进制表达形式就叫做机器数。机器数是有符号位的,一个数字二进制的最高位用来表示符号位,正数是0,负数是1。
  2. 真值:机器数对应的真正数值称为真值。例如:例:0000 1000的真值 = +000 1000 = +8,1000 1000的真值 = –000 1000 = –8
  3. 原码:原码就是机器数。我们以一个字节8位数据举例,例如数字8,它的原码就是0000 1000,而对于-8的话,原码就是1000 1000,首位是符号位;
  4. 反码:对正数而言,反码等于原码,而对于负数而言,反码是原码的基础上,符号位不变,剩余的按位取反。如我们还拿-8举例,-8的反码是11110111。
  5. 补码:对正数而言,同样,补码等于原码,而对于负数而言,就是在原码的基础上,符号位不变,剩余的按位取反再加1,即在反码的基础上加1。如-8,反码是11110111,补码是11111000。

  在Java中,由于没有无符号位,所以拿int来说,4个字节,占32位,最高位是符号位,0代表是正数,1代表是负数。

// 最小值是 -2的31次方
public static final int MIN_VALUE = 0x80000000;
10000000 00000000 00000000 00000000 => -2147483648;

//最大值是 2的31次方-1
public static final int MAX_VALUE = 0x7fffffff;
01111111 11111111 11111111 11111111 => 2147483647;

这里比较有意思的就是MAX_VALUE + 1 = MIN_VALUE

  我们在学习计算机组成原理的时候知道,计算机中位运算是通过补码进行的。那现在,我们就可以来看位运算了。

  首先,我们要先了解下,Java都是提供了哪些位运算符,通过JDK官方文档,我们可以知道,Java中有以下为运算符:左位移(<<)右位移(>>)无符号右位移(>>)按位与(&)按位或(|)按位非(~)按位异或(^) ,共七种位运算符,除了按位非是一元运算符,其他的都是二元运算符。

  对于位移运算符来说,有一点需要注意,就是如果操作符左边是int类型,则操作数右边的数将只会在小于2的5次方以下,就是32以下才有效。因为Java中int类型是4个字节,也就是32位,左移超过32,其实等同于左移的减去32。比如数字2左移2位,和左移34位的结果是一致的。对长整型long来说,其实也是类似的,不过不是32,是64而已。
  为了便于测试,我们下面的例子全都是使用8位来进行测试。

左移(<<)

例如将数字2左移2位(以下首位是符号位):

2的二进制表示为:[0] 0000 0010 十进制:2
左移2位后的结果:[0] 0000 1000 十进制:8

-2的二进制表示为:
原码: [1] 0000 0010    补码:[1] 1111 1110
左移2位后的结果:
补码:[1] 1111 1000    原码:[1] 0000 1000
最终结果是:-8

  这里简单说一点:正数的话,原码,反码,补码是一样的,而负数的话,从补码求原码和从原码求补码是一样的,也就是说补码的补码还是原码。

右移(>>)

同样,使用数字2来测试:

2的二进制表示为:[0] 0000 0010 十进制:2
右移2位后的结果:[0] 0000 0000 十进制:+0

-2的二进制表示为:
原码: [1] 0000 0010    补码:[1] 1111 1110
右移2位后的结果:
补码:[1] 1111 1111    原码:[1] 0000 0001
最终结果是:-1

这里也说一点,右移过程,首位补符号位,而左移,则是低位全部补0。

无符号右移(>>>)

同样,使用数字2来测试:

2的二进制表示为:    [0] 0000 0010 十进制:2
无符号右移2位后的结果:   0000 0000 十进制:0

-2的二进制表示为:
原码: [1] 0000 0010    补码:[1] 1111 1110
无符号右移2位后的结果(这里因为int是32位,所以使用省略号了):
补码: 0011 .... 1111    原码: 0011 .... 1111
最终结果是:2的30次方-1,使用Windows计算器得出:1073741823

这里其实还要说一点,无符号右移,忽略符号位,首位补0。

按位与(&)

这次我们使用数字3和5,-3和-5,-3和5来测试:

3的二进制表示为:    [0] 0000 0011
5的二进制表示为:    [0] 0000 0101
进行逻辑与之后:    [0]0000 0001 十进制:1

-3的二进制表示为:
原码: [1] 0000 0011    补码:[1] 1111 1101
-5的二进制表示为:
原码:[1] 0000 0101    补码: [1] 1111 1011
补码按位与的结果是:
补码:[1] 1111 1001    原码:[1] 0000 0111 十进制:-7

-3的二进制表示为:
原码: [1] 0000 0011    补码:[1] 1111 1101
5的二进制表示为:    [0] 0000 0101
补码按位与计算的结果是:
补码:[0] 0000 0101    原码:[0] 0000 0101 十进制:5

这里还要说一点,符号位参与位运算。

按位或(|)

这次我们还是使用数字3和5来测试:

3的二进制表示为:    [0] 0000 0011
5的二进制表示为:    [0] 0000 0101
进行按位或之后:    [0]0000 0111 十进制:7

按位非

将操作数的每一位都取反,包括符号位。

这次我们还是使用数字3来测试:

3的二进制表示为:    [0] 0000 0011
进行按位非之后:
补码 [1]1111 1100    原码:[1]0000 0100
十进制:-4

按位异或(^)

这个简单来说,就是相同的为0,不同的为1,包括符号位。

这次我们使用数字-3和5来测试:

-3的二进制表示为:
原码: [1] 0000 0011    补码:[1] 1111 1101
5的二进制补码表示为:    [0] 0000 0101
补码按位异或计算的结果是:
补码:[1] 1111 1000    原码:[1] 0000 1000 十进制:-8

以上测试我们都可以使用Integer.toBinaryString方法来查看元素的二进制形式,注:该方法返回的是元素的补码。

RegularEnumSet的addAll方法

现在我们可以来说一下RegularEnumSet类中addAll的实现了:elements = -1L >>> -universe.length;
比如说,universe数组的长度是5,那么 elements = -1L >>> -5;
在JDK文档中有说明:

  If the promoted type of the left-hand operand is int, then only the five lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x1f (0b11111). The shift distance actually used is therefore always in the range 0 to 31, inclusive.
  If the promoted type of the left-hand operand is long, then only the six lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x3f (0b111111). The shift distance actually used is therefore always in the range 0 to 63, inclusive.
以上文档位于:https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.19

  所以,如果是位移左侧是int类型,-1 >>> -5 就相当于 -1 >>> (-5 & 0x1f ) ,也就是 -1 >>> (-5 & 11111),也就是 -1 >>> 27
  同样,如果左侧是long类型,那么 -1L >>> -5,也就相当于 -1L >>> (-5 & 0x3f) ,也就是 -1L >>> (-5 & 111111),也就是 -1L >>> 59
  对int而言,无论位移多少,最终位移的范围都在0到31之间,如果大于31,则会进行模32运算,最终落在0到31之间的值就是真正的位移。而同样,对于long来说,则需进行模64运算,最终位移的范围也会在0到63之间。

本文参考自:

原码, 反码, 补码 详解
https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.19

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335