最近在学java,其实仅仅是在命令行里写程序跟C语言没有太大的区别,思想都是一样的。遇到了一个比较新鲜(后来知道原来C中也有)的东西——位元算(又叫位操作)。多新鲜啊,毕向东老师说这次(现在正在学习的这次)使用位元算符将会是你们今后使用的唯一的一次,因为在开发中根本用不着。其实也差不多。不过见到了几个应用,还是蛮有意思的,这里总结一下(万一考试就考到了呢,呵呵)。
Java中共有7个位运算符:~(取反)、&(与)、|(或)、^(异或)、>>(右移)、<<(左移)、>>>(无符号右移)。这里不在解释位运算,只是谈谈几种位运算的有趣用法。(不知道位运算?看看这里)
加密
1 一个数异或同一个数两次还是原数;
2 计算机中的文件都是以二进制的形式来存储的;
利用这两个特性,我们就可以用^运算来给文件加密。例如将一个二进制文件中所有的数据都异或42这个数字,就得到了一个加密后的文件,“42”就是这个文件的“钥匙”。将这个文件中的所有数据再异或42,就完成了“解密”的过程。
不增加新的变量来交换a b两个变量的值
交换a b两个变量的值,几乎是每一个人在学编程的时候都要接触的。现在我们想这个问题,我有两个瓶子,A瓶装着果汁,B瓶装着醋,要是A瓶装醋,B瓶装果汁,如何做呢?通常,我们会用下面这段代码:
1 2 3 4 5 |
int temp; temp = a; a = b; b = temp; |
这就像我们找来一个新瓶子C,先将果汁倒入新瓶子,然后醋倒入A,果汁再倒入B。这是很容易想到的办法。那么如果没有别的瓶子了呢?看看这个:
1 2 3 |
a = a + b; //此时a包含a 和 b b = a - b; a = a - b; |
只有两个瓶子,那么我们只好把它们倒在一起,然后将混合物中的果汁倒入B。不可思议是吗?别急,我们将果汁和醋倒在一起的时候,B瓶还在,这可是程序呀,B瓶仍然装着醋,这时我们就可以将(混合-醋)的值给B,也就是果汁,再将(混合-果汁)的值给A,完成!但是,这种方式有一个弊端:有可能a+b的值会超出int型的范围。
基于同样的想法,我们还可以用一下的代码:
1 2 3 |
a = a ^ b; b = a ^ b; //实际上是(a^b)^b 也就是a异或了b两次,等号右边是a的值 a = a ^ b; //此时b里面已经是“果汁”,实际上是(a^b)^a,也就是b异或了a两次,是b |
第一步之后,原来a占用了多少位依旧是多少位,绝对不会发生数据的溢出。
ps:为了程序的可读性,真正开发时要慎用后两种!
进制的转换(10进制转16进制为例)
接触位运算之前,进制转换我是这么操作的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
class turn10_16{ public static void main(String[] args){ int n=200; //n就是代转换的数字 boolean out_turn=false; //输出时用,去掉输出时候高位上的‘0’ int[] s=new int[20]; //将转换后的十六进制数存放在s[]数组中 while(n>0){ int i=0; s[i]++; while(s[i]>15){ //逢16进一,并且检查下一位 //是不是16,如果是,再进一 s[i]=0; i++; s[i]++; } n--; //数完一个之后n--,知道数完n个数 } for(int i=19;i>=0;i--){ if(out_turn == false){ //这个if是为了去掉最高位上的0, // 其中out_turn作为开关; if(s[i]==0) continue; else{ out_turn=true; i++; } } else{ //输出转换之后的结果,10输出A,类推 if(s[i]<10) System.out.print(s[i]); else System.out.print((char)('A'+(s[i]-10))); } } System.out.println(); } } |
这种转换就像数数,一共有多少个,我来用十六进制的方法再数一遍,逢16进1,数完为止。这种方法的弊端就是效率低,而且不能转换负数。
我们知道,10进制的数据在计算机中使用2进制来存储的,而16进制的出现也是为了阅读性强,使4个位置放在一起计数。那么理论上,用“位运算”来操作,效率肯定会高。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class turn10_16{ public static void main(String[] args){ int n=200; //n就是代转换的数字 boolean out_turn=false; //输出时用,去掉输出时候高位上的‘0’ int[] s=new int[20]; //将转换后的十六进制数存放在s[]数组中 for(int i=0;i<=8;i++){ //int型占用了8个byte位置,每个byte即一个16进制, //每次保留一个byte并且转换成16进制,至少要8次(可以优化) int temp= n & 15; //与0000-0000 0000-0000 0000-0000 0000-1111进 // 行&运算,只保留最后4个位置即“个位”上的数 s[i]=temp; //将这个数赋给个位 n=n>>>4; //无符号右移4个位置,再保留出十位上的数 } for(int i=19;i>=0;i--){ if(out_turn == false){ //这个if是为了去掉最高位上的0,其中out_turn作为 //开关; if(s[i]==0) continue; else{ out_turn=true; i++; } } else{ //输出转换之后的结果,10输出A,类推 if(s[i]<10) System.out.print(s[i]); else System.out.print((char)('A'+(s[i]-10))); } } System.out.println(); } } |
总结:如此看来,位元算还是很有必要学习的。基于计算机只认识0和1,很多关于内存的操作用位运算效率会提高不少,毕竟我们是与计算机打交道。像 *2/2运算、取绝对值运算,取相反数运算等等,直接对内存进行移位或者反码,快了不少。
无论学习什么东西,基础、原理性质的东西一定要好好学。不要以为用的少了就不重视。
位运算感觉 不知道怎么做。。。甚至感觉没用(主要是无法灵活使用,也不怎么会使用)
位运算用起来麻烦了一点,不过在效率上高了不少。不是很常用,可能要对效率要求非常非常高的时候才会用到吧。
Pingback: 位运算的妙用 – 前端开发,JQUERY特效,全栈开发,vue开发