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

原创 2015年07月09日 07:29:47

能抓住和定位单个原子的机器将能制造任何东西…
当然,一次一原子地造个大物体会很慢。
——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次方,记xexp,使用乘法需要计算exp-1次。而xexp可以形成x1 x2 x4x8 x16……次方,如x17= x16x。通过exp移位,仅需要计算x1 x2 x4……次方和所需要的有效位,如x17仅需要计算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);
    }



版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

HashMap深度解析(二)

HashMap有两个参数影响其性能:初始容量和加载因子。默认初始容量是16,加载因子是0.75。容量是哈希表中桶(Entry数组)的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动...
  • ghsau
  • ghsau
  • 2013-11-26 10:46
  • 36571

使用 Salt + Hash 来为密码加密

转:http://www.cnblogs.com/jfzhu/archive/2012/12/20/2825802.html 我们知道,如果直接对密码进行散列,那么黑客可以对通过获得这个密码散列值,...

Java中位运算(移位、位与、或、异或、非)

public class Test { public static void main(String[] args) { // 1、左移( << ) // 00...

设计模式六大原则(4):接口隔离原则

定义:客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。 问题由来:类A通过接口I依赖类B,类C通过接口I依赖类D,如果接口I对于类A和类B来说不是最小接口,则类B和类D...

秒杀系统的一点思考

秒杀系统难点往往是短时间内对数据进行读写,然后造成读写上的一些冲突,甚至锁非常严重.当然知道难点在哪里,我们自然就有办法解决,人类智慧无限,办法总比困难多. 简单说来也就两点,限流和提升关键点的性能....

Java位运算原理及使用讲解

前言 日常开发中位运算不是很常用,但是巧妙的使用位运算可以大量减少运行开销,优化算法。举个例子,翻转操作比较常见,比如初始值为1,操作一次变为0,再操作一次变为1。可能的做法是使用三木运算符,判断原始...

关于java代码中的位运算的使用

最近在查看java源码, 其实大量用到了位运算,在看到下面的代码时困惑了 long msb = 0; long lsb = 0; for (...

Android 性能优化之内存泄漏的检测与修复

转载请注明本文出自 clevergump 的博客:http://blog.csdn.net/clevergump/article/details/52013873, 谢谢!在 Android 开发中,...

[Android] 触屏setOnTouchListener实现图片缩放、移动、绘制和添加水印

本文主要讲述使用触屏实现图片缩放、移动、添加水印等功能,所以该篇文章主要通过setOnTouchListener监听实现该功能,如何使用RelativeLayout进行布局,MotionEvent.A...

算法之二分法查找

二分法检索(binary search)又称折半检索,二分法检索的基本思想是设字典中的元素从小到大有序地存放在数组(array)中, 首先将给定值key与字典中间位置上元素的关键码(key)比较,如...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)