Java19 Integer 位操作精解:compress与expand《Hacker‘s Delight》(第二版,7.4节)

compress(int i, int mask) 

这个方法是Java 19中新增的一个强大的位操作函数。

compress 方法的核心功能可以理解为 “按位过滤和压缩” 。

  1. 过滤 (Filter): 它使用 mask(掩码)作为过滤器。对于输入整数 i,只有那些在 mask 中对应位为 1 的比特才会被保留。
  2. 压缩 (Compress): 它将所有被保留下来的比特,按照它们在 i 中从低到高的原始顺序,紧凑地排列到返回值的低位端。

一个直观的例子

文档中给出的例子非常经典:compress(0xCAFEBABE, 0xFF00FFF0)

  • 输入 i0xCAFEBABE -> 1100 1010 1111 1110 1011 1010 1011 1110
  • 掩码 mask0xFF00FFF0 -> 1111 1111 0000 0000 1111 1111 1111 0000

执行过程:

  1. mask 的高8位是 FF,所以 i 的高8位 CA 被选中。
  2. mask 的次高8位是 00,所以 i 的次高8位 FE 被丢弃。
  3. mask 的次低12位是 FFF,所以 i 的对应12位 BAB 被选中。
  4. mask 的最低4位是 0,所以 i 的最低4位 E 被丢弃。
  5. 选中的比特 CABAB 被按顺序紧凑地排列,得到 0x000CABAB

expand

public static int expand(int i, int mask) 是一个静态方法,用于根据一个给定的掩码 mask 来“扩展”或“解压缩”整数 i 的位(bits)。这个操作可以看作是 Integer.compress(int i, int mask) 方法的逆过程。

核心功能: 对于 mask 中每一个为 1 的位,该方法会从输入整数 i 中按顺序(从最低位开始)取一个位,并将其放置到结果中与 mask 中那个 1 位对应的位置。结果中所有对应 mask 中 0 位的位置都将被清零。

Javadoc 注释解析与示例

我们先来看一下该方法的 Javadoc 注释,特别是它给出的示例,这有助于直观地理解其功能。

// ... existing code ...* @apiNote* Consider the simple case of expanding the digits of a hexadecimal* value:* {@snippet lang="java" :* expand(0x0000CABAB, 0xFF00FFF0) == 0xCA00BAB0* }* Starting from the least significant hexadecimal digit at position 0* from the right, the mask {@code 0xFF00FFF0} selects the first five* hexadecimal digits of {@code 0x0000CABAB}. The selected digits occur* in the resulting expanded value in order at positions 1, 2, 3, 6, and 7.
// ... existing code ...

让我们来分解这个例子:

  • 输入 i0x0000CABAB
  • 掩码 mask0xFF00FFF0
  • 预期结果0xCA00BAB0

分析过程:

  1. mask (0xFF00FFF0) 的二进制形式定义了哪些位是“有效”的目标位置。它的十六进制表示中,值为 F 的位置(第1, 2, 3, 6, 7位)代表了目标位置。
  2. i (0x0000CABAB) 是源数据。我们从 i 的最低位开始,依次取出位。
  3. expand 操作会将 i 的位填充到 mask 指定的有效位置中:
    • i 的最低的4位是 B (1011),它被放置在 mask 第一个有效区域(十六进制位 1),结果的 ...xxxB0 部分形成。
    • i 的接下来4位是 A (1010),它被放置在 mask 第二个有效区域(十六进制位 2),结果的 ...xxAB0 部分形成。
    • i 的再接下来4位是 B (1011),它被放置在 mask 第三个有效区域(十六进制位 3),结果的 ...xBABA0 部分形成。
    • mask 的第4、5位是 00,所以结果的对应位置也是 00
    • i 的再接下来4位是 A (1010),它被放置在 mask 第四个有效区域(十六进制位 6),结果的 ...A00BAB0 部分形成。
    • i 的最后4位是 C (1100),它被放置在 mask 第五个有效区域(十六进制位 7),最终形成 0xCA00BAB0

Javadoc 还提到了一个关键恒等式:expand(compress(x, m), m) == x & m。这清晰地表明 expand 和 compress 是一对互逆的操作。

源码解析:compress和expand是如何实现的?

实现源自经典著作《Hacker's Delight》(第二版,7.4节),是一种不包含分支和循环(Java代码中的for循环在编译后会展开)的高效并行算法。

以下分析改自 《Hacker's Delight》(第二版,7.4节)

compress

分步移动的数学原理​

  • ​总体目标​​:

    每个位需右移的距离 = 该位 对应mask ​​右侧 0 的数量​​(记为 d

  • ​分治策略​​:

    将 d拆解为二进制分量,分 5 轮处理:

    d = d₀ + 2×d₁ + 4×d₂ + 8×d₃ + 16×d₄ (其中 dᵢ ∈ {0,1} 是二进制系数)

    • ​第 j 轮​​:处理 ​​2ʲ​​ 的权重分量

    • ​移动条件​​:当轮需移动的位 = d二进制展开中 ​​2ʲ 的系数 dⱼ 为 1​​ 的位

​示例​​:某位需移动 d=6(二进制 110

  • 第一轮(j=0):移动 2⁰=1位(因 d₀=0→ ​​不移动​​)

  • 第二轮(j=1):移动 2¹=2位(因 d₁=1→ ​​移动​​)

  • 第三轮(j=2):移动 2²=4位(因 d₂=1→ ​​移动​​)

源码:

    @IntrinsicCandidatepublic static int compress(int i, int mask) {// See Hacker's Delight (2nd ed) section 7.4 Compress, or Generalized Extracti = i & mask; // Clear irrelevant bitsint maskCount = ~mask << 1; // Count 0's to rightfor (int j = 0; j < 5; j++) {// Parallel prefix// Mask prefix identifies bits of the mask that have an odd number of 0's to the rightint maskPrefix = parallelSuffix(maskCount);// Bits to moveint maskMove = maskPrefix & mask;// Compress maskmask = (mask ^ maskMove) | (maskMove >>> (1 << j));// Bits of i to be movedint t = i & maskMove;// Compress ii = (i ^ t) | (t >>> (1 << j));// Adjust the mask count by identifying bits that have 0 to the rightmaskCount = maskCount & ~maskPrefix;}return i;}

假设输入为:
x = abcd efgh ijkl mnop qrst uvwx yzAB CDEF,(Java代码里的 i
m = 1000 1000 1110 0000 0000 1111 0101 0101,

其中 x 中的每个字母代表一个比特(值为 0 或 1)。

x 中对应的比特 向右移动位数 等于该比特右边 m 中 0 的数量

首先清除 x 中不相关的比特会很方便,得到:
x = a000 e000 ijk0 0000 0000 uvwx 0z0B 0D0F。


首先确定哪些比特需要(向右)移动奇数个位置,并将它们移动一个比特位。​
这可以通过计算 mk = ~m << 1 并对结果执行 parallelSuffix 来完成。

得到:
mk = 1110 1110 0011 1111 1110 0001 0101 0100,
mp = 1010 0101 1110 1010 1010 0000 1100 1100。

可以观察到,

  • mk 标识了 m 中紧邻右侧是 0 的比特位,
  • mp 从右到左对这些位进行模 2 加法(parallelSuffix)。

因此,mp 标识了 m 中右侧有奇数个 0 的比特位。


​将要移动一个位置的比特是那些位于严格右侧有奇数个 0 的位置(由 mp 标识)并且在原始掩码中为 1-比特的位。​

这可以简单地通过 mv = mp & m 计算得出:
mv = 1000 0000 1110 0000 0000 0000 0100 0100。

m 的这些比特可以通过赋值语句移动:
m = (m ^ mv) | (mv >> 1);

x 的相同比特可以通过两个赋值语句移动:
t = x & mv;
x = (x ^ t) | (t >> 1);

(移动 m 的比特更简单,因为所有选中的比特都是 1。)

这里的异或操作是关闭 m 和 x 中已知的 1-比特,而或操作是打开 m 和 x 中已知的 0-比特。
这些操作也可以分别替换为异或,或者减法和加法。

​将由 mv 选择的比特向右移动一个位置后,结果是:​
m = 0100 1000 0111 0000 0000 1111 0011 0011,
x = 0a00 e000 0ijk 0000 0000 uvwx 00zB 00DF。


​现在我们必须为第二次迭代准备一个掩码,在这次迭代中,我们识别要向右移动 2 的奇数倍位置的比特。​

注意,mk & ~mp 这个量标识了那些在原始掩码 m 中紧邻右侧有偶数 0 的比特。【因为奇数0的位置 被删除了

这个量如果从右侧用 parallelSuffix 求和,就能识别出那些向右移动 2 的奇数倍(2, 6, 10 等)位置的比特。
因此,该过程就是将这个量赋给 mk ,并执行上述步骤的第二次迭代。

​修订后的 mk 值为:​
mk = 0100 1010 0001 0101 0100 0001 0001 0000。

expand

compress的逆向移动

public static int expand(int i, int mask) {// Save original maskint originalMask = mask;// Count 0's to rightint maskCount = ~mask << 1;int maskPrefix = parallelSuffix(maskCount);// Bits to moveint maskMove1 = maskPrefix & mask;// Compress maskmask = (mask ^ maskMove1) | (maskMove1 >>> (1 << 0));maskCount = maskCount & ~maskPrefix;maskPrefix = parallelSuffix(maskCount);// Bits to moveint maskMove2 = maskPrefix & mask;// Compress maskmask = (mask ^ maskMove2) | (maskMove2 >>> (1 << 1));maskCount = maskCount & ~maskPrefix;maskPrefix = parallelSuffix(maskCount);// Bits to moveint maskMove3 = maskPrefix & mask;// Compress maskmask = (mask ^ maskMove3) | (maskMove3 >>> (1 << 2));maskCount = maskCount & ~maskPrefix;maskPrefix = parallelSuffix(maskCount);// Bits to moveint maskMove4 = maskPrefix & mask;// Compress maskmask = (mask ^ maskMove4) | (maskMove4 >>> (1 << 3));maskCount = maskCount & ~maskPrefix;maskPrefix = parallelSuffix(maskCount);// Bits to moveint maskMove5 = maskPrefix & mask;int t = i << (1 << 4);i = (i & ~maskMove5) | (t & maskMove5);t = i << (1 << 3);i = (i & ~maskMove4) | (t & maskMove4);t = i << (1 << 2);i = (i & ~maskMove3) | (t & maskMove3);t = i << (1 << 1);i = (i & ~maskMove2) | (t & maskMove2);t = i << (1 << 0);i = (i & ~maskMove1) | (t & maskMove1);// Clear irrelevant bitsreturn i & originalMask;}

parallelSuffix(int maskCount)

这个方法是 Integer.compress 和 Integer.expand 的核心辅助函数。它的名字虽然叫 parallelSuffix(并行后缀),其实现的是一种“并行后缀异或扫描”算法。

代码 把变量命名为 maskPrefix,这是一个不恰当的命名

此方法计算一个“后缀”,但使用的不是加法,而是^(按位异或)运算。对于返回结果 maskPrefix 中的任意比特位 k,它的值等于输入 maskCount 中从第 0 位到第 k 位所有比特的异或总和。

result[k] = maskCount[0] ^ maskCount[1] ^ ... ^ maskCount[k]

该算法采用分治策略,在对数时间内完成计算:

// ... existing code ...@ForceInlineprivate static int parallelSuffix(int maskCount) {// 步骤1: 计算相邻比特的异或和int maskPrefix = maskCount ^ (maskCount << 1);// 步骤2: 计算相邻2比特块的异或和maskPrefix = maskPrefix ^ (maskPrefix << 2);// 步骤3: 计算相邻4比特块的异或和maskPrefix = maskPrefix ^ (maskPrefix << 4);// 步骤4: 计算相邻8比特块的异或和maskPrefix = maskPrefix ^ (maskPrefix << 8);// 步骤5: 计算相邻16比特块的异或和maskPrefix = maskPrefix ^ (maskPrefix << 16);return maskPrefix;}
// ... existing code ...
  • maskPrefix = maskCount ^ (maskCount << 1);: 第一步,每个比特位与它右边(低位)的比特进行异或。现在每个比特位存储了它自己和右边邻居的异或结果。
  • maskPrefix = maskPrefix ^ (maskPrefix << 2);: 第二步,将相邻的2比特块进行异或。这会把之前的结果组合起来,现在每个比特位存储了原始值中连续4个比特的异或和。
  • 后续步骤: 这个过程不断重复,块的大小翻倍(4, 8, 16),直到最终每个比特位都包含了从最低位到当前位所有比特的异或总和。

在 compress 和 expand 方法中,需要将源整数中的某些位根据掩码(mask)移动到新的位置。这个移动的距离不是固定的。parallelSuffix 的作用就是高效地、并行地计算出所有需要移动的位应该移动多远,是实现这两个复杂位操作算法的关键基石。

@ForceInline 注解建议JIT编译器将这个短小精悍的函数直接内联到调用它的地方(compressexpand),以消除函数调用的开销,追求极致性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93047.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93047.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

minio部署和双机热备

安装单机版MinIO&#xff08;准备2台机器A、B,A、B服务器操作一致&#xff09;切换目录并下载MinIO二进制文件cd /usr/local/bin wget https://dl.minio.org.cn/server/minio/release/linux-amd64/minio chmod x minio编辑配置文件vi /etc/default/minio.confMINIO_VOLUMES&quo…

【Java】 Java 21 革命性升级:虚拟线程与结构化并发的深度实践指南

还在为高昂的AI开发成本发愁?这本书教你如何在个人电脑上引爆DeepSeek的澎湃算力! Java 21 作为 Oracle JDK 的长期支持版本,引入了多项革命性特性,其中虚拟线程(Virtual Threads)和结构化并发(Structured Concurrency)尤为突出。这些特性旨在解决传统线程模型在高并发…

Apache IoTDB 全场景部署:基于 Apache IoTDB 的跨「端-边-云」的时序数据库 DB+AI

Apache IoTDB 全场景部署&#xff1a;基于 Apache IoTDB 的跨「端-边-云」的时序数据库 DBAI 文章目录Apache IoTDB 全场景部署&#xff1a;基于 Apache IoTDB 的跨「端-边-云」的时序数据库 DBAIApache IoTDB 介绍Docker部署指导企业版数据库配套工具 WorkbenchTimechoDB&…

计算机网络---传输控制协议Transmission Control Protocol(TCP)

一、TCP的定位与核心特性 TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;是TCP/IP协议栈中传输层的核心协议&#xff0c;与UDP&#xff08;用户数据报协议&#xff09;共同承担端到端数据传输功能。其设计目标是在不可靠的IP网络上提供可靠…

week1-[分支嵌套]公因数

week1-[分支嵌套]公因数 题目描述 给定 444 个正整数 a,b,c,ka,b,c,ka,b,c,k。如果 a,b,ca,b,ca,b,c 都是 kkk 的倍数&#xff0c;那么称 kkk 是 a,b,ca,b,ca,b,c 的公因数。否则如果某两个数都是 kkk 的倍数&#xff0c;那么称 kkk 是这两个数的公因数。问 kkk 是哪些数的公因…

C#枚举/结构体讲一讲

先展示一段简单代码// 定义枚举 public enum thisday {吃饭,不吃 }// 定义结构体 public struct person {public string name;public int age;public thisday zhuangtai; // 使用枚举类型作为字段 }static void Main(string[] args) {// 创建结构体实例person thisperson;thisp…

C++-setmap详解

Cset&map 1. 序列式容器和关联式容器 1.1 序列式容器 序列式容器按照线性顺序存储元素&#xff0c;元素的位置取决于插入的时间和位置&#xff0c;与元素的值无关。 主要特点&#xff1a;元素按插入顺序存储可以通过位置&#xff08;索引&#xff09;直接访问元素不自动排序…

解决程序连不上RabbitMQ:Attempting to connect to/access to vhost虚拟主机挂了的排错与恢复

前言&#xff1a;在分布式系统里&#xff0c;RabbitMQ作为消息中间件&#xff0c;是服务间通信的关键纽带。但实际使用中&#xff0c;程序连接RabbitMQ失败的情况时有发生。本文结合真实报错&#xff0c;细致呈现从问题发现到解决的完整排错思路&#xff0c;还会深入讲解Rabbit…

K8S中如何配置PDB(Pod Disruption Budget)

1. PDB 核心概念作用&#xff1a;控制自愿中断&#xff08;如节点升级、缩容&#xff09;期间&#xff0c;应用的最小可用副本数或最大不可用比例。关键参数&#xff1a;minAvailable&#xff1a;必须保持运行的 Pod 数量&#xff08;如 2 或 50%&#xff09;。maxUnavailable&…

从 0 到 1:用 MyCat 打造可水平扩展的 MySQL 分库分表架构

一、为什么要分库分表&#xff1f; 单机 MySQL 的极限大致在&#xff1a;维度经验值单表行数≤ 1 000 万行&#xff08;B 树三层&#xff09;单库磁盘≤ 2 TB&#xff08;SSD&#xff09;单机 QPS≤ 1 万&#xff08;InnoDB&#xff09;当业务继续增长&#xff0c;数据量和并发…

电池模组奇异值分解降阶模型

了解如何将奇异值分解 (SVD) 降阶模型 (ROM) 应用于电池模块热模拟。挑战随着电池模块在电动汽车和储能系统中的重要性日益提升&#xff0c;其热性能管理也成为一项重大的工程挑战。高功率密度会产生大量热量&#xff0c;如果散热不当&#xff0c;可能导致电池性能下降、性能下…

《Python函数:从入门到精通,一文掌握函数编程精髓》

坚持用 清晰易懂的图解 代码语言&#xff0c;让每个知识点变得简单&#xff01; &#x1f680;呆头个人主页详情 &#x1f331; 呆头个人Gitee代码仓库 &#x1f4cc; 呆头详细专栏系列 座右铭&#xff1a; “不患无位&#xff0c;患所以立。” Python函数&#xff1a;从入门到…

【记录贴】STM32 I2C 控制 OLED 卡死?根源在 SR1 与 SR2 的读取操作

问题描述最近在复用以前STM32F407控制OLED的代码&#xff0c;移植到STM32F103 上&#xff0c;使用硬件 I2C 通信方式。按照常规流程&#xff0c;先发送 OLED 的从机地址&#xff0c;OLED 有正常应答&#xff0c;但当发送第一个控制命令&#xff08;0xAE&#xff09;前的控制字节…

【AI驱动的语义通信:突破比特传输的下一代通信范式】

文章目录1 语义通信简介1.1 基本概念&#xff1a;什么是语义通信&#xff1f;语义通信的核心目标1.2 基本结构&#xff1a;语义通信系统结构语义通信系统的通用结构组成语义通信系统的结构关键模块1.3 基于大模型的语义通信关键技术&#x1f9e0;语义通信系统中AI大模型的设计建…

网络原理-HTTP

应用层自定义协议自定义协议是指根据特定需求设计的通信规则&#xff0c;用于设备或系统间的数据交换。其核心在于定义数据结构、传输方式及处理逻辑。协议结构示例典型的自定义协议包含以下部分&#xff1a;头部&#xff08;Header&#xff09;&#xff1a;标识协议版本、数据…

ROS配置debug指南

一. 安装插件 下面的这一个插件过期了需要用下面的这一个插件来替换:二. 设置CMakeLists.txt的编译模式 set(CMAKE_BUILD_TYPE "Debug") set(CMAKE_CXX_FLAGS_DEBUG "$ENV{CXXFLAGS} -O0 -Wall -g -ggdb") set(CMAKE_CXX_FLAGS_RELEASE "$ENV{CXXFLAG…

微软正式将GPT-5接入Microsoft Copilot Studio(国际版)

微软宣布正式在Microsoft Copilot Studio&#xff08;国际版&#xff09;中集成GPT-5&#xff0c;推动智能体构建能力实现突破性升级。此次更新不仅为企业用户带来更高效的响应速度、更精准的语境理解能力&#xff0c;还通过增强的逻辑推理功能&#xff0c;显著提升了AI交互的深…

微算法科技(NASDAQ:MLGO)通过蚁群算法求解资源分配的全局最优解,实现低能耗的区块链资源分配

随着区块链网络规模的不断扩大和业务需求的日益复杂&#xff0c;资源分配问题逐渐成为制约其发展的关键因素之一。传统的区块链资源分配方法往往存在效率低下、能耗过高、难以达到全局最优解等问题。高能耗不仅增加了运营成本&#xff0c;还对环境造成了较大的压力。因此&#…

深入浅出JVM:Java虚拟机的探秘之旅

深入浅出JVM&#xff1a;Java虚拟机的探秘之旅一、JVM 初相识&#xff1a;揭开神秘面纱 在 Java 的世界里&#xff0c;JVM&#xff08;Java Virtual Machine&#xff0c;Java 虚拟机&#xff09;就像是一个神秘的幕后大 boss&#xff0c;掌控着 Java 程序运行的方方面面。你可以…

Nginx学习笔记(八)—— Nginx缓存集成

&#x1f5c4;&#x1f5c4; Nginx缓存集成 &#x1f4cc;&#x1f4cc; 一、缓存核心价值 #mermaid-svg-CNji1KUDOsF8MwoY {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-CNji1KUDOsF8MwoY .error-icon{fill:#5522…