位运算符

Java还提供另外一组运算符–位运算符来扩展Java解决问题的范围.位运算符可以用于long,int,short,char或byte类型,不能用于boolean,float,double或类类型.他们被称为位运算符是因为他们用于测试,设置或移动组成整数值的位.运算符对于广泛应用的系统级程序设计任务十分重要,这些任务需要从设备查询或生成状态信息.下表列出了位运算符:
运算符
结果
&
按位与
|
按位或
^
按位异或
>>
右移
>>>
无符号右移
<<
左移
~
1的补码(一元非)
1.位运算符的与,或,非,异或
位运算符与,或,非,异或分别为 &,|,~,^,下表显示了每种运算的结果,用1或0来表示:
P
Q
P&Q
P|Q
P^Q
~P
0
0
0
0
0
1
1
0
0
1
1
0
0
1
0
1
1
1
1
1
1
1
0
0
可以把按位与想作一种把二进制设置为0的操作,即两个操作数中任何位为0的话,都会导致相应位输出结果为0:
    1101 0011
& 1010 1010

    1000 0010
下面的程序通过把第6位重置为0使小写字母都变为大写字母,对&进行了演示.根据unicode/ASCII字符集定义,小写字母与大写字母的区别只是前者比后者整整大了32.因此,如下方程序所述,把小写字母变为大写字母只需要把第6位设置为0即可:
    public static void main(String args[])
    {
        char ch;
        
        for(int i=0;i<10;i++)
        {
            ch = (char)('a'+i);
            System.out.print(ch);
            ch = (char)((int) ch & 65503);
            System.out.print(ch+",");
        }
    }
输出
aA,bB,cC,dD,eE,fF,gG,hH,iI,jJ,
&语句中使用的值65503表示二进制中的1111 1111 1101 1111.因此,按位与操作就只会让ch中的第6位设置0,而其他所有位都不变.
按位与运算符在确定哪个位为1,哪个位为0时,也十分有用.例如,下面的语句就用于确定status中的第4位是否置为1:
if((status & 8 ) != 0) System.out.println("bit 4 is on");
使用8是因为转换为二进制后只有第4位为1.因此,if语句只有在status的第4位也为1时才成立.下面的示例用于显示byte值的二进制形式的各个位,这也是这一概念的一个有趣的应用.
    public static void main(String args[])
    {
        int t;
        byte val;
        
        val = 123;
        for(t=128;t>0;t=t/2)
        {
            System.out.print(val+"&"+t+"=");
            if((val & t) != 0)  System.out.print("1");
            else System.out.print("0");
            System.out.println();
        }
    }
输出:
123&128=0
123&64=1
123&32=1
123&16=1
123&8=1
123&4=0
123&2=1
123&1=1
使用按位与,for循环检测val中的每个二进制位,以确定是1还是0,如果为1,则显示数字1;否则显示0.按位或同按位与正好相反,他可以用于把二进制位设为1.两个操作数中的任何为1的二进制位都会使结果中相应的二进制位设置为1,例如:
    1101 0011
|   1010 1010

    1111 1011
可以使用按位或将大写化程序改变为小写化程序:
    public static void main(String args[])
    {
        char ch;
        
        for(int i=0;i<10;i++)
        {
            ch = (char)('A'+i);
            System.out.print(ch);
            ch = (char)((int) ch | 32);
            System.out.print(ch+",");
        }
    }
输出:
Aa,Bb,Cc,Dd,Ee,Ff,Gg,Hh,Ii,Jj,
通过使字符与32,即二进制的0000 0000 0010 0000,进行OR操作,程序就可以使大写字母变为小写字母.因此,32就是只将第6位设置为1的二进制值.当该值与其他任何值进行OR操作后,都会使第6位结果置1,而其他二进制位则保持不变.如前所述,对于字符,这就意味着把每个大写字母转换为小写字母.
异或只在两个二进制位不同的时候置为1,如下所示:
    01111 1111
^ 10111 1001

    11000 0110
异或运算符有一个有趣的属性,使他可以轻松的对信息进行编码.当对某值X与另一个值Y进行异或操作以后,再将所得结果与Y进行异或操作,就会产生X.
R1 = X ^ Y;
R2 = R1 ^ Y;
R2的值与X一样,因此,一个值与同一个值进行两次异或位运算,结果就为原值.
可以使用这一原理来创建简单的加密程序,其中把某一整数做为密钥,通过对字符进行异或运算对消息进行编码和解码.编码时,第一次使用异或运算生成密码文本.解码时,第二次使用异或运算生成普通文本.当然,这种加密方式没有什么实际价值,他太容易破解了.但是,这确实是演示异或元素的一种不错的方法.下面的简单示例即砽了这种方法来对短消息进行编码和解码:
    public static void main(String args[])
    {
     String msg = "this is a test";
     String encode = "";
     String decode = "";
     int skey = 66;
     
     System.out.println("Original message:"+msg);
     
     for(int i=0;i<msg.length();i++)
     {
         encode = encode +  (char)(msg.charAt(i)^skey);
     }
     
     System.out.println("Encodeed message :  "+encode);
     for(int i=0;i<msg.length();i++)
     {
         decode = decode +  (char)(encode.charAt(i)^skey);
     }
     
     System.out.println("decoded message :  "+decode);
    }
输出:
Original message:this is a test
Encodeed message : 6*+1b+1b#b6'16
decoded message : this is a test
如你所见,两个异或运算的结果使用同一个密钥来生成编码消息.
一元非运算符就是把操作数的所有二进制位设置成相反的状态,例如,一个名为A的整数的二进制形式为1001 0110,那么~A生成的结果就为 0110 1001
下面的程序通过显示一个数及其一元非运算演示了NOT运算符的用法:
    public static void main(String args[])
    {
     byte b = -34;
     
     for(int t=128;t>0;t=t/2)
     {
         if((b & t) != 0) System.out.print("1 ");
         else System.out.print("0 ");
     }
     System.out.println();
     
     b = (byte)~b;
     for(int t=128;t>0;t=t/2)
     {
         if((b & t) != 0) System.out.print("1 ");
         else System.out.print("0 ");
     }
     System.out.println();
    }
输出:
1 1 0 1 1 1 1 0
0 0 1 0 0 0 0 1
2.移位运算符
在Java中,可以按照指定的位数将二进制位向左或向右移动.Java定义了三个移位运算符:
运算符
作用
<<
左移
>>
右移
>>>
无符号右移
这些运算的基本形式如下:
value<<num-bits
value>>num-bits
value>>>num-bits
这里,value是被移位的值,而移动的位数由num-bits指定.
每次左移都会引起指定值中的所有二进制位都向左移动一个位置,同时在右端补一个0.每次右移都会引起指定值中的所有二进制位都向右移动一个位置,同时保持符号位不变.如你所知,我们通常把整数值的高阶位设置为1来表示负数,Java也采用了这种方法,因此,如果被移动的值是一个负数,那么每次右移都会在左端补一个1.如果是正值,那么每次右移都会在左端补一个0.
除了符号位以外,在右移的时候我们还要注意其他的一些事情.今天,计算机对于负值使用的是补码方式.在这种方法中,负值存储是通过先把值的各二进制都置为相反的值,然后加1实现的.因此,-1的字节值的二进制形式是1111 1111.右移该值生成的永远是-1.
如果右移时,不想保持符号位,可以使用无符号右移(>>>),他总是在左端补一个0.出于这个原因,>>>也称为充零右移,在移动不代表整数的二进制位(如状态码)时可以使用无符号右移.
对于所有移位而言,移出的二进制位都会丢失,因此,移位不是循环的,移出的位是无法在被检索的.
下面是一个说明左移和右移的程序,这里,给定整数一个初值1,表示其低阶位被置为1,然后,对该整数执行8次移位操作.每次移位后,显示该值的低8位.然后重复该过程,不同之处是把1放在第8位,执行右移:
    public static void main(String args[])
    {
     int val = 1;
     
     for(int i=0;i<8;i++)
     {
         for(int t=128;t>0;t=t/2)
         {
             if( (val & t) != 0 )  System.out.print("1 ");
             else System.out.print("0 ");
         }
         System.out.println();
         val = val << 1;
     }
     
     System.out.println();
     
     val = 128;
     for(int i=0;i<8;i++)
     {
         for(int t=128;t>0;t=t/2)
         {
             if( (val & t) != 0 )  System.out.print("1 ");
             else System.out.print("0 ");
         }
         System.out.println();
         val = val >> 1;
     }
    }
输出:
0 0 0 0 0 0 0 1
0 0 0 0 0 0 1 0
0 0 0 0 0 1 0 0
0 0 0 0 1 0 0 0
0 0 0 1 0 0 0 0
0 0 1 0 0 0 0 0
0 1 0 0 0 0 0 0
1 0 0 0 0 0 0 0

1 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0
0 0 1 0 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 0 1 0 0 0
0 0 0 0 0 1 0 0
0 0 0 0 0 0 1 0
0 0 0 0 0 0 0 1
在对byte和short值进行移位时,要特别小心,因为在计算表达式时,Java会自动把这些类型升级为int类型.例如,如果对一个byte值进行右移,会首先把他升级为int类型,然后才进行移位.移位的结果也是一个int类型的值.这一转换常常没有作用.然而,如果要对一个byte或short类型的负值进行移位,那么在把他升级为int类型时,就会出现符号位溢出.因此,整数值结果的高阶位就会被1填充.执行普通右移操作时,这是可以正常运行的.但是当执行充零右移时,要移动24个1后字节值中才会出现0.
3.位运算符的赋值速记符
所有的二进制位运算都有一种把赋值与位运算结合在一起的速记形式.例如,下面两条语句都可以把x与127异或的结果赋给x:
x = x ^ 127;
x ^= 127;
问:既然二进制是以2为幂的,那么移位运算符可以用作乘或除2的快速计算方法吗?
答:可以,移位操作可以用于执行乘以2或除以2的快速计算.左移把一个值乘以2,右移把一个值除以2.