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)

  • 輸入?i:?0xCAFEBABE?->?1100 1010 1111 1110 1011 1010 1011 1110
  • 掩碼?mask:?0xFF00FFF0?->?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 ...

讓我們來分解這個例子:

  • 輸入?i:?0x0000CABAB
  • 掩碼?mask:?0xFF00FFF0
  • 預期結果:?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):移動?21=2位(因?d?=1→ ??移動??)

  • 第三輪(j=2):移動?22=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
英文地址,請注明出處:http://en.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…