JAVA位运算的知识点总结

一、在计算机中数据是如何进行计算的?

1.1:java中的byte型数据取值范围

我们最开始学习java的时候知道,byte类型的数据占了8个bit位,每个位上或0或1,左边第一位表示符号位,符号位如果为1表示负数,为0则表示正数,因此要推算byte的取值范围,只需要让数值位每一位上都等于1即可。

我们来用我们的常规思维来分析下byte类型的取值范围:

图1

如果按照这种思路来推算,七个1的二进制数转换为十进制是127,算上符号位,取值范围应为:-127~+127,但事实上我们知道,byte的取值范围是-128~127,这里先打个问号,接着往下看。

现在让我们计算下byte类型的7加上byte类型的-2是多少:

图2

诶?跟我们预想的不一样,因为我们是知道7和-2的和应该是5才对,结果应该表示为:00000101,但事实上通过图2的结果来看确实跟预想的不一样,所以计算机在做计算的时候,肯定不是表面上的符号位+数值位的方式进行的计算的。

1.2:原码,反码,补码

我们先来看下定义:

👉 原码定义:符号位加后面的数值,比如图2里的00000111和10000010都是原码,原码比较简单,就是我们在上面单纯理解上的原值。

👉 反码定义:正数的反码就是它的原码,负数的反码符号位不变,其余数值位全部按位取反,例如:

00000111的反码:00000111

10000010的反码:11111101

👉 补码定义:同样的,正数的补码仍然等于它的原码本身,负数的补码等于它自己的反码+1,例如:

00000111的补码:00000111

10000010的补码:11111110

🌴 总结:正数的原码、反码、补码完全一致,负数的反码等于它原码的数值位按位取反,负数的补码等于它的反码+1

现在让我们用反码的方式来计算下图2中的式子:

图3

利用数值的反码计算出的结果已经很接近正确答案了,+4的反码等于它的原码,现在只需要让它+1就是正确答案,还记得补码的定义吗?负数的补码等于它的反码+1,那现在让我们用补码做下计算试试?

图4

ok,我们发现,用它们的补码做加法,得到的数值就是我们想要的正确答案,事实上,计算机并没有减法运算器,所有的减法运算,都是以一个正数加上一个负数的形式来交给加法运算器计算的,由于负数的符号位为1,虽然我们人是知道它的含义,但是作为计算机,它是不知道第一位是符号位的,它要做的就仅仅是让两个数相加而已,正是因为如此,我们才不能简简单单保存负数,通过图4我们知道,两个数的补码相加,可以得到一个准确的数值。

再举个相加结果为负数的例子,让两个负数相加:

图5

如果结果为负数的话,也是适用的,只是它仍然是以补码的形式存放的,需要转成原码才符合我们人的理解方式。

现在回到上面留下的问题,为什么byte的取值范围是-128~127呢?

我们之前按照图1里的理解,理所应当的以为它应该是-127~127的范围,那是因为我们按照图1的理解方式,数值就是以符号位+数值位的方式理解的(也就是按照原码的方式理解的),但是你可以想一下,如果按照图1那种理解方式,是不是会存在两个0值呢?

即:10000000和00000000,+0和-0;

其次如果站在机器角度上来说,所有的负数都很大,至少要比所有正数大,因为负数的最高位也就是符号位都是1,显然这是不对的,通过本节我们知道了,所有的数均通过自己的补码完成计算,如果将最后得到的结果转成原码,就是我们人眼可以理解的最终值(符号位+数值位),如果现在利用补码的方式做理解,符号位为0的数没啥好说的,自然取值区间为:0~127,但是符号位为1的负数呢?负数就存在一个特殊值(也就是我们之前片面理解的-0):10000000,如果按照原码理解它是-0,但我们前面说过,计算机里所有数字,都是以补码的方式参与运算的,而负数的补码不等于其原码,这个10000000在计算机里显然是某个负数的补码,那么问题就变的简单多了,即10000000是谁的补码呢?答案是:-128,这也是为什么负数的取值范围会比正数多一个的原因,byte类型如此,其它类型也是如此,比如int型的负数取值也比正数多1。

这一块的定义要清晰,对理解后面的位运算会有很大的帮助。

二、java中的位运算 2.1:与运算

与运算符号:&

与运算特点:1&1=1、1&0=0、0&1=0、0&0=0

现在我们来举一个例子:

图6

让我们再来试试负数:

图7

2.2:或、异或

跟与运算的运算方式一致,只不过规则不太一样:

或运算符号:|

或运算规则:1|1=1、1|0=1、0|1=1、0|0=0

异或运算符号:^

异或运算规则:1^1=0、1^0=1、0^1=1、0^0=0

2.3:按位取反

取反符号:~

即一个数对自己取反,例如:

某个数字a的二进制为: 1010110

则~a为: 0101001

2.4:左移运算

左移运算符:<<

例如:

图8

位运算越界&数位抛弃:

图8中的116的二进制数的数值位为7位,符号位为0,此时如果左移超过24位,就会出现负数,为什么会这样?因为java中的位移越界时,java会抛弃高位越界部分,我们知道java里int类型的第一位是符号位,如果符号位是1,则表示其为负数,现在将数值位占7bit符号位为0的116左移24位,就会出现下方结果:

01110100000000000000000000000000

正好31位占全,顶至符号位,低位补0,我们称24为116的不越界的最大左移值,若超出这个值,就会越界,比如左移25位:

11101000000000000000000000000000

显然左移25位后会把数值位的1移动到符号位,这时它表示为一个负数的补码。根据这个规则,我们如果让其左移28位,则值为:

01000000000000000000000000000000

也就是十进制的1073741824,即:116 << 28 = 1073741824,那如果越界过多呢?比如int型的数据,左移32位:116 << 32 = 116

会发现,如果左移自己位数一样多的位数,那么这个数就等于它本身,因此运算符合以下规则:

设x为被位移值,y为本次位移的位数,z为x所属类型的最大存储位数:

x << y = x << (y%z)

如果是int型(32位,long型就用64代入计算),符合如下规则:

116 << 4 = 116 << (4%32) = 116 << 4 = 1856

116 << 32 = 116 << (32%32) = 116 << 0 = 116

116 << 36 = 116 << (36%32) = 116 << 4 = 1856

2.5:有符号右移运算&无符号右移运算

有符号右移运算符:>>

无符号右移运算符:>>>

例如:a >> b表示a右移b位,跟上面的左移例子一样,右移也会有越界问题,只是右移越界是从右边开始抛弃越界部分的,右移操作有符号位干扰,如果是正数右移,无此干扰项,因为符号位本就是0右移不会影响值的准确性,但如果是负数,第一位是符号位,且值为1,右移就有影响了,现在仍然以116为例:

正数右移:

图9

上述是正数,右移无影响,但是负数,这里以-116为例,我们知道负数在计算机里是以补码的形式存储的,所以图里直接用-116的补码做运算,位移过程如下:

图10

你会发现右移跟左移不一样,左移是不用担心自己符号位存在“补位”问题的,但是右移存在,如图中-116右移4位后,左边第一位,也就是符号位,就面临着补位的问题,那我现在是该补1呢,还是补0呢?这也就是为什么右移操作会存在有符号右移和无符号右移两种移动方式:

☘️ 有符号右移:依照原符号位,如果原符号位是1,那么图4里需要补位的空位全部补1,如果原符号位为0,则全部补0

☘️ 无符号右移:无视原符号位,全部补0

现在让我们用有符号的方式将-116右移4位,即-116 >> 4,按照有符号的规则,补位符合原符号位,则右边4位全部补1:

图11

得到的仍然是个负数,它仍然是一个补码,图里展示不开,它的结果为:11111111111111111111111111111000,经转换可知它是-8的补码,即:-116 >> 4 = -8

现在再试试用无符号右移,根据无符号的特点,右移后的前四位无脑补0:

图12

图里展示不开,它的结果为:00001111111111111111111111111000

可见它是个正数,转换成十进制为:268435448,即:-116 >>> 4 = 268435448

最后说一下,跟左移一样,右移里不管是有符号还是无符号,也符合取余的方式,计算出位移的最终位数:

-116 >> 4 = -116 >> (4%32) = -116 >> 4 = -8

-116 >> 32 = -116 >> (32%32) = -116 >> 0 = -116

-116 >> 36 = -116 >> (36%32) = -116 >> 4 = -8

2.6:类型转换溢出

了解完位运算,来看一个比较实际的问题,看下面的代码:

long a = 8934567890233345621L;
int b = (int) a; //b的值为-1493678507

最终b的值是一个负数,这是由于long型64位,让int型强行接收,会出现位溢出的问题,这个流程如下:

图13

三、位运算在实际项目中的运用

位运算的性能是非常好的,相比运算流程,计算机更喜欢这种纯粹的逻辑门和移动位置的运算,但位运算在平常的业务代码里并不太常见,因为它的可读性不太好,但是我们仍然可以利用位运算来解决一些实际项目里的问题。

比如用来表示开关的功能,比如需求里经常有这种字段:是否允许xx(0不允许,1允许),是否有yy权限(0没有,1有),是否存在zz(0不存在,1存在)

上面只是举例,类似这种只有两种取值状态的属性,如果当成数据库字段放进去的话,太过浪费,如果之后又有类似的字段,又得新增数据库字段,为了只有两种取值的字段,实在是不太值得。

这个时候何不用一个字段来表示这些字段呢?你可能已经猜到要怎么做了:

图14

顶一个int型或者long型的字段,让它的每一个二进制位拥有特殊含义即可,然后按照位运算将其对应的位置上的数值变成0或1,那如何将某个数的二进制位第x位上的数值变成1或0呢?其实这在位图结构里经常用到,就是利用1这个特殊的值作位移运算后再与原值进行位运算,让我们看下这个过程:

把一个数的第2位的字符变成1,现在假设这个数初始化为0,int型,我们把它当成二进制展示出来:

图15

现在如何把这个数的第二位变成1呢?目前是这样做的:

0 | 1 << 1

即原值跟1左移1位后的值作或运算,先来看看1 << 1的结果:

图16

然后拿着图16的结果,跟原数(也就是0)进行或运算:

图17

可以看到,原数的第二位已经被置为1了,它的十进制对应2,其它位的数置为1也大同小异,例如,现在让第6位也变成1只需要:

2 | 1 << 5

即拿着原值(现在为2)跟1左移5位后的数做或运算,这个流程如下:

图18

看完了把某个位置的数值置为1,那如何把某位设置为0呢?我们现在把图18里的结果的第6位重新置回0,目前的做法为:

34 & ~(1 << 5)

即拿着原值(经过上面几步的运算,现在值为32)跟1左移5位按位取反后的数做与运算,来看下这个流程:

图19

经过上面的流程,就可以把原值的第6位变成0了。

那么我们知道了让一个数的二进制位的某位变成0或1的方法,那如何知道一个数的某位上究竟是0还是1呢?毕竟我们业务代码需要知道第几位代表什么意思并且获取到对应位置上的值。

假如我现在想知道十进制int型数34的第6位是0还是1,写法如下:

34 >> 5 & 1

即让原值(34)右移5位后跟1做与运算,来看下这个流程:

图20

由图可以看出,想要知道一个数的第几位是1还是0,只需要将其对应位置上的值“逼”到最后一位,然后跟1相与即可,如果对应位置上的值是0,那么与1相与后的结果一定为0,反之一定为1.

☘️ 总结

到这里已经说完了为什么要用一个数表示那么多开关,以及如何给一个开关位设置对应的开关值,以及如何找到对应开关位的值,有了这些操作,我们再也不需要为这种只有0和1取值的字段新增数据库字段了,因为一个int型的数字,就可以表达32个开关属性,如果超了,还可以扩成64位的long型~

到此这篇关于JAVA位运算的知识点总结的文章就介绍到这了,更多相关JAVA有关位运算的全套梳理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java三种移位运算符原理解析

    这篇文章主要介绍了Java三种移位运算符原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 java移位运算符不外乎就这三种:<<(左移).>>(带符号右移)和>>>(无符号右移). 1. 左移运算符 左移运算符<<使指定值的所有位都左移规定的次数. 1)它的通用格式如下所示: value << num num 指定要移位值value 移动的位数. 左移的规则只记住一点:丢弃最高位(符

  • Java移位运算符详解实例(小结)

    移位运算符它主要包括:左移位运算符(<<).右移位运算符(>>>).带符号的右移位运算符(>>),移位运算符操作的对象就是二进制的位,可以单独用移位运算符来处理int型整数. 运算符 含义 << 左移运算符,将运算符左边的对象向左移动运算符右边指定的位数(在低位补0) >> "有符号"右移运算 符,将运算符左边的对象向右移动运算符右边指定的位数.使用符号扩展机制,也就是说,如果值为正,则在高位补0,如果值为负,则在高位补

  • java位运算加密示例

    创建一个类,通过位运算中的"^"异或运算符把字符串与一个指定的值进行异或运算,从而改变字符串每个字符的值,这样就可以得到一个加密后的字符串.当把加密后的字符串作为程序输入内容,再与那个指定的值进行异或运算,实现把加密后的字符串还原为原有字符串的值. 复制代码 代码如下: import java.util.Scanner;public class Example {    public static void main(String[] args) {        Scanner sc

  • 深入学习java位运算的基础知识

    相信大家和我一样,接触java这门语言的时候就听过java位运算的鼎鼎大名,当然也仅限于听说过.日常开发过程中使用过么?使用位运算的好处是什么? 想要真正理解java位运算,首先要搞清楚,这个"位"代表的含义. 一切的起源:二进制 位:二进制位,简称"位".是二进制记数系统中表示小于2的整数的符号,一般用1或 0表示,是具有相等概率的两种状态中的一种.二进制位的位数可表示一个机器字的字长,一个二进制位包含的信息量称为一比特(bit). 举个栗子: int占4个字节(

  • JAVA位运算的知识点总结

    一.在计算机中数据是如何进行计算的? 1.1:java中的byte型数据取值范围 我们最开始学习java的时候知道,byte类型的数据占了8个bit位,每个位上或0或1,左边第一位表示符号位,符号位如果为1表示负数,为0则表示正数,因此要推算byte的取值范围,只需要让数值位每一位上都等于1即可. 我们来用我们的常规思维来分析下byte类型的取值范围: 图1 如果按照这种思路来推算,七个1的二进制数转换为十进制是127,算上符号位,取值范围应为:-127~+127,但事实上我们知道,byte的取

  • Java位运算知识点详解

    在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+.-.*./.%).关系运算(<.>.<=.>=.==.!=)和逻辑运算(&&.||.!),所以相对来说对位运算不是那么熟悉,本文将以Java的位运算来详细介绍下位运算及其应用. 1. 位运算起源 位运算起源于C语言的低级操作,Java的设计初衷是嵌入到电视机顶盒内,所以这种低级操作方式被保留下来.所谓的低级操作,是因为位运算的操作对象是二进制位,但是这种低级操作对计算机而言是非常简单直接,友好高效

  • Java位运算和逻辑运算的区别实例

    复制代码 代码如下: public class Test {     public static void main(String[] args) {         // 逻辑运算符执行的是短路求值,当左边操作数可以推断出表达式的值,就不再执行 了         int x, y = 10;         if (((x = 0) == 0) || ((y = 20) == 20)) {             System.out.println(y);// 输出10         }

  • Java利用位运算实现乘法运算详解

    目录 前言 正文 十进制相乘 二进制相乘 思路分析 代码实现 总结 前言 在上一篇中,我们介绍了使用位运算实现加法和减法运算,接下来本文主要介绍如何用位运算实现乘法运算,在实现乘法时要用位运算实现,并且不能出现加减乘除任何符号. 之前介绍过一篇如何用位运算实现加法和减法: 如何用位运算实现加减运算? 正文 在用位运算实现之前,我们先来回忆一下小学时,学乘法时用的十字相乘法. 十进制相乘 例如,26 * 15,在进行乘法操作时,我们一般这样算,先用5乘以6得到30,把0写下把3记在一边,再用5乘以

  • Java基础之位运算知识总结

    一.位运算的分类与展现效果 java位运算可以分为左移和右移,其中右移还有无符号右移.   java只对整型位移,可以分为int体系和long体系.int体系包括(byte, short, int, char),long体系只包含long.int体系中进行位运算时,除int类型外都会先转换为int再进行运算.. 无符号右移指的是,向右移动时,左边补位的是0. 一般来说,右移左移常用作乘2n 或者除以2n.(右移除以2n,左移乘以2n) int i1 = 4; int r1 = i1 >> 2;

  • Java必备知识之位运算及常见进制解读

    目录 常见几种进制? Java八种按位运算? HashMap添加元素四步曲用到的位运算? 前奏:HashMap如何添加一个元素? 第一步曲 第二步曲 第三步曲 第四步曲 终曲:为什么HashMap底层源码用这么多位运算? 您好,我是贾斯汀,欢迎又进来学习啦! [学习背景] 学习Java的小伙伴,都知道想要提升个人技术水平,阅读JDK源码少不了,但是说实话还是有些难度的,底层源码实现的原理离不开各种常用的数据结构和算法,很多时候还会用到各种位运算,比如面试必问和工作写烂透了的HashMap,就一个

  • Java利用位运算实现加减乘除的方法详解

    目录 前言 一.常见位运算 1. &运算 2. |运算 3. ^运算 4. ~运算 二.位运算实现加法 三.位运算实现减法 四.位运算实现乘法 五.位运算实现除法 前言 我们经常使用的加减乘除,我们所看到的只是表面的效果,那么加减乘除在底层究竟是怎么实现的?今天就让我们一探究竟.今天用位运算实现的加减乘除不使用任何的加减乘除符号. 一.常见位运算 1. &运算 &运算二进制每一位全1为1,否则为0 public static void main(String[] args) { i

  • Java利用位运算实现比较两个数的大小

    目录 题目要求 主要思路 方法1(不考虑溢出) 方法2(考虑溢出情况) 题目要求 如何不要用任何比较判断符(>,==,<),返回两个数( 32 位整数)中较大的数. 主要思路 方法1(不考虑溢出) 要比较 a 和 b 的大小,因为不能用比较符号,我们可以通过 a - b 的符号位来判断,如果 a - b 的符号位是 1,说明 a - b < 0,则 a 小,否则 a 大或者 a 和 b 相等. 如何判断一个数的符号位是 0 还是 1 ? 由于是 32 位整数,所以如果将一个数右移 31

随机推荐