java位运算实战:2个高频场景,比常规写法更高效
很多Java开发者做日常开发时只用过基础的加减乘除运算却忽略了位运算的强大——位运算直接操作二进制底层运算效率比常规算术运算快数倍而且在JDK源码、框架底层比如HashMap的哈希计算中大量应用。掌握它不仅能优化代码性能面试时也能体现你的基础扎实度。今天分享2个工作中高频可用的位运算场景每一个都有常规写法对比、位运算更优写法还有避坑提醒。一、场景1判断一个数是否为奇数替代 % 2 取模这是最常见的场景之一比如判断循环次数、数据奇偶性很多人第一反应是用取模运算但其实这里藏着两个坑而且效率不如位运算。常规写法有坑// 常规取模判断奇数 public static boolean isOdd(int num) { return num % 2 1; }看似没问题但有两个明显弊端1. 取模运算的底层逻辑比位运算复杂效率更低尤其是循环中大量调用时差距会被放大2. 负数判断会出错比如 num -3-3 % 2 的结果是 -1此时返回 false但-3其实是奇数不符合预期。位运算最优写法无坑、高效// 位与运算判断奇数高效且兼容负数 public static boolean isOdd(int num) { return (num 1) 1; }原理解析二进制中奇数的末尾一定是1偶数的末尾一定是0而数字1的二进制是 000...0001只有末尾一位是1。位与运算amp;的规则是两个二进制位都为1结果才为1否则为0。所以用 num amp; 1只会保留num二进制的末尾一位其余位全部变为0。00001101 (13) 00000001 (1) ----------- 00000001 (1)如果结果是1说明num末尾是1即为奇数如果是0即为偶数完美兼容正数、负数而且运算效率翻倍。二、场景2正整数快速除以2替代 / 2 除法日常开发中经常会有“将正整数除以2”的需求比如分页计算、数据减半用除法运算很直观但效率不高位运算能实现“秒级”计算。常规写法效率低// 常规除法除以2 public static int divideByTwo(int num) { return num / 2; }弊端除法运算属于算术运算底层需要经过多步计算耗时比位运算长。位运算最优写法高效、简洁// 右移1位等价于除以2 public static int divideByTwo(int num) { return num 1; }原理解析右移运算gt;gt;的规则是将二进制所有位向右移动指定位数左边补符号位正数补0负数补1右边溢出的位直接丢弃。比如正数 6 右移1位等价于 6⁄2 3二进制 十进制 110 6 011 3右移1位本质就是将数字缩小为原来的1/2运算直接操作二进制效率比除法高很多而且结果和除法完全一致。但要注意的是要正整数负数不能。例如负数 -3二进制 右移1位 → 二进制 -2等价于 -3⁄2 -2。二进制 十进制 11111111 11111111 11111111 11111101 -3 11111111 11111111 11111111 11111110 -2Java 的整数除法是向零取整即舍弃小数部分直接取整数部分。-3 ÷ 2 -1.5向零取整得到 -1。另外除以 2 的幂可以用右移除以 3、5 等非 2 的幂不能直接用移位。三、两个整数交换替代临时变量有坑对于两个不同的整数变量三行异或操作确实能交换它们的值且不引入显式的临时变量// 异或运算交换两个整数无临时变量 public static void swap(int a, int b) { a a ^ b; b a ^ b; a a ^ b; }“临时变量会多占用一份内存开销” —— 夸大其词 局部变量 temp 分配在栈上或直接被编译器优化为寄存器不产生堆内存开销。在绝大多数场景下一个 int 临时变量的开销可以忽略不计几个字节微秒级以下。“位运算更高效” —— 事实相反在主流 CPU 上 临时变量交换通常编译为 mov 或 xchg 指令直接操作寄存器。异或交换需要三次异或运算且每条异或指令都依赖上一条结果数据依赖链会导致流水线停顿。现代 CPU 上临时变量交换比异或交换更快且功耗更低。大量基准测试包括 Java JIT都证实了这一点。“无临时变量节约内存” —— 微优化毫无必要 在循环中调用几百万次临时变量带来的额外内存操作依然可忽略而编译器甚至可能将其完全优化掉。追求这种“无临时变量”属于过度的位运算炫技牺牲可读性换不存在的性能提升。异或交换有隐蔽的陷阱 如果 a 和 b 指向同一内存位置例如 swap(arr[i], arr[i])异或交换会将该位置的值置为 0导致错误。临时变量交换则安全。Java 中限制很大 只能用于基本整数类型int, long 等不能用于 float, double, 对象引用。无法在真正需要交换两个变量的场景使用Java 方法参数是传值不能直接交换调用方变量。通常异或交换仅用于数组元素交换但数组元素交换用临时变量同样简单且更清晰。四、总结位运算虽好但有两个容易踩坑的点一定要注意1. 位运算只适用于整数类型byte、short、int、long不能用于浮点数float、double否则会编译报错2. 区分右移和无符号右移我们今天用的 是算术右移会保留符号位负数右移左边补1而 是无符号右移左边一律补0适合无符号整数的运算日常开发中很少用3. 异或交换的坑如果a和b指向同一内存地址比如 swap (arr[i], arr[i])会导致结果变成0因为 a ^ a 0日常开发中只要交换两个不同的变量就不会有问题。