《编程导论(Java)·12.1基本位运算》

能抓住和定位单个原子的机器将能制造任何东西…
当然,一次一原子地造个大物体会很慢。
——Eric Drexler《Engines of Creation: The Coming Eraof Nanotechnology》

     位运算(Bitwise operations)在bit层面上对操作数进行按位操作。集成电路的逻辑门(logic gate)能够在bit上执行与、或、异、反操作,因而位运算在汇编语言中被广泛使用。通常它们的效率比数值的加减运算稍快,而比数值的乘除运算则有显著提高。高级语言如Java中,在通常情况下不会考虑如此低层的操作,只有在特定场合或某些关键算法实现时,才使用位运算。
     本章将介绍位运算的各种操作及其应用。

12.1基本位运算

Java只能对int、long进行按位操作。如果操作数短于int,如byte、char,它们将被自动提升为int然后再参与运算。

Java位运算操作符分成两大类:位-逻辑运算符和移位运算符。


12.1.1 位-逻辑运算符

Java中的按位运算,使用了重载的运算符:&(AND、与)、|(OR、或)、^(XOR、异)。这三个重载的操作符,根据操作数的数据类型决定它们是逻辑运算还是位运算。如5&2是位运算而true|(3<2) 是逻辑运算。而~(tilde,求反)将一个数的二进制补码按位求反。

每一个bit只可能是0和1,对应地,boolean值只有false和true。因此按位运算与逻辑运算具有本质上一致的真值表,如表12-1所示。比较[表格3-4真值表],以1对应于true、0对应于false,非常容易换算出彼此的真值表。

表格 12‑1 真值表

p

q

p & q

 p ^ q 

p| q    

~p

1

1

1

0

1

0

1

0

0

1

1

0

0

1

0

1

1

1

0

0

0

0

0

1

位运算之所以称为按位运算,是因为两个操作数的对应位相操作,不会对其他位造成影响(没有算术加法的进位等规则)。

练习12-1.:请比较位运算和逻辑运算的所有操作符。提示:&&和||、逻辑操作的!(非)与不等于操作符!=保持一致而不与位运算的~重载。

练习12-2.:复习0.1.2二进制补码,写出97、-5的二进制补码。

 

1. 与或反

按位与、按位或和按位非/反是基本的位操作。Java 7所引入了二进制文字,极大地增强了位操作代码的可读性。如果求10& -5 ,必须非常熟悉二进制补码,才知道得数;如果准备计算0b1010& -0b0101,较容易看出操作结果。

正数如10的二进制文字,可以有若干前导0,因而0b00_1010和0b1010均为10;而负数如-5,通常表示为-0b101,若表述为0b1111_1111_1111_1111_1111_1111_1111_1011就太长。

10         =0b1010 

-5    = 0b...1111_1011

10& -5 =      0b1010,即10。

【按位与、按位或和按位非/反的基本操作,同学们自己在练习12-3中 自己玩】

如果一个操作对操作数的位很敏感,则可以利用计算机存储数值的二进制特点,通过位运算得到高效而简洁的代码。【有很多的例子,后面补充一些】例如判断int i是否是2的幂次方。2的幂如16的二进制表示为0b0001_0000,仅仅有一位是1,而16-1的二进制表示为0b0000_1111,两者相与为0。因为0& (0-1)也等于0,所以要排除,添加&&(v!=0)。

例程 12‑1判断x的绝对值是否2的幂
package algorithm.bitOp;
public class BitOpDemo{    
    public static boolean 是否是2的幂(int x){
        int v= (x>=0) ? x : -x;
        return  ( (v & (v - 1)) == 0  &&(v!=0) );
    }    
}


练习12-3.:使用algorithm.bitOp. BitOpDemo的方法“与或异反操作()”,了解这四个运算符。

练习12-4.:判断int变量i的值是奇数,可以boolean isOdd = (i % 2 = = 1),请用位运算进行判断。

练习12-5.:编写方法complement(int),为任意int数求其补码。

补充判断一个数能否被3整除

在例程3-2中有方法

    privatestatic boolean is3X(int n){

        return( n%3 == 0);

    }

现在要求改变思路——使用位运算。如果一个数的每个位相加之和能被3整除,则这个数就可以被3整除。但是,这是十进制中的规律,那么在二进制中,需要我们找到其特点——这是使用位运算的关键。 如果所有的偶数位出现1的次数为 even_count, 奇数位出现1的次数为 odd_count,两者之差如果是3的倍数,那么这个数就是3倍数。( 为什么有这个特点,怎样找到这个特点的?我不知道!)代码中要用到移位,后面给出。

 

2. 掩码

与、或运算常用于对某个数x的特定位进行设置和提取。表达式mask | x或mask & x中,与x运算的数称为掩码。令b表示一个位。

置1:按照真值表,1|b =1、0|b =b,所以mask | x能够按照mask为1的位将x的对应位设置为1,其他位不变。例如需要设置x的倒数1、倒数6位设置为1,其他位不变,则可以令mask为0b10_0001。

清0:按照真值表,0&b =0、1&b =b,故mask & x能够按照mask的0将x对应的位设置为0。

取位:不管置1还是清0,x都有一些位保持不变。假设需要提取int x的最低12位,可以使其他位清0,即求x & 0xFFF(清0型取位);也可以使其他位为1,即x | 0xFFFF_F000(置1型取位),但不方便。

掩码通常参与移位运算,见下一节。

练习12-6.:如何提取int x的最低字节;如何将x的倒数1、3,5,7位置1。令int mask = 0x80000000,mask & x的作用是什么?

练习12-7.:何谓置1、清零。

 

3. 异或

令b表示一个位。按照真值表,1^b = ~b、0^b = b。因而可以推导出许多有趣的规律用于编程。例如对于int x,有性质:

²       x^ x = 0

²       x^ x^ x = x

²       x^ (-1) = ~x。

一个著名的例子,将[11.1说明] algorithm.sorting.IntSort的换位操作方法swap(int[] a ,int one, inttwo)以异或实现,其特点是不需要过渡的临时变量。

例程 12‑2异或换位

package algorithm.bitOp;
public class XOR{
    public static void swap(int[] a ,int i, int j){
        a[i] = a[i]^a[j];
        a[j] = a[i]^a[j];
        a[i] = a[i]^a[j];
    }
}

异或换位的原因,记a[i]为x,a[j]为y。

第一步:x起临时变量作用,记作x’= x ^y。

第2步:y’=x’ ^y = (x^y)^y=x^(y^y)=x^0=x;

第3步:x=x’^y’= (x^y)^ x=x^x^y=y。

练习12-8:根据性质x^ x = 0,说明x= a ^ b ^ x等价于切换语句(x仅取值a、b):

(1) if(x == a) x=b; else x =a;

(2) x = (x == a) ? b:a;

 

12.1.2 移位运算符

移位意味着将一个数的各二进制位序列向左或右移动(shife、move)若干位,移出的位自然地被抛弃,而移位运算的关注点是如何填充空缺位

右移有两种,>>操作符为算术右移,它保持原数值的算术符号,左边用最高位(符号位)填充。>>>操作符为逻辑右移,左边以0填充。因而算术右移>>可以用于替代正负整数的除法, 例如x>>1等价于x/2、x>>n等价于x/2n。逻辑右移x>>>并不重视x二进制的补码是否是一个有效的算术运算值【一些时候,Java中缺少无符号数真的不方便】。

例程12-3中,以0x8000_0000为掩码mask,提取x的最高位。通过mask的逻辑右移逐一提取x的一个位并打印该位。Java中int有32位,从最高位(符号位)移到最低位只需要31次。为了方便每输出4bit打印一个空格,i初始化为1。【在学习基本按位与、按位或和按位非的时候,这个工具 showBits(int x)有点用】

例程 12‑3打印int数值的位

package algorithm.bitOp;
public class BitOpDemo{
    private static final int iSIZE = 32;//integer
    public static void showBits(int x) {
        int mask = 0x8000_0000;
        for (int i = 1; i <=iSIZE; i++) {
            byte bit = (byte) ((mask & x) != 0 ? 1 : 0);            
            System.out.print(bit); 
            if (i % 4 == 0) { System.out.print(" "); }
            mask >>>= 1;
        }
}
}


例程12-4使用移位运算进行求幂,计算并返回一个double数的int次方,记x exp,使用乘法需要计算exp-1次。而x exp可以形成x 1 x 2 x 4x 8 x 16……次方,如x 17= x 16x。通过exp移位,仅需要计算x 1 x 2 x 4……次方和所需要的有效位,如x 17仅需要计算5+2次而非16次。

例程 12‑4求一个double数的int次方

package algorithm.bitOp;

public class ShifeOpDemo{

    public static double power(double d, int exp) {

        if (exp < 0) {

            return 1 / power(d, -exp);

        }else if(exp == 0){

            return 1;

        }

        double value = 1;

        while (exp > 0) {

            if ((exp & 1) == 1) value *= d;           

            exp >>= 1;// exp每右移一位,x 求平方一次。

            d *= d;

        }

        return value;

    }

}

左移运算符<<,高位左移后溢出被舍弃,右端补0。因此算术左移运算要注意符号位的变化。在不产生溢出条件下,正数左移如i<<2等价于i*4;负数则以其绝对值左移再取负数。

例程12-5统计String的byte[]表示(01串)中1的个数。可以按照例程12-3将mask逻辑右移而提取x的一个位,这里将x左移而提取x的一个位。

例程 12‑5统计01串中1的个数

package algorithm.bitOp;

public class ShifeOpDemo{

    public static void getOnes(String s){

        byte[] sequence = s.getBytes();

        int count = 0;//1的个数

        int total = sequence.length * 8; //总个数

        for (byte x:sequence) {

            for(int i = 0;i<8;i++){//对字节的每一位操作

                boolean bit = (x & 0x80) == 0;//提取最高位,为0则bit为false。

                if(!bit) count ++;//1的个数

                x<<=1;//高位左移后溢出被舍弃,右端补0。

            }

        }

        System.out.println( count );

    }

}

在长期的汇编语言或C语言编程中,人们积累了大量位运算的技巧,位运算要比乘、除法运算效率高很多。在阅读一些算法的源代码时,经常会见到程序员使用这些位运算技巧。

练习12-9.给定byte[] bs,长度为8的倍数。请将bs的每个byte的第3位取出并保存到byte b中。

练习12-10.:给定字符串s,它由01组成,如"00011101…",将它们填入int n的第30位开始各位中。如果s长度不足,则n的低位补0。提示:定义int getBit(),完成从s中提取一个字符并输出一个0或1。

 


    /**
     * 如果所有的偶数位出现1的次数为 even_count, 奇数位出现1的次数为 odd_count,
     * 两者之差如果是3的倍数,那么这个数就是3倍数。
     */
    public static boolean is3X(int n){
        int odd_count =0,even_count =0;
        if(n < 0)   n = -n;
        if(n == 0) return true;
        if(n <= 2) return false;
        while(n!=0){
            if((n & 1)==1) odd_count++;
            n = n>>1;
            if((n & 1)==1) even_count++;
            n = n>>1;
        }
        return  is3X(odd_count - even_count);
    }



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值