Appearance
位运算基础(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<<0 | 1 | 0001 | 只占第 0 位 ✓ |
WRITE = 1<<1 | 2 | 0010 | 只占第 1 位 ✓ |
EXEC = 1<<2 | 4 | 0100 | 只占第 2 位 ✓ |
若错写 EXEC = 3 | 3 | 0011 | 同时占第 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 |= MASK | addFlags(...) |
| 关闭 | flags &= ~MASK | clearFlags(...) |
| 翻转 | 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<<0 | FLAG_ACTIVITY_NEW_TASK = 1<<28 | 各占一位 |
flags |= READ | addFlags(NEW_TASK) | 打开 |
(flags & READ) != 0 | (flags & NEW_TASK) != 0 | 有没有 |
flags &= ~READ | clearFlags(NEW_TASK) | 关闭 |
READ | WRITE | NEW_TASK | CLEAR_TOP | 合并多个 |
其它 Android 里仍会碰到的位运算(知道场景即可,不必背常量值):
| 场景 | 典型写法 | 频率 |
|---|---|---|
| 颜色 ARGB 打包 | (alpha << 24) | (red << 16) | (green << 8) | blue | 高(自定义绘制、协议) |
WindowManager.LayoutParams.flags | 同 Intent:或运算合并标志、与运算查询 | 中(悬浮窗、全屏等) |
MeasureSpec 打包宽高与 mode | size & 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 << 3 | 8(只在第 3 位为 1) |
5 << 2 | 20(5×4;溢出按类型截断) |
-1 >> 1 | 仍是 -1(符号位填充) |
-1 >>> 1 | 268435455(0x0FFFFFFF;高位补 0) |
何时用哪个:带符号整数「除以 2」用 >>;要把高位无符号地参与低位计算(hash 拌位、无符号语义)用 >>>。日常业务代码里 >> / >>> 不如 &、|= 常见。
记什么、不记什么
建议熟练(面试 + 读源码高频):
- 标志位四操作:
& != 0查、|=开、&= ~关、^=翻(并分清^不是「属于」) 1 << n造标志位;合并用|;不要把独立标志写成 1、2、3(第三位应是 4)&vs&&:&不短路;判标志只用&index & (size - 1)在size为 2 的幂时等价% size(HashMap 前提)- ARGB 打包与
Intent.addFlags的「每位一开关」模型
知道即可(用到再查常量表):
- 各
FLAG_ACTIVITY_*的具体位号与十六进制值 -1 >> nvs-1 >>> n的精确数值MeasureSpec、Window flag 的具体常量^交换变量等技巧题
Flutter 侧:记「Dart 运算符同 Java + 插件里仍是 Android FLAG」即可;路由层不必强行记位标志。
和后续专题的关系
- HashMap 下标、扩容 rehash → map-concurrency-compare
- CAS / Atomic* → CAS 与无锁原子基础
复习检查题
&和&&有什么区别?答:
&是按位与(对int/long等逐位运算);用在boolean上时是逻辑与,但两侧都会求值,不短路。&&只能用于boolean,是逻辑与且短路:左为false时不再算右侧。整数之间不能用&&;判标志位用(flags & READ) != 0,不是flags && READ。-1 >> 4和-1 >>> 4为何不同?答:
int的-1二进制为 32 个1。>>有符号右移,高位补符号位1,结果仍是-1。>>>无符号右移,高位补0,低 28 位为1,结果是268435455(0x0FFFFFFF)。集合 hash 拌位用>>>,就是要避免符号位干扰低位。如何用位运算判断
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),不是判断包含。为什么说「掩码取低 n 位」要求模数是 2 的幂?
答:当
size = 2^n时,size - 1的二进制是低 n 位全 1,index & (size - 1)等价于index % size,且比取模更快。若size不是 2 的幂,(size - 1)不再是连续低位掩码,按位与的结果不等于取模,下标会算错(HashMap 因此要求容量为 2 的幂)。为什么标志位用
1 << n定义,不能把三个标志写成十进制 1、2、3?答:独立标志要求各占一位。
1、2各占一位,但3的二进制是11,同时占两位,无法表示「单独的第三个标志」。第三位应是1<<2(十进制 4)。AndroidFLAG_*同理:每位独立,用|合并,用&查询。
速记
- 一个
int= 32 个开关;1<<n造开关,|合并,&查询。 &筛位,|开位,^翻位,~取反掩码;&= ~关位。^是翻转,不是「属于」;「属于」只用&。<</>>乘除 2 的幂;无符号语义用>>>。- Android:
addFlags/clearFlags/getFlags & MASK就是这套口诀的 API 皮。