Java位运算符代码演示

原码、反码、补码

在计算机内,有符号数有3种表示方法:原码、反码和补码,机器数的最高位为符号位,符号位为0表示正数,符号位为1表示负数。

原码=符号位+真值的绝对值。如:3的原码(这里假设机器数的字长为8)是00000011,-3的原码是10000011。

对于正数,反码=原码;对于负数,反码=原码保留符号位+绝对值部分按位取反。如:-3的原码是10000011,反码是11111100。

对于正数,补码=原码;对于负数,补码=反码保留符号位+(绝对值部分+1)。如:-3的原码是10000011,补码是11111101。

总结:对于正数,原码=反码=补码;对于负数,反码=原码保留符号位+绝对值部分按位取反,补码=反码保留符号位+(绝对值部分+1),可见负数的原码反码补码的符号位都是1;对于0,+0和-0有两种不同的原码表示形式,一般统一将-0变为+0,即将0归入正数,所以0的原码=反码=补码。

问:原码的概念很好理解,为什么还要加入反码和补码的概念?
答:因为计算机中底层只能计算加法,所以需要将减法转换成加法来处理。如:3-3=3+(-3),原码不能解决这个问题,补码可以,反码可以看作是为了引出补码的概念。补码可以带符号位参与运算,计算机的数值型数据都是补码的形式存储的。如:3-3=3+(-3),3和-3用补码表示(00000011和11111101)并相加得到的结果是00000000,即0;3-5=3+(-5),-5的原码是10000101,反码是11111010,补码是11111011,3和-5用补码表示(00000011和11111011)并相加得到的结果是11111110,这是补码,符号位为1说明是个负数,将这个补码转换成原码(先得到反码:符号位不变,绝对值部分-1,得11111101,再得到原码:符号位不变,绝对值部分按位取反,得10000010,即-2),根据原码得到结果-2。

代码演示

Java的位运算符有:&(按位与)、|(按位或)、^(按位异或)、~(按位取反)、<<(左移)、>>(右移)、>>>(无符号右移,注意并没有无符号左移的位运算符)

  • 计算机的位只有1、0两个值,计算数值型数据的时候,使用位运算符比使用算术运算符要快。
    • 注:位操作符的结果都是int类型。
  • a&b:对比a、b的每个位,只有两者均为1时,结果为1,其他时候结果为0。可理解为,比较每个位,每个位取小那个值。
  • a|b:对比a、b的每个位,只要两者有一个为1,结果就为1,只有两者全为0时结果为0。可理解为,比较每个位,每个位取大那个值。
  • a^b:对比a、b的每个位,两者不同时结果为1,相同时结果为0。异或有翻转的作用。
  • ~a:对a的每个位进行取反操作。
  • a<<n:左移n位,溢出舍去,少了补n个0。在一些情况下,结果为 a ∗ 2 n a*2^n a2n
    • 注:移位要先求模(包括左移右移无符号右移),再移。a移n位,实际上移动的位数是n%(32或64,a为int型就是32,long型就是64)。
  • a>>n:右移n位,溢出舍去,用符号位填充高位。结果为 a / 2 n a/2^n a/2n
    • 注:结果向下取整,10/4=2.5,向下取整等于2;-10/4=-2.5,向下取整等于-3。
  • a>>>n:右移n位,溢出舍去,用0填充高位。在一些情况下,结果为 a / 2 n a/2^n a/2n
public class TestBitOperations {

	public static void main(String[] args) {
		int a = 0b10; // 2,正数,原码=反码=补码=00000000 00000000 00000000 00000010
		int b = 010; // 8,正数,原码=反码=补码=00000000 00000000 00000000 00001000
		int c = 10; // 10,正数,原码=反码=补码=00000000 00000000 00000000 00001010
		int d = 0x10; // 16,正数,原码=反码=补码=00000000 00000000 00000000 00010000
		int negativeA = -a;// 负数,原码=10000000 00000000 00000000 00000010,反码=11111111 11111111 11111111 11111101,补码=11111111 11111111 11111111 11111110
		int negativeB = -b;// 负数,原码=10000000 00000000 00000000 00001000,反码=11111111 11111111 11111111 11110111,补码=11111111 11111111 11111111 11111000
		int negativeC = -c;// 负数,原码=10000000 00000000 00000000 00001010,反码=11111111 11111111 11111111 11110101,补码=11111111 11111111 11111111 11110110
		int negativeD = -d;// 负数,原码=10000000 00000000 00000000 00010000,反码=11111111 11111111 11111111 11101111,补码=11111111 11111111 11111111 11110000

		int[] numbers = { a, b, c, d, negativeA, negativeB, negativeC, negativeD};
		for (int element : numbers) {
			// Integer.toBinaryString(int i)只有在i为负数时,会返回补码
			System.out.printf("%d 在计算机中存储的是 %s%n", element, formatBinaryString(intToBinaryString(element)));
		}
		System.out.println("--------------------------------------------------------------------------------");

		showBitOperations(b, c, 2);
		System.out.println("--------------------------------------------------------------------------------");
		
		showBitOperations(c, d, 2);
		System.out.println("--------------------------------------------------------------------------------");
		
		showBitOperations(negativeB, negativeC, 2);
		System.out.println("--------------------------------------------------------------------------------");
		
		// 移位要先根据移的位数求模(int类型的数据模是32,long类型的数据模是64),再移。
		System.out.printf("%d(%s) 左移(<<) %d位得到%d(%s)%n", a, formatBinaryString(intToBinaryString(a)), 34, c, formatBinaryString(intToBinaryString(c)));
		System.out.println("--------------------------------------------------------------------------------");
		
		// 判断奇偶
		for (int i = 0; i < 10; i++) {
			String str = (i & 1) == 1 ? "是奇数" : "是偶数";
			System.out.println(i + str);
		}
		System.out.println("--------------------------------------------------------------------------------");
	}

	/**
	 * 将int值转换为二进制字符串表示。
	 * 
	 * @param n 要转换的int值
	 * @return 对应的二进制字符串
	 */
	private static String intToBinaryString(int n) {
		StringBuilder binary = new StringBuilder(32);
		for (int i = 31; i >= 0; i--) {
			// 通过位与运算和位移操作获取每一位的值,并追加到字符串中
			binary.append((n >> i & 1));
		}
		return binary.toString();
	}
	
	/**
	 * 每8位加个空格,方便观察
	 * @param binaryString 输入的二进制字符串
	 * @return 格式化后的字符串
	 */
	private static String formatBinaryString(String binaryString) {
		if (binaryString == null) {
            return null; // 或者抛出异常,根据实际需求处理null输入
        }
        
        StringBuilder sb = new StringBuilder();
        int length = binaryString.length();
        for (int i = 0; i < length; i++) {
            sb.append(binaryString.charAt(i)); // 添加当前字符
            if ((i + 1) % 8 == 0) { // 检查是否达到每隔8字符的条件
                sb.append(" "); // 添加空格
            }
        }
        return sb.toString(); // 返回格式化后的字符串
	}
	
	/**
	 * 演示Java的位运算符(&、|、^、~、<<、>>、>>>)
	 * @param a
	 * @param b
	 * @param n 位移几位
	 */
	private static void showBitOperations(int a, int b, int n) {
		// 按位与
		int aAndB = a & b;
		System.out.printf("%d(%s) 按位与(&) %d(%s)得到%d(%s)%n", a, formatBinaryString(intToBinaryString(a)), b, formatBinaryString(intToBinaryString(b)), aAndB, formatBinaryString(intToBinaryString(aAndB)));

		// 按位或
		int aOrB = a | b;
		System.out.printf("%d(%s) 按位或(|) %d(%s)得到%d(%s)%n", a, formatBinaryString(intToBinaryString(a)), b, formatBinaryString(intToBinaryString(b)), aOrB, formatBinaryString(intToBinaryString(aOrB)));

		// 按位异或(不同为1,同为0)
		int aXorB = a ^ b;
		System.out.printf("%d(%s) 按位异或(^) %d(%s)得到%d(%s)%n", a, formatBinaryString(intToBinaryString(a)), b, formatBinaryString(intToBinaryString(b)), aXorB, formatBinaryString(intToBinaryString(aXorB)));

		// 按位取反
		int notA = ~a;
		int notB = ~b;
		System.out.printf("%d(%s) 按位取反(~)后得到 %d(%s)%n", a, formatBinaryString(intToBinaryString(a)), notA, formatBinaryString(intToBinaryString(notA)));
		System.out.printf("%d(%s) 按位取反(~)后得到 %d(%s)%n", b, formatBinaryString(intToBinaryString(b)), notB, formatBinaryString(intToBinaryString(notB)));

		// 左移
		int leftShiftA = a << n;
		int leftShiftB = b << n;
		System.out.printf("%d(%s) 左移(<<) %d位得到%d(%s)%n", a, formatBinaryString(intToBinaryString(a)), n, leftShiftA, formatBinaryString(intToBinaryString(leftShiftA)));
		System.out.printf("%d(%s) 左移(<<) %d位得到%d(%s)%n", b, formatBinaryString(intToBinaryString(b)), n, leftShiftB, formatBinaryString(intToBinaryString(leftShiftB)));
		
		// 右移
		int rightShiftA = a >> n;
		int rightShiftB = b >> n;
		System.out.printf("%d(%s) 右移(>>) %d位得到%d(%s)%n", a, formatBinaryString(intToBinaryString(a)), n, rightShiftA, formatBinaryString(intToBinaryString(rightShiftA)));
		System.out.printf("%d(%s) 右移(>>) %d位得到%d(%s)%n", b, formatBinaryString(intToBinaryString(b)), n, rightShiftB, formatBinaryString(intToBinaryString(rightShiftB)));

		// 无符号右移
		int unsignedRightShiftA = a >>> n;
		int unsignedRightShiftB = b >>> n;
		System.out.printf("%d(%s) 无符号右移(>>>) %d位得到%d(%s)%n", a, formatBinaryString(intToBinaryString(a)), n, unsignedRightShiftA, formatBinaryString(intToBinaryString(unsignedRightShiftA)));
		System.out.printf("%d(%s) 无符号右移(>>>) %d位得到%d(%s)%n", b, formatBinaryString(intToBinaryString(b)), n, unsignedRightShiftB, formatBinaryString(intToBinaryString(unsignedRightShiftB)));
	}
}

运行结果:
在这里插入图片描述

应用

  1. 判断某数的奇偶:(n & 1) == 1(true为奇、false为偶)

  2. 判断两个数是否异号:(a^b)<0(true为异号、false为同号)

  3. 速算某数*2的n次方:某数<<n

    1. 注:移位要先求模(包括左移右移无符号右移),再移。某数移n位,实际上移动的位数是n%(32或64,某数为int型就是32,long型就是64)。
    2. 注:左移可能导致符号位改变,此时结果就不对了。
    3. 注:左移可能导致结果超过某数数据类型的上限(int型是正负21亿多),此时结果就不对了。
  4. 速算某数/2的n次方:某数>>n

    1. 注:移位要先求模(包括左移右移无符号右移),再移。某数移n位,实际上移动的位数是n%(32或64,某数为int型就是32,long型就是64)。
    2. 注:结果向下取整,10/4=2.5,向下取整等于2;-10/4=-2.5,向下取整等于-3。
  5. 某数+1:-~某数

    1. 证明:因为n和~n的每个位相加都是1(包括符号位),所以n+(~n)=-1,所以n+1=-~n。
  6. 某数-1:~-某数

    1. 证明:由n+(~n)=-1,得~n=-n-1,即(-n)-1=~-(-n),换元法可得n-1=~-n。
  7. 不用临时变量交换两个数:

    int a = 8;
    int b = 10;
    a = a^b;
    b = b^a;
    a = a^b;
    
    1. 证明:需要用到^的性质。
      ① a ^ a =0 (任何数异或本身结果为0)
      ② a ^ b =b ^ a (交换律)
      ③ a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c (结合律)
      ④ 0 ^ a = a (异或0具有保持的特点)
      ⑤ a ^ b ^ a = b (根据①②④可得)
      a=a原^b原,b=b原^a=b原^(a原^b原)=a原,至此,a存放的数据为a原^b原,b存放的数据为a原,只要再执行a^a原就能实现a里面存放b原的数据了,但a原的数据已经不在a里面了,而是在b里面了,所以a=a^b=(a原^b原)^a原=b原,完成交换。
  8. 求某数的绝对值: |n| = (n^(n>>31))-(n>>31)(这里的n是32位的数据)

    1. 证明:(TODO)
  9. 字母转小写:字母|" "

    1. 证明:空格的ASCII码是32(即00100000),字母的ASCII码的范围是A~Z(65~90,即010000001~01011010)、a~z(97~122,即01100001~01111010),每个小写字母比对应的大写字母的ASCII码大32(即00100000),刚好为空格的ASCII码。字母|" "表示比较字母和空格的每一个位,取大那个值,这样可起到将字母的第三个位恒定设为1,字母的其他位数据不变,结果就相当于将字母转小写(如果字母原来是小写,就不变)。
  10. 字母转大写:字母&"_"

    1. 证明:下划线的ASCII码是95(即01011111),字母&"_"表示比较字母和_的每一个位,取最小那个值,这样可起到将字母的第三个位恒定设为0,字母的其他位数据不变,结果就相当于将字母转大写(如果字母原来是大写,就不变)。
  11. 大小写互换:字母^" "

    1. 证明:字母^" "表示字母的第三个位10互换(1^1=0,0^1=1),字母的其他位数据不变,结果就相当于将字母切换大小写。

参考

  1. 《Java核心技术 卷1 基础知识 原书第11版》 机械工业出版社 Cay S.Horstmann著 林琪、苏钰涵等译
  2. Java中的位运算 by 柯柯不会Java
  3. 一篇搞定位运算——java位运算详解 by 青癯
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值