Skip to content

位运算基础(Java / Android 复习用) ​

一句话定义 ​

位运算是按二进制每一位独立运算(与、或、异或、取反、移位),用来在一个 int/long 里打包多个开关(标志位)、用掩码筛字段、或做快速乘除 2 的幂。

可运行 Demo:bitwise-operators-demo · BitwiseOperatorsBasicsDemo.java


核心模型:一个 int = 32 个独立开关 ​

文档里的 READ / WRITE / EXEC 是教学用的 rwx 假名;Android 里的 Intent.FLAG_ACTIVITY_NEW_TASK 等是同一套机制的真实常量:

  • 每个标志只占一位(二进制里只有一个 1,如 ...0010)
  • 用 | 合并多个标志(addFlags)
  • 用 & 查询是否包含某位(hasFlags 底层)
  • 用 |= / &= ~ 打开 / 关闭某位(addFlags / clearFlags)
  • 用 ^= 翻转某位(开变关、关变开;不是「属于」)

为什么用 1 << n,不直接写 1、2、3?

写法十进制二进制(低 4 位示意)问题
READ = 1<<010001只占第 0 位 ✓
WRITE = 1<<120010只占第 1 位 ✓
EXEC = 1<<240100只占第 2 位 ✓
若错写 EXEC = 330011同时占第 0、1 位,无法当独立 EXEC 开关 ✗

1 << n 的优势:一眼看出第几位是开关;数值上 READ/WRITE 碰巧是 1、2,但第三位必须是 4 而不是 3。Android 源码同样写 1 << 28,而不是死记 0x10000000(二者等价)。


为什么要学 ​

优势说明
快常数时间、无分支,CPU 直接做位操作
省内存一个 int 可塞 32 个布尔标志
表达力强掩码、权限、协议字段、hash 拌位都靠它
劣势说明
可读性差flags & MASK 不如命名良好的 API 直观
易写错优先级、符号位、>> vs >>> 搞混就翻车
调试难要会看二进制/十六进制才舒服

符号速查(含「记什么」) ​

符号名称规则(单 bit)工程里常干什么记忆口诀
&按位与都是 1 才 1查有没有(掩码筛位)筛子
|按位或有 1 就 1合并 / 打开标志开位
^按位异或相同 0,不同 1翻转某几位(toggle)翻位
~按位非0↔1 全翻转配合 & 关掉某位取反掩码
<<左移右补 0乘 2^n;1<<n 造标志位左移乘 2
>>有符号右移负数左补 1带符号 ÷ 2右移除 2(有符号)
>>>无符号右移左补 0高位「挪」到低位参与计算无符号右移

标志位四操作(Android Intent 底层同一套):

意图写法Android API
有没有(flags & MASK) != 0判 getFlags() / 权限位
打开flags |= MASKaddFlags(...)
关闭flags &= ~MASKclearFlags(...)
翻转flags ^= MASK少见直接 API;开关 UI 状态

注意:^ 是翻转(0→1、1→0),不是「属于」。「属于 / 包含」只用 &。


Android 高频用例:Intent FLAG(建议对照记忆) ​

下面与文档/demo 里的 READ | WRITE 完全同构,只是名字换成系统常量、位号更大:

java
// 源码概念:每个 FLAG 只占 int 中的一位(此处省略高位 0)
// FLAG_ACTIVITY_CLEAR_TOP  = 1 << 26  →  第 26 位为 1
// FLAG_ACTIVITY_NEW_TASK   = 1 << 28  →  第 28 位为 1
// FLAG_ACTIVITY_SINGLE_TOP = 1 << 29  →  第 29 位为 1

intent.addFlags(
    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP
);
// 等价于:把两个开关位都设为 1,其它位不动

int flags = intent.getFlags();
boolean hasNewTask = (flags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0;
// 等价于 READ 例子里的 (flags & READ) != 0

intent.clearFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// 底层等价于 flags &= ~FLAG_ACTIVITY_NEW_TASK
教学例子Android 真实例子操作
READ = 1<<0FLAG_ACTIVITY_NEW_TASK = 1<<28各占一位
flags |= READaddFlags(NEW_TASK)打开
(flags & READ) != 0(flags & NEW_TASK) != 0有没有
flags &= ~READclearFlags(NEW_TASK)关闭
READ | WRITENEW_TASK | CLEAR_TOP合并多个

其它 Android 里仍会碰到的位运算(知道场景即可,不必背常量值):

场景典型写法频率
颜色 ARGB 打包(alpha << 24) | (red << 16) | (green << 8) | blue高(自定义绘制、协议)
WindowManager.LayoutParams.flags同 Intent:或运算合并标志、与运算查询中(悬浮窗、全屏等)
MeasureSpec 打包宽高与 modesize & MODE_MASK、size | MODE中(读 View 测量源码)
HashMap 下标 / 对齐index & (capacity - 1)中(见集合专题)
hash 拌位h ^ (h >>> 16)中(集合专题)

View.VISIBLE / INVISIBLE / GONE 是互斥枚举值(0、4、8),一般不用 | 组合,不要和 FLAG 位图混淆。


Flutter 对照(浅) ​

Flutter 路由层多用 MaterialPageRoute(fullscreenDialog: true)、go_router 路径等 bool / enum,日常很少手写 int 位标志——类型安全、可读性优先。

仍会遇到同一套位运算的时机:

  • 插件 / 嵌入层:MainActivity、Platform Channel 里 startActivity 仍 addFlags(Intent.FLAG_...),与上文 Android 一致。
  • Dart 语法:&、|、^、<<、>> 与 Java 相同;自定义协议、FFI 结构体、能力位图时会写 const mask = 1 << 3。
  • 思想对照:Semantics(button: true, enabled: true) 是多个独立能力组合,只是用多个字段而非一个 int。

& 按位与 — 掩码筛子 ​

比喻:32 个开关里,掩码标出「我只关心这几格」,value & mask 把其它位清零。

java
int READ  = 1 << 0;  // ...0001
int WRITE = 1 << 1;  // ...0010
int flags = READ | WRITE;  // ...0011 → 十进制 3

boolean canRead = (flags & READ) != 0;   // 有没有 READ
int lowNibble = value & 0x0F;            // 只保留低 4 位

单 bit 标志用 (flags & MASK) != 0;若 MASK 占多个 bit 且要求「这几位全开」,用 (flags & MASK) == MASK。


|、^、~ — 开、翻、关 ​

java
flags |= READ;           // 打开:该位强行变 1(已是 1 仍保持 1)
flags &= ~READ;          // 关闭:~READ 除 READ 位外全 1,& 后清掉 READ 位
flags ^= TOGGLE_BIT;     // 翻转:该位 0→1、1→0;再 ^= 一次恢复原状
运算符对 mask 为 1 的位常见误用
|=一律设为 1想「关掉」却用了 |=
^=0↔1 切换想「有没有」却用了 ^
&筛出关心的位与 && 混用判标志

^ 性质:a ^ a = 0,a ^ 0 = a — 面试可能考无临时变量交换;工程里标志位以外少见。


移位:<< / >> / >>> ​

表达式结果直觉
1 << 38(只在第 3 位为 1)
5 << 220(5×4;溢出按类型截断)
-1 >> 1仍是 -1(符号位填充)
-1 >>> 1268435455(0x0FFFFFFF;高位补 0)

何时用哪个:带符号整数「除以 2」用 >>;要把高位无符号地参与低位计算(hash 拌位、无符号语义)用 >>>。日常业务代码里 >> / >>> 不如 &、|= 常见。


记什么、不记什么 ​

建议熟练(面试 + 读源码高频):

  1. 标志位四操作:& != 0 查、|= 开、&= ~ 关、^= 翻(并分清 ^ 不是「属于」)
  2. 1 << n 造标志位;合并用 |;不要把独立标志写成 1、2、3(第三位应是 4)
  3. & vs &&:& 不短路;判标志只用 &
  4. index & (size - 1) 在 size 为 2 的幂时等价 % size(HashMap 前提)
  5. ARGB 打包与 Intent.addFlags 的「每位一开关」模型

知道即可(用到再查常量表):

  • 各 FLAG_ACTIVITY_* 的具体位号与十六进制值
  • -1 >> n vs -1 >>> n 的精确数值
  • MeasureSpec、Window flag 的具体常量
  • ^ 交换变量等技巧题

Flutter 侧:记「Dart 运算符同 Java + 插件里仍是 Android FLAG」即可;路由层不必强行记位标志。


和后续专题的关系 ​


复习检查题 ​

  1. & 和 && 有什么区别?

    答:& 是按位与(对 int/long 等逐位运算);用在 boolean 上时是逻辑与,但两侧都会求值,不短路。&& 只能用于 boolean,是逻辑与且短路:左为 false 时不再算右侧。整数之间不能用 &&;判标志位用 (flags & READ) != 0,不是 flags && READ。

  2. -1 >> 4 和 -1 >>> 4 为何不同?

    答:int 的 -1 二进制为 32 个 1。>> 有符号右移,高位补符号位 1,结果仍是 -1。>>> 无符号右移,高位补 0,低 28 位为 1,结果是 268435455(0x0FFFFFFF)。集合 hash 拌位用 >>>,就是要避免符号位干扰低位。

  3. 如何用位运算判断 flags 是否包含某个标志?与 Intent.addFlags 有何对应?

    答:单 bit 标志(如 READ 或 FLAG_ACTIVITY_NEW_TASK)写 (flags & MASK) != 0 表示「有没有」。多 bit 掩码且要求全开时用 (flags & MASK) == MASK。打开 flags |= MASK ↔ addFlags;关闭 flags &= ~MASK ↔ clearFlags。翻转用 flags ^= MASK(toggle),不是判断包含。

  4. 为什么说「掩码取低 n 位」要求模数是 2 的幂?

    答:当 size = 2^n 时,size - 1 的二进制是低 n 位全 1,index & (size - 1) 等价于 index % size,且比取模更快。若 size 不是 2 的幂,(size - 1) 不再是连续低位掩码,按位与的结果不等于取模,下标会算错(HashMap 因此要求容量为 2 的幂)。

  5. 为什么标志位用 1 << n 定义,不能把三个标志写成十进制 1、2、3?

    答:独立标志要求各占一位。1、2 各占一位,但 3 的二进制是 11,同时占两位,无法表示「单独的第三个标志」。第三位应是 1<<2(十进制 4)。Android FLAG_* 同理:每位独立,用 | 合并,用 & 查询。

速记 ​

  • 一个 int = 32 个开关;1<<n 造开关,| 合并,& 查询。
  • & 筛位,| 开位,^ 翻位,~ 取反掩码;&= ~ 关位。
  • ^ 是翻转,不是「属于」;「属于」只用 &。
  • << / >> 乘除 2 的幂;无符号语义用 >>>。
  • Android:addFlags / clearFlags / getFlags & MASK 就是这套口诀的 API 皮。

站点构建时间:2026/8/24 23:43:17