在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+、-、*、/、%)、关系运算(<、>、<=、>=、==、!=)和逻辑运算(&&、||、!),所以相对来说对位运算不是那么熟悉,本文将以Java的位运算来详细介绍下位运算及其应用。

1、 位运算起源

  位运算起源于C语言的低级操作,Java的设计初衷是嵌入到电视机顶盒内,所以这种低级操作方式被保留下来。所谓的低级操作,是因为位运算的操作对象是二进制位,但是这种低级操作对计算机而言是非常简单直接,友好高效的。在简单的低成本处理器上,通常位运算比除法快得多,比乘法快几倍,有时比加法快得多。虽然由于较长的指令流水线和其他架构设计选择,现代处理器通常执行加法和乘法的速度与位运算一样快,但由于资源使用减少,位运算通常会使用较少的功率,所以在一些Java底层算法中,巧妙的使用位运算可以大量减少运行开销。

2、 位运算详解

  Java位运算细化划分可以分为按位运算和移位运算,见下表。

细化

符号

描述

运算规则

按位运算

&

两位都为1,那么结果为1

|

有一位为1,那么结果为1

~

~0 = 1,~1 = 0

^

异或

两位不相同,结果为1

移位运算

<<

左移

各二进制位全部左移N位,高位丢弃,低位补0

>>

右移

各二进制位全部右移N位,若值为正,则在高位插入 0,若值为负,则在高位插入 1

>>>

无符号右移

各二进制位全部右移N位,无论正负,都在高位插入0

  在进行位运算详解之前,先来普及下计算机中数字的表示方法。对于计算机而言,万物皆0、1,所有的数字最终都会转换成0、1的表示,有3种体现形式,分别是:原码、反码和补码

  原码:原码表示法在数字前面增加了一位符号位,即最高位为符号位,正数位该位为0,负数位该位为1.比如十进制的5如果用8个二进制位来表示就是00000101,-5就是10000101。

  反码:正数的反码是其本身,负数的反码在其原码的基础上,符号位不变,其余各个位取反。5的反码就是00000101,而-5的则为11111010。

  补码:正数的补码是其本身,负数的补码在其原码的基础上,符号位不变,其余各位取反,最后+1。即在反码的基础上+1。5的反码就是00000101,而-5的则为11111011。

  了解了这几个概念后,我们现在先记住一个结论,那就是在计算机系统中,数字一律用补码来表示、运算和存储,具体的原因可以看这篇文章的讨论,这里不做更多讨论,因为不是本文的重点。

2.1 与运算(&)

  规则:转为二进制后,两位为1,则结果为1,否则结果为0。

  举例:

十进制

二进制(正数原码、反码、补码一致)

10

00000000000000000000000000001010

&12

&00000000000000000000000000001100

=

=

8

00000000000000000000000000001000

十进制

二进制(原码)

-6

10000000000000000000000000000110

&-2

&10000000000000000000000000000010

十进制

二进制(反码)

-6

11111111111111111111111111111001

&-2

&11111111111111111111111111111101

十进制

二进制(补码)

-6

11111111111111111111111111111010

&-2

&11111111111111111111111111111110

=

=

-6

11111111111111111111111111111010

  最后的计算结果11111111111111111111111111111010还是补码的形式,要看其十进制,还需要先转成二进制原码。

  先转反码:11111111111111111111111111111010-1=11111111111111111111111111111001,得反码11111111111111111111111111111001。

  再转原码:在反码的基础上转原码,符号位不变,其他各位取反,得10000000000000000000000000000110。第一位1代表负数,后面0110转成十进制是6,得-6。

2.2 或运算(|)

  规则:转为二进制后,有一位为1,则结果为1,否则结果为0。

  举例:

十进制

二进制(正数原码、反码、补码一致)

10

00000000000000000000000000001010

|12

|00000000000000000000000000001100

=

=

14

00000000000000000000000000001110

十进制

二进制(原码)

-6

10000000000000000000000000000110

|-2

|10000000000000000000000000000010

十进制

二进制(反码)

-6

11111111111111111111111111111001

|-2

|11111111111111111111111111111101

十进制

二进制(补码)

-6

11111111111111111111111111111010

|-2

|11111111111111111111111111111110

=

=

-2

11111111111111111111111111111110

2.3 非运算(~)

  规则:转为二进制后,~0 = 1,~1 = 0。

  举例:

十进制

二进制(正数原码、反码、补码一致)

~7

~00000000000000000000000000000111

=

=

-8

11111111111111111111111111111000(补码需转换为原码)

  11111111111111111111111111111000-1得反码,可以把1000看成是0112,得反码11111111111111111111111111110111。根据反码得原码10000000000000000000000000001000。

十进制

二进制(原码)

~(-6)

~10000000000000000000000000000110

十进制

二进制(反码)

~(-6)

~11111111111111111111111111111001

十进制

二进制(补码)

~(-6)

~11111111111111111111111111111010

=

=

5

00000000000000000000000000000101(正数原码、反码、补码一致)

 

2.4 异或运算(^)

  规则:转为二进制后,两位不相同,结果为1,否则为0。

  举例:

十进制

二进制(正数原码、反码、补码一致)

15^2

00000000000000000000000000001111

^00000000000000000000000000000010

=

=

13

00000000000000000000000000001101

 

2.5 左移运算(<<)

  规则:转为二进制后,各二进制位全部左移N位,高位丢弃,低位补0。

  举例:

十进制

二进制(正数原码、反码、补码一致)

2<<2

00000000000000000000000000000010

=

0000000000000000000000000000001000

8

00000000000000000000000000001000

 

十进制

二进制(先取补码 再对补码操作位移)

-2<<2

10000000000000000000000000000010(原码)

11111111111111111111111111111101(反码)

11111111111111111111111111111110(补码)

1111111111111111111111111111111000

11111111111111111111111111111000(补码)

11111111111111111111111111110111(反码)

-8

10000000000000000000000000001000(原码)

 

2.6 右移运算(>>)

  规则:转为二进制后,各二进制位全部右移N位,若值为正,则在高位插入 0,若值为负,则在高位插入 1。

  举例:

十进制

二进制(正数原码、反码、补码一致)

2>>2

00000000000000000000000000000010

=

0000000000000000000000000000000010

0

00000000000000000000000000000000

 

十进制

二进制(先取补码 再对补码操作位移)

-6>>2

10000000000000000000000000000110(原码)

11111111111111111111111111111001(反码)

11111111111111111111111111111010(补码)

1111111111111111111111111111111010

11111111111111111111111111111110(补码)

11111111111111111111111111111101(反码)

-2

10000000000000000000000000000010(原码)

 

2.7 无符号右移运算(>>>)

  规则:转为二进制后,各二进制位全部右移N位,无论正负,都在高位插入0。

  举例:

十进制

二进制(先取补码 再对补码操作位移)

-1>>>1

10000000000000000000000000000001(原码)

11111111111111111111111111111110(反码)

11111111111111111111111111111111(补码)

011111111111111111111111111111111

01111111111111111111111111111111(补码)

01111111111111111111111111111110(反码)

溢出,只能表示到int的最大值2147483647

10000000000000000000000000000001(原码)

3、 应用

3.1 不用额外的变量实现两个数字互换

  见参考资料中的BitOperationTest,方法reverse通过三次异或操作完成了两个变量值的替换。

  证明很简单,我们只需要明白异或运算满足下面规律(实际不止如下规律):

  0^a = a,a^a = 0;

  a ^ b = b ^ a;

  a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;

  a ^ b ^ a = b;

  假设a,b两个变量,经过如下步骤完成值交换:a=a^b,b=b^a,a=a^b。

  证明如下:

  因为a ^ b = b ^ a,又a=a^b,b=b^a。故b=b^a= b^ (a^b)=a。

  继续a=a^b,a=(a^b) ^ b^ (a^b),故a=b。完成值交换。

3.2 不用判断语句实现求绝对值

  公式如下:(a^(a>>31))-(a>>31)

  先整理一下使用位运算取绝对值的思路:若a为正数,则不变,需要用异或0保持的特点;若a为负数,则其补码为原码翻转每一位后+1,先求其原码,补码-1后再翻转每一位,此时需要使用异或1具有翻转的特点。

  任何正数右移31后只剩符号位0,最终结果为0,任何负数右移31后也只剩符号位1,溢出的31位截断,空出的31位补符号位1,最终结果为-1.右移31操作可以取得任何整数的符号位。

  那么综合上面的步骤,可得到公式。a>>31取得a的符号,若a为正数,a>>31等于0,a^0=a,不变;若a为负数,a>>31等于-1 ,a^-1翻转每一位。

3.3 判断一个数的奇偶性

  通过与运算判断奇偶数,伪代码如下:

  n&1 == 1?”奇数”:”偶数”

  奇数最低位肯定是1,而1的二进制最低位也是1,其他位都是0,所以所有奇数和1与运算结果肯定是1。

参考资料:

https://github.com/lingjiango/ConcurrentProgramPractice

https://en.wikipedia.org/wiki/Bitwise_operation

https://www.zhihu.com/question/20159860

Java基础-一文搞懂位运算的更多相关文章

  1. Java 基础 一文搞懂泛型

    本文将从以下四个方面来系统的讲解一下泛型,基本上涵盖了泛型的主体内容. 什么是泛型? 为什么要使用泛型? 如何使用泛型? 泛型的特性 1. 什么是泛型? 泛型的英文是Generics,是指在定义方法. ...

  2. Java基础语法(语法、位运算、JavaDoc等)

    一.注释.标识符.关键字 1.注释(comments) 平时我们编写代码,在代码量比较少的时候,我们还可以看懂自己编写的代码,但是当项目结构一旦复杂起来,我们就需要用到注释了! 注释并不会被程序执行, ...

  3. 一文搞懂所有Java集合面试题

    Java集合 刚刚经历过秋招,看了大量的面经,顺便将常见的Java集合常考知识点总结了一下,并根据被问到的频率大致做了一个标注.一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多.两颗 ...

  4. Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!

    本文原作者: Wizey,作者博客:http://wenshixin.gitee.io,即时通讯网收录时有改动,感谢原作者的无私分享. 1.引言 典型的Web端即时通讯技术应用场景,主要有以下两种形式 ...

  5. 基础篇|一文搞懂RNN(循环神经网络)

    基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...

  6. 三文搞懂学会Docker容器技术(下)

    接着上面一篇:三文搞懂学会Docker容器技术(上) 三文搞懂学会Docker容器技术(中) 7,Docker容器目录挂载 7.1 简介 容器目录挂载: 我们可以在创建容器的时候,将宿主机的目录与容器 ...

  7. 一文搞懂如何使用Node.js进行TCP网络通信

    摘要: 网络是通信互联的基础,Node.js提供了net.http.dgram等模块,分别用来实现TCP.HTTP.UDP的通信,本文主要对使用Node.js的TCP通信部份进行实践记录. 本文分享自 ...

  8. 一文搞懂指标采集利器 Telegraf

    作者| 姜闻名 来源|尔达 Erda 公众号 ​ 导读:为了让大家更好的了解 MSP 中 APM 系统的设计实现,我们决定编写一个<详聊微服务观测>系列文章,深入 APM 系统的产品.架构 ...

  9. 一文搞懂Google Navigation Component

    一文搞懂Google Navigation Component 应用中的页面跳转是一个常规任务, Google官方提供的解决方案是Android Jetpack的Navigation componen ...

随机推荐

  1. 《机器学习实战(基于scikit-learn和TensorFlow)》第六章内容学习心得

    本章讲决策树 决策树,一种多功能且强大的机器学习算法.它实现了分类和回归任务,甚至多输出任务. 决策树的组合就是随机森林. 本章的代码部分不做说明,具体请到我的GitHub上自行获取. 决策树的每个节 ...

  2. Redis 设计与实现 (一)--数据结构

    底层数据结构:动态字符串.字典.整数集合.双端链表.压缩列表 字符串对象: int    浮点数值 raw  字符串值>32字节 embstr   字符串值<32字节 字符串编码转换: i ...

  3. Docker - 参考信息

    初见 从 0 开始了解 Docker 可能是把Docker的概念讲的最清楚的一篇文章 Docker新手指南 8 个基本的 Docker 容器管理命令 Docker 核心技术与实现原理 在线教程 Doc ...

  4. 毕业不到一年,绩效打了个D!

    周末了,和大家来聊聊程序员工作态度的问题. 说说栈长的事迹吧,这是好多年前的事了,那时候,栈长才毕业不到一年,那次绩效打了个D!事后,我很气愤啊,我那时还在博客上写文章怒骂了部门经理,现在想起来,真是 ...

  5. 【sping揭秘】25、Spring远程方案

    分化:RMI,EJB,Hessian Spring有 Rmi,http,hessian,burlap 基于rmi的remoting方案 RMI要求远程类对象包路径和本地一致 基于HTTP的轻量级rem ...

  6. [学习笔记]利用e-debug和GetWindowTextA破解CM课件

    本课是针对注册时候的报错弹窗不是信息框MessageBox,而是窗体的情况 首先打开课件看一下 既然课件是个易语言程序,那使用E-DEBUG试试 E-DEBUG打开课件,点击“start”,课件弹出登 ...

  7. elasticsearch 操作

    文档:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html 客户端:https://www.elasti ...

  8. JAVA UUID 生成唯一标识

    Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket Reprint it anywhere u want 需求 项目在设计表的时候,要处理并发多的一些数据 ...

  9. 从零开始学 Web 之 Vue.js(六)Vue的组件

    大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新...... github:https://github.com/Daotin/Web 微信公众号:Web前端之巅 博客园:ht ...

  10. 高性能Mysql笔记 — 优化

    性能优化 了解查询的整个生命周期,清楚每个阶段的时间消耗情况 性能分析 慢查询日志--服务器性能分析 参考 慢查询日志是优化很重要的手段,但是开启慢查询日志对性能的影响并不大,所以可以考虑在线上打开慢 ...