我们必须要了解的Java位运算(不仅限于Java) - 陈咬金 - 博客园

基本概念

1、当前常见的CPU位数是32位和64位,所谓32位处理器就是一次只能处理32位,也就是4个字节的数据,而64位处理器一次则能处理64位,即8个字节的数据。

2、一字节(1Byte)等于8位(8bit),位是计算机存储数据的最小单位,也就是计算存储的数据是一系列二进制位信息。每个位用0或1表示。(大B和小b的区别需注意哦)

3、为什么一个字节是8位?现在通用的说法是8位可以涵盖所有的字符编码,即ASCII编码。但历史上也存在过4位或者7位为1Byte的场景,只是在后续的字符集标准统一中逐渐被8bit所替代。除了历史原因之外,还有数据存储的需要(必须要能在一个字节内表示超过100种状态,包括常用数字,大小写字母等),再加上早期计算机存储价格昂贵,所以8bit也是在当时符合二进制特性的必然结果。

4、二进制加减运算,加法:0 0=0,0 1=1,1 0=1,1 1=10 ,逢2进1。

5、二进制转十进制及十进制转二进制。

上述列出了一些基本概念和对应的可参考链接,建议先优先了解一下。尤其是二进制和十进制的互相转换以及二进制的加减法规则,在后面都会具体涉及到。

机器数、真值

人类用十进制完全是因为我们有10个手指头。如果有一天你看到一个外星人,它只有4个手指头,那么他使用的一定是四进制,如图所示:

java 中位运算的应用场景(我们必须要了解的Java位运算)(1)

如果能看明白上图,说明你已经明白了进制和手指头的关系了。现代的计算机内部使用门电路,它们只能表示0或者1两个状态。如果计算机是一个人,那么他只有两个手指头,所以它使用二进制。是的,就是这样,所谓的进制,实际上并不是什么神秘的东西,只是关于数的一种表示方式而已了。门电路概念

机器数

机器数分为:无符号数和有符号数两种。

一个数在计算机中的二进制表示形式, 叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0,负数为1。

比如,十进制中的数 3 ,计算机字长为8位,转换成二进制就是00000011。

如果是 -3 ,就是 10000011 。那么,这里的 00000011 和 10000011 就是机器数。

真值

因为第一位是符号位,所以机器数的形式值就不等于真正的数值。

例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。

所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

例:0000 0001的真值 = 000 0001 = 1,1000 0001的真值 = –000 0001 = –1

原码、反码、补码

为了妥善的处理数据运算过程中符号位的问题,于是就产生了把符号位和数值位一起编码起来表示相应的数的各种表示方法。例如我们熟悉的原码、反码、补码、移码等。通常将未经编码的数称为真值,编码后的数称为机器数或者机器码。

计算机当前所使用的机器数是采用的补码的方式。在探求为何机器要使用补码之前, 让我们先了解原码, 反码和补码的概念。对于一个数, 计算机要使用一定的编码方式进行存储, 原码, 反码, 补码是机器存储一个具体数字的编码方式。

原码

原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:

[ 1]原 = 0000 0001 [-1]原 = 1000 0001

第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:

[01111111,11111111]

转换为真值后即:

[127,-127 ]

原码是人脑最容易理解和计算的表示方式。原码也可以理解为最原始的机器码。

反码

反码的表示方法是:正数的反码是其本身,负数的反码是在其原码的基础上, 符号位不变,其余各个位取反。

[ 1] = [00000001]原 = [00000001]反 [-1] = [10000001]原 = [11111110]反

可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算。

补码

补码的表示方法是:正数的补码就是其本身,负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后 1. (即在反码的基础上 1)。

[ 1] = [00000001]原 = [00000001]反 = [00000001]补 [-1] = [10000001]原 = [11111110]反 = [11111111]补

对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值。

互相转换、注意事项

注意:我们在最初的基本概念中提到了二进制和十进制的转换方式。但是需要知道的是,一个数在计算机中的二进制表示方式叫做机器数。

而机器数是有符号的,我们将对应的最高位存放符号,0位正数,1位负数。所以机器数并不等于二进制。

由此我们才引出了真值的概念,例如:符号数 10000011,在十进制中对应的值为131,但是在机器数中,由于1表示负数,所以其真正数值是-3,而并不是131。

符号数:10000011,我们需要先消除其符号位的影响,将其调整为正数:00000011,此时将正数转换为10进制为3,然后再加上最初的符号位表示负数,所以为-3。

需知道的是,二进制仅是以2为基数的计数方式而已,在二进制中是无法区分正数和负数的。而如果想区分正数和负数那么必须在二进制的最高一位中用0和1来表示符号以此来表示正负。那么此时最高位的0和1已经不是二进制的一种计数方式了,而只是一种符号的标识,该符号的标识则是万万不可以直接参与二进制的计算的。

当我们用最高位中的0和1来表示正负时,此时该二进制的数已经不再是符合二进制规则的数了,而是机器数。

二进制仅仅是以2为基数的计算方式,此时最高位的值已经不再是通过2位基数的计算方式而计算出来的,而只是一个表示正负的标识了,所以此时该数值则已经不再是符合二进制规则的数了,而是机器数。

所以,也只有机器数才可以表示正负。而机器数所对应得到的结果则是真数,而并不是10进制数。

如果此时抛出来一个问题:10000011 转换为对应的10进制,那么对应的结果则是131,但如果是转换为真数,则是-3。同样的,如果是将00000011 转换为对应的10进制,则是3,而如果转换为真数,则也是3。

所以当我们看到一个以8位数所表示的二进制数时,则一定要确认该二进制数是表示机器数,还是二进制数?是转换为10进制数,还是真数。其中最大的区别则是,最高的符号位到底是参与二进制的运算,还是仅仅表示符号位。

机器数转真数:10000011 >消除最高位1的影响先转为正数>00000011>再将该正数以二进制的方式转为十进制为> 3 >此时再将最初的符号添加回来>3调整为-3。

二进制数转10进制数:10000011 >无需消除最高位1的影响直接转为正数>10000011>再将该正数以二进制的方式转为十进制为 > 131>无需添加符号位>131仍然为131。

所以可知,真数和10进制数最大的区别则是是否忽略最高位,在确认完是否忽略最高位,得到最初的正数后,该正数的计算方式,则和二进制转十进制的方式完全相同。其实就是二进制转十进制。哈哈。


那么在注意了二进制数和机器数以及十进制数和真数的区别之后。我们则需要注意的另外一个问题则是:

机器数是包含原码、反码、补码。其中三者之间是可以互相转换的。原码转反码转补码,这块上面已经说明过了,而补码则也可以通过想反的方式重新转换为原码。

而此处需要知晓的则是,反码和补码当然是不可以直接转为真数的,而必须要通过原码才可以进行转换。

这个其实也没什么问题,毕竟原码经过层层转换后得到补码,而补码又可以直接转换为真数,当然不可能了。

接下来则是,为什么原码需要转换为补码,为什么原码不是计算机的计算方式,而是补码?

本文原创地址为 https://www.cnblogs.com/zh94/p/16195373.html

原创声明:作者:陈咬金、 博客地址:https://www.cnblogs.com/zh94/

为什么补码才是计算机的真正计算方式

现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相同:

[ 1] = [00000001]原 = [00000001]反 = [00000001]补

所以不需要过多解释. 但是对于负数:

[-1] = [10000001]原 = [11111110]反 = [11111111]补

可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?

首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减。但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数, 即: 1-1 = 1 (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了。

于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码:计算十进制的表达式: 1-1=0

1 - 1 = 1 (-1) = [00000001]原 [10000001]原 = [10000010]原 = -2

如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数。为了解决原码做减法的问题, 出现了反码:计算十进制的表达式: 1-1=0

1 - 1 = 1 (-1) = [0000 0001]原 [1000 0001]原= [0000 0001]反 [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0

发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 虽然人们理解上 0和-0是一样的, 但是0带符号是没有任何意义的. 而且会有[0000 0000]原和[1000 0000]原两个编码表示0。

于是补码的出现, 解决了0的符号以及两个编码的问题:

1-1 = 1 (-1) = [0000 0001]原 [1000 0001]原 = [0000 0001]补 [1111 1111]补 = [0000 0000]补=[0000 0000]原

这里说明一下,二进制想加:0000 0001 1111 1111 = 1 0000 0000,但由于是8位数,所以最终的值为 0000 0000。

这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:

(-1) (-127) = [1000 0001]原 [1111 1111]原 = [1111 1111]补 [1000 0001]补 = [1000 0000]补

由于我们使用原码来表示正时,最大值为:01111111,最小值为:11111111,所以直接转换为对应的10进制后的结果为,127,-127。而此处使用补码后,由于补码的规则是,首位不变,其它反转,并 1。所以(-1) (-127) 刚好为-128。

使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, 127], 而使用补码表示的范围为[-128, 127]。

因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-2的31次方, 2的31次方 - 1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值。

Amazing,我们在上面最初使用原码进行加法运算时,由于我们人脑还需要先判断一下最高位的符号后,才能进行二进制运算,然后再添加上对应的符号位。而采用补码后,直接将对应的符号位也参与运算,将补码的数值直接相加,得到的竟然刚好也就是二进制转换后的结果。这样一来,计算机的基础电路设计就可以更加简单,而无需关注符号位的问题,仅需要按照二进制的加法法则执行即可。简直完美。所以这也是补码作为计算机的真正计算方式的原因之一!

但,补码后所得到的值想加刚好就是直接二进制的值相加后的结果,真的是就刚好这么巧吗?其实不然,背后还蕴含这很有意思的数学原理,详情可参考:深入理解原码、补码 & 数的机器码表示 & 机器码原理 & 原码、补码原理

位运算

文章最顶部基本概念处列到了“10进制2进制互转”,以及“2进制加减法”对应的相关链接。此处再推荐几个在线计算的网址,便于将自己的计算结果进行二次验证:在线原码、补码、10进制互转 & 在线进制转换 & 在线二进制加减法

接下来则开始涉及到位运算了。

概念

什么是位运算?我们先来看下百度百科的概念:

程序中的所有数在计算机内存中都是以二进制的形式储存的。位运算就是直接对整数在内存中的二进制位进行操作。

百度百科中所给的解释是具备歧义性的,按照百度百科的解释,直接对整数在内存中的二进制位进行操作就是位运算的话,那么使用二进制数进行算术运算法( ,-,*,/ )岂不是也属于位运算?

我们再来看下维基百科所给的概念解释:

位操作是程序设计中对位模式或二进制数的一元和二元操作。在许多古老的微处理器上,位运算比加减运算略快,通常位运算比乘除法运算要快很多。在现代架构中,情况并非如此:位运算的运算速度通常与加法运算相同(仍然快于乘法运算)。

维基百科中针对位运算的概念相对合理,通过维基百科中的概念我们可以很明显的区分到,位运算是和加减乘除这些算数运算符是不同的。不同的CPU针对位运算的操作是较快于(乘/除)法运算的。

所以这也才是我们需要了解位运算的真正原因,那就是CPU处理器针对位运算符的计算是快于算术运算符的!在特定的编码场景下使用位运算的执行速率则是远远大于算术运算的!

网络上针对位运算较多的内容解释是:位运算是直接对整数在内存中的二进制位进行操作,所以位运算更加节省内存、提高运算效率等等的。其实这是很不严谨的说法,很容易误导大家对位运算的理解,因为所有的整数最终在计算机中都是二进制数,那么所有对整数的运算岂不是都是位运算?当然不是啦。且,位运算真正快的原因也并不是因为节省内存,而主要是因为CPU对位运算的支持!和内存并没有较大关联。

为何位运算CPU执行速率更快

程序中的基本运算包含:

其中按位运算和移位运算均属于位运算的范畴。

位运算的具体执行逻辑,我们下面会详细说明。这里先以位运算中的 与 运算符 “&” 来简单说明下位运算的主要执行逻辑:& 与运算符的运算规则是:两个位都为1时,则结果为1。如:3&5 即 0000 0011& 0000 0101 = 0000 0001,因此 3&5 的值得1。

根据与运算符的规则可知,位运算的整体执行逻辑实际是较为简单的,更多的是进行位数的比较,从而得到一个结果,这种较为简单的运算逻辑,则对于CPU处理器来说,在电路的设计中则也会更加简单许多,以下为与运算符所涉及到的CPU电路图:

java 中位运算的应用场景(我们必须要了解的Java位运算)(2)

而对于一个除法来说呢,在CPU中所对应的电路图设计则是这样的:

java 中位运算的应用场景(我们必须要了解的Java位运算)(3)

可以看到,整个CPU电路图的设计复杂了不止一个层级,所以这也就是为何位运算比我们人常用的算术运算更快的直接原因了。因为对于整个CPU的执行逻辑来说从设计层面就复杂了很多。


当然,提到CPU的电路图设计,就不得不提到对应的CPU中晶体管的特性了,而晶体管中所涉及到的开关(01)的特性也就构成了逻辑电路,从而构成了与门、或门、非门、异或门等电路特性。这块内容,可以参考如下链接:位运算中隐藏的CPU秘密程序中的位运算于基本电路关系程序中位运算于基本电路-知乎四位计算机的原理及实现位运算的理解程序中的取余是如何实现的二进制乘除法的实现

CPU中的电路设计和数学的算法实现有着很精妙的联系,计算机前辈的力量是无穷的。那到了这里,我们也就知道了程序中基本运算在执行速率上的真正差别实际上是在CPU这一层级的,了解了这些之后,我们也就可以接着开始说明位运算符的真正执行逻辑了。(位运算仅需进行01的比较,和移位等简单的逻辑操作,基本上和直接执行二进制的相加规则一样,在电路设计和逻辑上均更加简单,而乘除求余则在电路的设计中较为复杂)

本文原创地址为 https://www.cnblogs.com/zh94/p/16195373.html

原创声明:作者:陈咬金、 博客地址:https://www.cnblogs.com/zh94/

位运算符

首先需知道的是,计算机中执行位运算,肯定是采用的补码的方式进行的位计算哦,所以对于真值为负数的情况下,必须先转为补码才能进行计算。

符号

描述

运算规则

&

两个位都为1时,结果才为1

|

两个位都为0时,结果才为0

^

异或

两个位相同为0,相异为1

~

取反

0变1,1变0

<<

左移

各二进位全部左移若干位,高位丢弃,低位补0

>>

右移

各二进位全部右移若干位,对无符号数,高位补0。各编译器处理方法不一样,有的补符号位(算术右移),有的补0(逻辑右移)

& 与运算符

运算规则:两位同时为1,结果才为1,否则结果为0。

0&0=0 0&1=0 1&0=0 1&1=1

例如:2 & -2

java 中位运算的应用场景(我们必须要了解的Java位运算)(4)

注意:负数当然是按照补码的方式来进行位计算哦。

& 与运算符的用途:

根据与运算符的计算特性,我们常用的使用方式有:1、判断奇偶数我们知道,按照二进制和十进制(除二取余)的换算方式,如果是偶数的情况下,换算为二进制后末位必然是0,如果是奇数则末位为1。比如:2 >10 ,3 > 11,100 >1100100,121 > 1111001。

所以,我们按照与运算符的运算规则,使用 if(a & 1)1表示为奇数,if(a & 1)0,则表示为偶数。如:121 & 1 = 1111001 & 0000001 = 00000012 & 1 =10 & 01 =00

使用与运算符的方式,则完全可以替代掉:if (a % 2 == 0) 来判断奇偶数的方式,且位运算符由于CPU的支持,执行效率也更高。

关于二进制转十进制的方式,参考基本概念中URL即可。

2、取一个数的指定位比如取数 X=1010 1110 高4位,只需要另外找一个数,令 Y的高4位为1,其余位为0,即Y=1111 0000,然后将X和Y进行与运算,(X & Y = 1010 1110 & 1111 0000 = 1010 0000)即可得到X 的指定高4位。

如果想获取X的低4位的数,则将Y的低4位为1,其余位数为0即可,(X & Y=1010 1110 & 0000 1111=0000 1110)便可得到X的指定低4位。

| 或 运算符

运算规则:两个位都为0时,则结果为0。否则则为1。

0|0=0 0|1=1 1|0=1 1|1=1

例如:2 | -2 =-2

java 中位运算的应用场景(我们必须要了解的Java位运算)(5)

| 或运算符的用途

首先与和或是两个相反的概念,所以上述所提到的与的用途,在这里只要略作改造就也适合于或运算符。但是目的是一样的,所以对应相同用途的场景,这里不再赘述。

1、常用来对一个数据的某些位设置为1比如将数 X=1010 1110 的低4位设置为1,只需要另找一个数Y,令Y的低4位为1,其余位为0,即Y=0000 1111,然后将X与Y进行按位或运算(X|Y=1010 1111)即可得到。

同样的,使用 & 运算符,则可以方便的将某些位设置为 0,如上述的X & Y,将X的低4位设置为0,则X & Y = 1010 1110 & 1111 0000 = 1010 0000。

以上则说明 & 和 | 灵活运用,其实是可以达到相同效果的。但实际使用中则并不然,首先对于上述低4位设置为1的场景,我们只需要找一个Y的数,令Y的低4位为1,其余位为0,这样一个数是很好找的,是一个固定的数,比如:15。转换为二进制后为1111。

但如果使用 & 运算符来面对这个场景,则需要找一个Y,Y的第四位为0,其余位置为1,这样一个数则很难找,并且随着位数的不同,值也是不断变换的,比如:1111 0000=240,但如果是12位数,1111 1111 0000=4080。所以如果使用 & 运算符来在该场景下则是没有 | 运算符更加方便的。

尽管 & 和 | 的规则相反,可灵活变更,但针对特定场景下,还是使用特定的运算符效果更佳O。

^ 异或运算符

运算规则:两个位相同为0,相异为1。

0^0=0 0^1=1 1^0=1 1^1=0

例如:2 ^ -2

java 中位运算的应用场景(我们必须要了解的Java位运算)(6)

^ 异或运算符用途

1、交换两个数

a=a^b; //a=a^b b=a^b; //b=(a^b)^b=a^0=a a=a^b; //a=(a^b)^(a^b^b)=0^b=0

交换两个数的原理,即上面注释所写内容。

不使用位运算的方式交换两个数,则需要定义一个中间变量C,来承接其中的一个数的交换,对于Java来说,定义一个新的Int 变量C,则表示内存中需开辟一个4字节的空间。

所以根据服务特性来选择合适的方式即可,对内存使用率有强要求则使用位运算,没要求则都可以。

static void swap(int a, int b) { int c = a; a = b; b = c;} static void swapBit(int a, int b) { a = a ^ b; b = a ^ b; a = a ^ b;}

~ 取反运算符

运算规则:0变1,1变0。

java 中位运算的应用场景(我们必须要了解的Java位运算)(7)

<< 左移运算符

运算规则:将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。

设 a=15,即二进制数00001111,左移2位得00111100,即十进制数60。

左移一位相当于该数乘以2,左移2位相当于该数乘以2^2=4。上面举的例子15<< 2=60,即乘了4。(结论:左移n位等于乘以2的n次方)

设 a=-46,补码后为,1010 1110,a = a<< 2 将a的二进制位左移2位、右补0,即得a=1011 1000,转换为真值后为-56。

设 a=110,补码后为:0110 1110,a = a<<2 将a 的二进制位左移2位,右补0,即得 a=1011 1000,转换为真值后为184。

以此可知,左移n位等于乘以2的n次方,该结论仅适用于该数左移时被溢出舍弃的高位中不包含1的情况,如果溢出的高位中包含1,则不符合上述结论。

>> 右移运算符

运算规则:将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。例如:a=a>>2 将a的二进制位右移2位,左补0 或者 左补1需要看被移数是正还是负。

设 a=16,补码后为00010000,a = a<<2 将a的二进制位右移2位,左边补0,即得a=00000100,转换为真值后为4。

设a=-16,补码后为11110000,a = a<<2 将a的二进制位右移2位,左边补1,得到a=11111100,转换真值后为-4。

结论:右移运算符,操作数每右移一位,相当于该数除以2。

本文原创地址为 https://www.cnblogs.com/zh94/p/16195373.html

原创声明:作者:陈咬金、 博客地址:https://www.cnblogs.com/zh94/

Java位运算

位运算本身就是处理器、计算机自身所提供的能力,所以针对位运算的使用,实际上是不限于任何编程语言的,此处之所以以JAVA为例,主要是因为本人常用的开发语言是JAVA,哈哈。

针对JAVA中位运算的使用,实际上在JDK中有这很丰富的案例,比如:

1、JDK中线程池ThreadPoolExecutor的实现当中使用Integer类型(4字节,32位)其中高3位保存线程池状态,而低29位保存线程池内有效线程数量。

2、比如JDK的HashMap中使用位运算的方式将初始化容量的数值,快速的转换为2的n次幂。以及计算key的hash时,根据该key的hashCode结果,再将该hashCode的高16位和低16位通过位运算的方式进行混合,以此降低hash碰撞的概率等等。

3、比如我们直接打开常用的Integer类的源码,也会发现里面有大量的位运算的使用。

此处仅是为了通过上述举例的方式来以此说明位运算在Java生态中的使用程度,实际上是非常丰富的,并且由于位运算独特的计算特性,在某些相对特殊的代码场景下,使用位运算会意想不到的将问题给简单化。

如果想了解更多在JAVA中的使用场景和案例,建议大家直接翻看各种源码即可。

以上是一些举例,以下再做一些小的补充说明:

在Java当中的位运算,是只针对Int类型和Long类型有效(java中,一个int的长度始终是32位,也就是4个字节,它操作的都是该整数的二进制数,Long则是64位,表示8字节。),而对于byte,char,short,当为这三个类型时,JVM会先把他们转换为Int类型后再进行操作。

使用 toBinaryString() 可以将对应的十进制转为对应的补码。

System.out.println(Integer.toBinaryString(10));//1010 System.out.println(Integer.toBinaryString(-10));//11111111111111111111111111111101 System.out.println(Long.toBinaryString(10));//1010 System.out.println(Long.toBinaryString(-10));//1111111111111111111111111111111111111111111111111111111111110110

如上代码可知,Integer和Long转换为补码时,Integer为32位,Long是64位。实际上上述的基本类型32位还是64位,均是直接定义在源码当中的,感兴趣直接看对应的Integer和Long的源码即可。

其它参考链接

以下参考链接,仅供参考,部分链接中的内容,可能会具备一些歧义,请读者自行分辨。

祝福各位读者所有看到的知识,都可以最终成为自己的知识!撒花!✿✿ヽ(°▽°)ノ✿!

位运算教程位运算介绍java位运算位运算技巧位运算常用方式

我们必须要了解的Java位运算(不仅限于Java) - 陈咬金 - 博客园

,