java按位异加密输入为,Java位运算

前言

先抛出一个问题?

有 1000 个一模一样的瓶子,其中有 999 瓶是普通的水,有一瓶是毒药。任何喝下毒药的生物都会在一星期之后死亡。现在,你只有 10 只小白鼠和一星期的时间,如何检验出哪个瓶子里有毒药?

如果用二分法:

1000 / 2 = 500

500 / 2 = 250

250 /2 = 125

125 / 2 = 62 + 63

63 / 2 = 31 + 32

到现在位置你已经用完了所有的小白鼠,你只能确定32or31中的一堆,显然是无法实现了。

可以用位运算来思考,该问题。

1,把1000瓶标号:1,2,3,4,5,6...1000.

2,所有老鼠排列在一起组成一个2进制队列: 0000000000

0代表不喝,1代表喝

3,0000000001代表第一瓶水被喝情况

0000000010代表第二瓶水被喝情况

0000000011代表第三瓶水被喝情况

0000000100代表第四瓶水被喝情况

...

1111101000代表第1000瓶水被喝情况

4,第7天,喝了毒药的老鼠都死了,那个二进制队列转为为十进制就是毒药的标号。

比如第3只老鼠死亡,其他老鼠没死,队列为0000000100,第四瓶水有毒。

第1,5,6,8老鼠死亡,其他没死,队列为0010110001,第177瓶水有毒。

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

java支持的位运算符

1、原码、反码和补码说明:

一个数可以分成符号位(0正1负)+ 真值,原码是我们正常想法写出来的二进制。由于计算机只能做加法,负数用单纯的二进制原码书写会出错,于是规定了反码(正数不变,负数符号位不变,真值部分取反);再后来由于+0, -0的争端,于是改进反码,变成补码(正数不变,负数符号位不变,真值部分取反,然后+1)。二进制前面的0都可以省略,所以总结来说:计算机里的负数都是用补码(符号位1,真值部分取反+1)表示的。

2、位运算符和2的关系

位运算符和乘2、除2在大多数时候是很相似的,可以进行替代,同时效率也会高的多,但是两者切记不能混淆 ;很多时候有人会把两者的概念混淆,尤其是数据刚好是 2、4、6、8、100等偶数的时候,看起来就更相似了,但是对于奇数,如本文使用的12345 ,右移之后结果为6172 ,这个结果就和数学意义上的除以2不同了,不过对于int 类型的数据,除2 会对结果进行取整,所以结果也是6172 ,这就更有迷惑性了。

按位与(&)

按位与的运算规则

操作数1

0

0

1

1

操作数2

0

1

0

1

按位与

0

0

0

1

规则总结:只有两个操作数对应位同为1时,结果为1,其余全为0. (或者是只要有一个操作数为0,结果就为0)。

按位或(|)

按位或的运算规则

操作数1

0

0

1

1

操作数2

0

1

0

1

按位或

0

1

1

1

规则总结:只有两个操作数对应位同为0时,结果为0,其余全为1.(或者是只要有一个操作数为1,结果就为1)

按位非(~)

按位非的运算规则

操作数

0

1

按位或

1

0

按位异或(^)

按位异或的运算规则

操作数1

0

0

1

1

操作数2

0

1

0

1

按位异或

0

1

1

0

规则总结:异:1

关于异或运算符,有很多非常有用的特性,我们在这里梳理总结一下。

Ⅰ、异或运算符满足交换律

也就是说,ab与ba是等价的,虽然a和b交换了位置,但还是会运算出相同的结果。这个规律还可以推广到N个操作数,也就是说,如果有N个变量都参与了异或运算,那么它们的位置无论如何交换,运算的结果都是相同的。

Ⅱ、任何两个相同的数字进行异或操作,所得到的结果都必然为0

这个特性并不难理解,因为两个相同的数字,换算成补码后,每个二进制位上的数也都相同,这样在进行异或运算时,按照运算规则,每个二进制位上得到的运算结果也都是0,这N个0所组成的二进制串就是数字0的补码。我们可以利用这个特性快速的判断两个整数是否相同。另外,利用这个特性还可以实现内存的快速清零操作,比如我们可以在代码中写上a=a^a;这条语句能快速的把变量a所占据的那几个字节的内存迅速清零。

Ⅲ、对于任意一个二进制位来说,这个位上的数与0进行异或运算,运算结果与这个二进制位上的数是相同的,而与1进行异或运算,结果与这个二进制位上的数字相反

注意,我们现在说的是二进制位上的数字,所谓相反不是说原来这个位上是1,运算结果是-1,而是说原来是1,运算结果为0,原来如果是0,运算结果是1,这才是此处所说的”相反”的概念。这个特性也非常好理解,小伙伴们一定要记住它,在以后进行一些位运算操作的时候经常会用到这个特性。

Ⅳ、对于任何两个整数a和b,abb等于a

这个结论为什么成立呢?简单说来,就是因为这个表达式中有bb,而bb的结果为0,前文已讲过,任何一个数与0进行按位异或操作,结果仍然是这个数本身,所以,abb等于a。这个特性在加密运算方面有着很普遍的应用。我们可以把a当作要加密的数据,而把b当作密钥。a异或b就是把a用密钥b进行了加密操作,当需要解密时,仍然以b作为密钥,再进行一次异或就实现了解密。

这个特性还可以推出另外一个结论:对于任何两个整数a和b,aba等于b。我们能够得到这个结论的原因也很简单,就是因为按照交换律,ab与ba的运算结果是一样的,所以aba等价于baa,这个表达式中出现了aa,aa的值也为0,所以整个表达式的其实就相当于b^0,最终结果还是b。

左移(<

运算规则:按二进制形式把所有的数字向左移动对应的位数,高位移出(舍弃),低位的空位补零。例如: 12345 << 1,则是将数字12345左移1位:

0ad7a9a58595

img

位移后十进制数值变成:24690,刚好是12345的二倍,所以有些人会用左位移运算符代替乘2的操作,但是这并不代表是真的就是乘以2,很多时候,我们可以这样使用,但是一定要知道,位移运算符很多时候可以代替乘2操作,但是这个并不代表两者是一样的。

思考一下:如果任意一个十进制的数左位移32位,右边补位32个0,十进制岂不是都是0了?当然不是!!! 当int 类型的数据进行左移的时候,当左移的位数大于等于32位的时候,位数会先求余数,然后再进行左移,也就是说,如果真的左移32位 12345 << 32 的时候,会先进行位数求余数,即为 12345<

右移(>>)

同样,还是以12345这个数值为例,12345右移1位: 12345>>1。

0ad7a9a58595

img

右移后得到的值为 6172 和int 类型的数据12345除以2取整所得的值一样,所以有些时候也会被用来替代除2操作。另外,对于超过32位的位移,和左移运算符一样,会先进行位数求余数。

无符号右移(>>>)

无符号右移运算符和右移运算符是一样的,不过无符号右移运算符在右移的时候是补0的,而右移运算符是补符号位的。以下是-12345二进制表示:

0ad7a9a58595

img

对于源码、反码、补码不熟悉的同学,请自行学习,这里就不再进行补充了讲解了,这里提醒一下,在右移运算符中,右移后补0,是由于正数 12345 符号位为0 ,如果为1,则应补1。

0ad7a9a58595

img

运算符的优先次序

在对一个表达式进行计算时,如果表达式中含有多种运算符,则要安运算符的优先次序一次从高向低进行。运算符的优先次序如下:

0ad7a9a58595

img

小白鼠实验应用

我们到此可以回归题目,用代码把题目做出来:

代码如下:

public static void main(String[] args) {

//结果反推第几瓶

System.out.println("结果反推第几瓶:");

Scanner input = new Scanner(System.in);

System.out.println("请用户输入总共死亡的个数:");

int dead = input.nextInt(); // dead用于储存死亡的个数

System.out.println("它们的编号从小到大依次为(从0开始):");

int[] deadRats = new int[dead]; // number数组用于储存死亡试验品的标签序号

for(int i = 0; i < dead; i++){

deadRats[i] = input.nextInt();

}

double result = 0;

for(int i = 0; i < deadRats.length; i++){

result = result + Math.pow(2, deadRats[i]);

}

System.out.println("毒药编号:" + result);

//验算实验过程

//编号每一个小白鼠,1代表正常,0代表死亡

int[] srcRatsArray = new int[10];

for(int i = 0; i < 10; i++){

srcRatsArray[i] = 1;

}

//定义1000瓶水和药,不为零的即为毒药,从编号1开始,那么1000瓶,长度为1001

int[] bottleArray = new int[1001];

//记录结果的老鼠数组

int[] dtsRatsArray = new int[10];

for(int i = 0; i < 10; i++){

dtsRatsArray[i] = 1;

}

System.out.println("请指定毒药编号:");

int bottle = input.nextInt();

bottleArray[bottle] = 1;

//利用异或(^)运算, 活着的老鼠 1 碰到毒药 1 就会为0

for(int j = 1; j < 1001; j++){

String s = Integer.toBinaryString(j);

char[] chars = s.toCharArray();

//遍历j的二进制每一位

for(int x = chars.length - 1; x >= 0; x--){

if(chars[x] == '1'){

//index需要倒序

int realIndex = 9 - x;

//记录死亡的结果

if((srcRatsArray[realIndex] ^ bottleArray[j])== 0){

dtsRatsArray[realIndex] = 0;

}

}

}

}

for(int i = 0; i < 10; i++){

if(dtsRatsArray[i] == 0){

System.out.println("死亡老鼠:" + i);

}

}

}

参考:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值