乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • 学习路径
  • 第1章 Java概述与环境搭建

    • Java概述与环境搭建
    • Java语言概述
    • 解释型语言与编译型语言对比
    • JDK安装与配置
    • JDK、JRE、JVM 详解
    • HelloWorld程序详解
    • IDE 介绍
  • 第2章 标识符与基本数据类型

    • 章节导读
    • 变量概述
    • 常量概述
    • 基本类型与包装类
    • 字节型 byte
    • 短整型 short
    • 整型 int
    • 长整型 long
    • 单精度浮点型 float
    • 双精度浮点型 double
    • 字符型 char
    • 布尔型 boolean
    • 类型转换
  • 第3章 运算符与表达式

    • 章节导读
    • 算术运算符
    • 赋值运算符
    • 关系运算符
    • 逻辑运算符
    • 位运算符
    • 条件运算符
    • 运算符优先级
    • 表达式
  • 第4章 流程控制

    • 章节导读
    • 常见的程序运行流程
    • if-else 选择结构
    • switch 多分支选择
    • while 循环
    • do-while 循环
    • for 循环
    • break 与 continue
  • 第5章 数组

    • 章节导读
    • 一维数组
    • 多维数组
    • Arrays 工具类
  • 第6章 类与对象

    • 章节导读
    • 类与对象
    • 方法定义与调用
    • 构造方法
    • 封装
    • 访问修饰符
    • package 与 import
    • static 关键字
    • this 关键字
    • 参数传递 详解
    • 枚举
    • 成员内部类
    • 局部内部类
    • 静态内部类
    • 匿名内部类
  • 第7章 接口与继承

    • 章节导读
    • 继承
    • super 关键字
    • final 关键字
    • 多态
    • 向上转型与向下转型
    • 抽象类
    • 接口
    • 抽象类与接口对比
  • 第8章 注解

    • 章节导读
    • 注解基础
    • 元注解详解
    • 自定义注解
  • 第9章 常用类

    • 章节导读:Java 常用类
    • Object 类:万类之祖
    • 包装类:基本类型的对象化
    • String:不可变的字符串
    • StringBuffer:线程安全的可变字符串
    • StringBuilder:可变的字符串构建器
    • Math:数学运算工具类
    • Random:伪随机数生成器
    • 大数值运算 详解
    • 日期时间API 详解
  • 第10章 异常机制

    • 章节导读
    • 异常体系与分类
    • try-catch-finally
    • try-with-resources
    • throws 与 throw
    • 自定义异常
  • 第11章 泛型

    • 章节导读
    • 泛型基础
    • 通配符与PECS原则
    • 类型擦除
  • 第12章 集合框架

    • 章节导读
    • 集合框架概述
    • ArrayList
    • LinkedList
    • HashMap 详解
    • LinkedHashMap 详解
    • TreeMap 详解
    • HashSet
    • TreeSet 详解
    • TreeSet 与 Comparable
    • Collections 工具类详解
  • 第13章 IO流

    • 章节导读
    • IO流概述
    • 字节流
    • 字符流
    • 缓冲流
    • 转换流 详解
    • 序列化 详解
    • NIO与Files 详解
    • NIO与Files工具类
  • 第14章 多线程与并发

    • 第十六章 多线程与并发 —— 章节导读
    • 线程基础详解
    • synchronized 详解
    • Lock 与显式锁详解
    • volatile 详解
    • wait 与 notify 详解
    • ThreadLocal详解
    • 原子类详解
    • 并发工具类详解
    • 线程池详解
  • 第15章 反射

    • 章节导读
    • 反射概述与 Class 对象
    • Constructor 与对象创建
    • Field与Method详解
    • 反射应用详解
  • 第16章 JDK8新特性

    • 章节导读
    • Lambda 表达式
    • Stream API 基础
    • Stream API 高级详解
    • Optional 详解
    • 新日期时间API详解
  • 第17章 JDK9-11新特性

    • 章节导读
    • 模块化系统 — Project Jigsaw(JDK 9)
    • var 局部变量类型推断(JDK 10)
    • 集合工厂方法与增强(JDK 9 / 10 / 11)
    • 接口增强:private 方法(JDK 9)
    • Stream API 增强(JDK 9)
    • Optional 增强(JDK 9 / 10 / 11)
    • String 新增方法(JDK 11)
    • HTTP Client 与 Files 增强(JDK 11)
    • 直接运行 Java 源文件 — JEP 330(JDK 11)
  • 第18章 JDK12-17新特性

    • 章节导读
    • Switch 表达式(JDK 12 预览 / JDK 14 正式)
    • 文本块 Text Blocks(JDK 13 预览 / JDK 15 正式)
    • Records 记录类(JDK 14 预览 / JDK 16 正式)
    • 密封类 Sealed Classes(JDK 15 预览 / JDK 17 正式)
    • instanceof 模式匹配(JDK 14 预览 / JDK 16 正式)
    • Switch 模式匹配 — Pattern Matching for switch(JDK 17 预览 / JDK 21 正式)
    • Helpful NPE 与 String 增强(JDK 12 / JDK 14 / JDK 15)
    • Stream 增强(JDK 12 / JDK 16)
    • 日期时间增强 — Day Period 支持(JDK 16)
  • 第19章 JDK18-21新特性

    • 章节导读
    • 虚拟线程(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 序列集合(JDK 21 正式)
    • Switch 模式匹配(JDK 17 预览 / JDK 18 第二预览 / JDK 20 第四预览 / JDK 21 正式)
    • Record 模式匹配(JDK 19 预览 / JDK 20 第二预览 / JDK 21 正式)
    • 未命名模式与变量(JDK 21 预览 / JDK 22 正式)
  • 第20章 JDK 22-25 新特性

    • 章节导读
    • 字符串模板(JDK 22 预览 / JDK 23 第二预览 / JDK 24 第三预览)
    • Stream Gatherers(JDK 22 预览 / JDK 24 第二预览)
    • 隐式声明类与实例方法(JDK 23 预览 / JDK 24 第二预览)
    • 原始类型模式匹配(JDK 24 预览)
  • 附录

    • Java 核心知识点
    • Java SE 专业术语
    • Java特性索引(JDK 8 → 25)

"大翔在飞翔科技架构评审会上画了一个权限表:'读=4,写=2,执行=1——这就是位运算。一个字节8个位,可以同时控制8种权限,比建8张表高效一万倍。'白歌补充道:'而且 n << 3 比 n * 8 快一个数量级,虽然现代JVM会自动优化,但理解底层原理才能写出真正高性能的代码。'黄俪翻开笔记本:'原来无符号右移 >>> 是做HashMap哈希扰动的不二法门。'"

位运算符

概述

位运算符直接操作整数类型的二进制位(bit)。Java SE 8 提供7种位运算符,涵盖按位逻辑运算和位移运算。位运算在权限控制、高效乘除、哈希算法、数据压缩、网络协议解析等底层场景中不可或缺。

位运算符仅适用于整数类型:byte、short、int、long、char。操作前,byte/short/char 会被自动提升为 int。

操作符行为表

按位逻辑运算符

操作符含义运算规则示例(int a=5(0101), b=3(0011))结果
&按位与两位都为1则得1,否则得0a & b → 0101 & 00111(0001)
|按位或两位任意一个为1则得1a | b → 0101 | 00117(0111)
^按位异或两位不同得1,相同得0a ^ b → 0101 ^ 00116(0110)
~按位取反1变0,0变1(含符号位)~a → ~0101-6(补码表示)

位移运算符

操作符含义运算规则示例(int a=8(1000))结果
<<左移所有位向左移n位,右侧补0a << 232
>>带符号右移所有位向右移n位,左侧补符号位-8 >> 2-2
>>>无符号右移所有位向右移n位,左侧补0-8 >>> 21073741822

深度原理分析

一、补码(Two's Complement)——位运算的数学基础

Java中所有整数类型都使用补码表示负数。理解补码是驾驭位运算的前提。

补码的求法(以8位为例):

  1. 写出绝对值的原码
  2. 按位取反(得到反码 / one's complement)
  3. 加1(得到补码 / two's complement)
求 -5 的8位补码表示:
+5 原码: 0000 0101
按位取反: 1111 1010  (反码)
加1:      1111 1011  (补码 = -5)

验证: 5 + (-5) = 0
  0000 0101  (+5)
+ 1111 1011  (-5)
------------
1 0000 0000  → 溢出丢弃进位 → 0000 0000 = 0 ✓

为什么要用补码? 补码使得加法和减法可以用同一套电路完成——A - B 就是 A + (-B的补码),硬件只需加法器。

二、按位逻辑运算的逐位详解

以8位为例,设 a = 0b0110_1101 (109),b = 0b1011_0010 (178):

按位与 &(同1则1):
  0110 1101  (109)
& 1011 0010  (178)
-------------
  0010 0000  (32)     ← 用于"掩码":提取特定位

按位或 |(有1则1):
  0110 1101  (109)
| 1011 0010  (178)
-------------
  1111 1111  (255)    ← 用于"置位":将特定位设为1

按位异或 ^(不同则1):
  0110 1101  (109)
^ 1011 0010  (178)
-------------
  1101 1111  (223)    ← 用于"翻转":切换特定位

按位取反 ~(1→0, 0→1):
~ 0110 1101  (109)
-------------
  1001 0010  (-110)   ← 包括符号位一起取反

三、位移运算的低层机制

关键区别——>> vs >>>:

int n = -8;          // 二进制(int 32位): 11111111 11111111 11111111 11111000

// >> 带符号右移:高位补1(符号位),结果仍是负数
System.out.println(n >> 2);   // -2  (11111111...11111110)

// >>> 无符号右移:高位补0,结果变成很大的正数
System.out.println(n >>> 2);  // 1073741822  (00111111...11111110)

位移的模运算规则:当位移量超过类型位数时,Java会取模。对于 int(32位),n << 35 实际执行 n << (35 % 32) = n << 3。对于 long(64位),取模64。

四、位运算的高效乘除

在硬件层面,位移比乘法/除法快得多(尽管现代JVM的JIT编译器会自动将 n * 8 优化为 n << 3,但理解这一原理对性能调优至关重要):

数学运算位运算等价前提条件
n × 2^kn << k无溢出风险
n ÷ 2^k(正数)n >> kn为非负数
n % 2^kn & (2^k - 1)2^k 次幂取模
n × 10(n << 3) + (n << 1)通用优化
int n = 100;
System.out.println(n << 3);       // 100 * 8 = 800
System.out.println(n >> 2);       // 100 / 4 = 25
System.out.println(n & 15);       // 100 % 16 = 4  (因为 15 = 0b1111)

完整示例一:飞翔科技权限控制系统

场景

大翔为飞翔科技内部系统设计了基于位运算的权限控制——一个 int 可以同时管理8种权限,比数据库关联表方案高效数万倍。

/**
 * 飞翔科技权限控制系统 —— 位运算实战
 *
 * 权限模型(每个权限占一个bit):
 *   bit 0 (值=1):   查看员工信息
 *   bit 1 (值=2):   编辑员工信息
 *   bit 2 (值=4):   删除员工信息
 *   bit 3 (值=8):   查看薪资
 *   bit 4 (值=16):  编辑薪资
 *   bit 5 (值=32):  系统管理
 *   bit 6 (值=64):  审计日志
 *   bit 7 (值=128): 超级管理员
 */
public class PermissionSystem {
    // 权限常量(2的幂次)
    static final int PERM_VIEW_EMPLOYEE   = 1;    // 0000 0001
    static final int PERM_EDIT_EMPLOYEE   = 2;    // 0000 0010
    static final int PERM_DELETE_EMPLOYEE = 4;    // 0000 0100
    static final int PERM_VIEW_SALARY     = 8;    // 0000 1000
    static final int PERM_EDIT_SALARY     = 16;   // 0001 0000
    static final int PERM_SYSTEM_ADMIN    = 32;   // 0010 0000
    static final int PERM_AUDIT_LOG       = 64;   // 0100 0000
    static final int PERM_SUPER_ADMIN     = 128;  // 1000 0000

    public static void main(String[] args) {
        System.out.println("===== 飞翔科技权限系统 feixiang.net =====\n");

        // --- 白歌(架构师):拥有 查看+编辑+查看薪资+编辑薪资 权限 ---
        int baiGePermissions = PERM_VIEW_EMPLOYEE | PERM_EDIT_EMPLOYEE
                             | PERM_VIEW_SALARY | PERM_EDIT_SALARY;
        System.out.println("白歌的权限编码: " + baiGePermissions);
        System.out.println("二进制: " + toBinary8(baiGePermissions));

        // --- 权限检查:使用 & (按位与)---
        System.out.println("\n--- 权限查询 ---");
        System.out.println("白歌能否查看员工? "
            + ((baiGePermissions & PERM_VIEW_EMPLOYEE) != 0));
        System.out.println("白歌能否删除员工? "
            + ((baiGePermissions & PERM_DELETE_EMPLOYEE) != 0));
        System.out.println("白歌是否超级管理员? "
            + ((baiGePermissions & PERM_SUPER_ADMIN) != 0));

        // --- 授予权限:使用 | (按位或)---
        System.out.println("\n--- 授予新权限 ---");
        System.out.println("授予白歌 '审计日志' 权限...");
        baiGePermissions |= PERM_AUDIT_LOG;  // 等价于 baiGePermissions = baiGePermissions | PERM_AUDIT_LOG
        System.out.println("新权限编码: " + baiGePermissions);
        System.out.println("二进制: " + toBinary8(baiGePermissions));
        System.out.println("白歌能否查看审计日志? "
            + ((baiGePermissions & PERM_AUDIT_LOG) != 0));

        // --- 撤销权限:使用 & 和 ~ ---
        System.out.println("\n--- 撤销权限 ---");
        System.out.println("撤销白歌 '编辑薪资' 权限...");
        baiGePermissions &= ~PERM_EDIT_SALARY;  // 将对应位清零
        System.out.println("新权限编码: " + baiGePermissions);
        System.out.println("二进制: " + toBinary8(baiGePermissions));
        System.out.println("白歌能否编辑薪资? "
            + ((baiGePermissions & PERM_EDIT_SALARY) != 0));

        // --- 切换权限:使用 ^ (按位异或)---
        System.out.println("\n--- 切换权限(有→无,无→有)---");
        System.out.println("切换白歌 '系统管理' 权限(首次:添加)...");
        baiGePermissions ^= PERM_SYSTEM_ADMIN;
        System.out.println("编码: " + baiGePermissions + ", 二进制: " + toBinary8(baiGePermissions));
        System.out.println("白歌是否系统管理员? "
            + ((baiGePermissions & PERM_SYSTEM_ADMIN) != 0));

        System.out.println("再次切换 '系统管理' 权限(二次:移除)...");
        baiGePermissions ^= PERM_SYSTEM_ADMIN;
        System.out.println("编码: " + baiGePermissions + ", 二进制: " + toBinary8(baiGePermissions));
        System.out.println("白歌是否系统管理员? "
            + ((baiGePermissions & PERM_SYSTEM_ADMIN) != 0));

        // --- 大翔:超级管理员(拥有所有权限)---
        System.out.println("\n--- 大翔:超级管理员 ---");
        int daXiangPermissions = PERM_VIEW_EMPLOYEE | PERM_EDIT_EMPLOYEE
                               | PERM_DELETE_EMPLOYEE | PERM_VIEW_SALARY
                               | PERM_EDIT_SALARY | PERM_SYSTEM_ADMIN
                               | PERM_AUDIT_LOG | PERM_SUPER_ADMIN;
        System.out.println("大翔的权限编码: " + daXiangPermissions);
        System.out.println("二进制: " + toBinary8(daXiangPermissions));
        // 检查是否拥有所有权限
        if (daXiangPermissions == 255) {  // 8位全1 = 0b1111_1111 = 255
            System.out.println("  全部8项权限已开通 √");
        }
    }

    /** 将int转为8位二进制字符串,便于观察 */
    static String toBinary8(int n) {
        String s = Integer.toBinaryString(n);
        // 补零到8位
        while (s.length() < 8) s = "0" + s;
        // 如果超过8位,只取低8位
        if (s.length() > 8) s = s.substring(s.length() - 8);
        return s;
    }
}

运行输出:

===== 飞翔科技权限系统 feixiang.net =====

白歌的权限编码: 27
二进制: 00011011

--- 权限查询 ---
白歌能否查看员工? true
白歌能否删除员工? false
白歌是否超级管理员? false

--- 授予新权限 ---
授予白歌 '审计日志' 权限...
新权限编码: 91
二进制: 01011011
白歌能否查看审计日志? true

--- 撤销权限 ---
撤销白歌 '编辑薪资' 权限...
新权限编码: 75
二进制: 01001011
白歌能否编辑薪资? false

--- 切换权限(有→无,无→有)---
切换白歌 '系统管理' 权限(首次:添加)...
编码: 107, 二进制: 01101011
白歌是否系统管理员? true
再次切换 '系统管理' 权限(二次:移除)...
编码: 75, 二进制: 01001011
白歌是否系统管理员? false

--- 大翔:超级管理员 ---
大翔的权限编码: 255
二进制: 11111111
  全部8项权限已开通 √

权限控制核心模式总结


完整示例二:位运算高效计算

场景

黄俪在优化飞翔商城的批量价格计算时,用位运算替代乘除法,大幅提升计算效率。

/**
 * 飞翔商城 —— 位运算高效计算
 * 演示 <<、>>、>>>、& 的数学等价用法
 */
public class EfficientCalculation {
    public static void main(String[] args) {
        int productPrice = 189;          // 商品单价(元)
        int quantity = 64;               // 购买数量(故意取2的幂便于演示)

        System.out.println("===== 飞翔商城高效计算 feixiang.net =====\n");

        // --- 左移 << : 等价于乘以 2^k ---
        System.out.println("--- 左移(乘法优化)---");
        System.out.println(quantity + " × 2  = " + (quantity << 1));  // 64 × 2 = 128
        System.out.println(quantity + " × 4  = " + (quantity << 2));  // 64 × 4 = 256
        System.out.println(quantity + " × 8  = " + (quantity << 3));  // 64 × 8 = 512
        System.out.println(quantity + " × 16 = " + (quantity << 4));  // 64 × 16 = 1024

        // --- 右移 >> : 等价于除以 2^k(正整数)---
        System.out.println("\n--- 带符号右移(除法优化)---");
        int totalAmount = 1024;
        System.out.println(totalAmount + " ÷ 2  = " + (totalAmount >> 1));  // 512
        System.out.println(totalAmount + " ÷ 4  = " + (totalAmount >> 2));  // 256
        System.out.println(totalAmount + " ÷ 8  = " + (totalAmount >> 3));  // 128
        System.out.println(totalAmount + " ÷ 16 = " + (totalAmount >> 4));  // 64

        // --- 取模优化:n & (2^k - 1) 等价于 n % 2^k ---
        System.out.println("\n--- 按位与取模(n % 2^k 优化)---");
        int n = 189;
        System.out.println(n + " % 8  = " + (n % 8) + "(常规写法)");
        System.out.println(n + " & 7  = " + (n & 7) + "(位运算写法,7=0b111)");
        System.out.println(n + " % 16 = " + (n % 16));
        System.out.println(n + " & 15 = " + (n & 15) + "(15=0b1111)");

        // --- 奇偶判断:n & 1 ---
        System.out.println("\n--- 奇偶判定(n & 1)---");
        System.out.println("189 & 1 = " + (189 & 1) + " → 奇数");
        System.out.println("188 & 1 = " + (188 & 1) + " → 偶数");
        System.out.println("(比 n % 2 快,JVM自动优化后两者无差别,但理解原理重要)");

        // --- 无符号右移 >>> : 处理大数 ---
        System.out.println("\n--- 无符号右移 >>> ---");
        int negative = -189;
        System.out.println("-189 的二进制: " + Integer.toBinaryString(negative));
        System.out.println("-189 >>  2  = " + (negative >> 2)
            + "    二进制: " + Integer.toBinaryString(negative >> 2));
        System.out.println("-189 >>> 2  = " + (negative >>> 2)
            + "  二进制: " + Integer.toBinaryString(negative >>> 2));
        System.out.println("(>>> 高位补0,结果变成很大的正数)");

        // --- 交换两个整数(不使用临时变量)---
        System.out.println("\n--- 异或交换两数(无临时变量)---");
        int a = 188, b = 88;
        System.out.println("交换前: a=" + a + ", b=" + b);
        // 三步异或完成交换
        a = a ^ b;  // a = 188^88
        b = a ^ b;  // b = (188^88)^88 = 188
        a = a ^ b;  // a = (188^88)^188 = 88
        System.out.println("交换后: a=" + a + ", b=" + b);
        System.out.println("(利用性质: a^b^a = b, a^b^b = a)");
    }
}

运行输出:

===== 飞翔商城高效计算 feixiang.net =====

--- 左移(乘法优化)---
64 × 2  = 128
64 × 4  = 256
64 × 8  = 512
64 × 16 = 1024

--- 带符号右移(除法优化)---
1024 ÷ 2  = 512
1024 ÷ 4  = 256
1024 ÷ 8  = 128
1024 ÷ 16 = 64

--- 按位与取模(n % 2^k 优化)---
189 % 8  = 5(常规写法)
189 & 7  = 5(位运算写法,7=0b111)
189 % 16 = 13
189 & 15 = 13(15=0b1111)

--- 奇偶判定(n & 1)---
189 & 1 = 1 → 奇数
188 & 1 = 0 → 偶数
(比 n % 2 快,JVM自动优化后两者无差别,但理解原理重要)

--- 无符号右移 >>> ---
-189 的二进制: 11111111111111111111111101000011
-189 >>  2  = -48    二进制: 11111111111111111111111111010000
-189 >>> 2  = 1073741776  二进制: 11111111111111111111111010000
(>>> 高位补0,结果变成很大的正数)

--- 异或交换两数(无临时变量)---
交换前: a=188, b=88
交换后: a=88, b=188
(利用性质: a^b^a = b, a^b^b = a)

易错场景

反例一:>> 用于负数得到意外结果

// ❌ 错误认知:以为 >> 就是简单除以 2^n
int n = -8;
System.out.println(n >> 2);   // -2(正确,-8/4=-2)
int m = -5;
System.out.println(m >> 1);   // -3(不是 -2!因为向负无穷取整)
// -5 >> 1 = -3, 但 -5/2 = -2(向零取整)

原因:>> 对于负数不是简单的除法——它是向负无穷方向取整(floor division),而Java整数除法是向零取整。因此 -5 >> 1 = -3(floor of -2.5),而 -5 / 2 = -2(truncation toward zero)。

纠正:

// ✅ 理解差异,选择合适的操作
int m = -5;
int floorDiv = m >> 1;    // -3(向负无穷取整)
int truncDiv = m / 2;     // -2(向零取整)
// 根据业务需求选择

反例二:位运算优先级与比较运算符混淆

// ❌ 错误:位运算优先级低于比较运算符
int flag = 0b0011;
if (flag & 0b0001 == 0) {  // 实际是 flag & (0b0001 == 0) → flag & false → 编译错误或意外结果
    System.out.println("bit0 为0");
}

纠正:

// ✅ 正确:用括号明确优先级
int flag = 0b0011;
if ((flag & 0b0001) == 0) {  // 先按位与,再比较
    System.out.println("bit0 为0");
}

反例三:~ 取反的结果出乎意料

// ❌ 错误预期:~5 = -5?错!
System.out.println(~5);  // 输出 -6,而非 -5

原因:~ 是按位取反(含符号位),不是求相反数。~n = -n - 1(补码性质)。求相反数应该用 -n。


面试考点

Q1:>> 和 >>> 有什么区别?Java为什么需要无符号右移?

>> 是带符号右移(arithmetic shift),高位补符号位(正数补0,负数补1),保持数值符号不变。>>> 是无符号右移(logical shift),高位始终补0,结果总是非负数。

Java设计 >>> 的原因:(1)处理无符号数据(如网络字节流、哈希值);(2)实现位图算法;(3)HashMap的 hash() 方法中使用 >>> 让高位参与扰动,减少哈希碰撞。

Q2:如何使用位运算判断一个数是否是2的幂?

(n > 0) && ((n & (n - 1)) == 0)。原理:2的幂的二进制只有一个1,减1后该位变成0、后面全变1,按位与后必为0。例:8 (1000) & 7 (0111) = 0。此技巧在HashMap的 tableSizeFor() 中大量使用。

Q3:~5 为什么等于 -6 而不是 -5?

Java使用补码表示负数。5的32位二进制是 000...0101。按位取反(含符号位)得 111...1010。补码解释:这是一个负数,其绝对值 = ~(111...1010) + 1 = 000...0101 + 1 = 000...0110 = 6,所以值为 -6。通式:~n = -n - 1。

Q4:位运算的常用应用场景有哪些?

① 权限控制:一个int管理32种权限(如Linux文件权限 rwx = 4|2|1)。② 高效乘除:n << k 等价 n × 2^k。③ 哈希算法:JDK HashMap的 hash() 方法使用 >>> 扰动高位。④ 奇偶判断:n & 1 比 n % 2 理论上更高效。⑤ 数据压缩:多个小值打包到一个int中。⑥ 加密/校验:CRC校验、简单异或加密。⑦ 状态标志:用一个整数的不同bit表示不同状态。

上一页
逻辑运算符
下一页
条件运算符