Java 位運算符和移位運算符

參考:

Bitwise and Bit Shift Operators

《Java 編程思想 第3章 操作符》


今天學習 Java BitSet 類時,發現對於位運算符和移位運算符的操作有些陌生,所以重新複習一下


主要內容:

  1. 位操作淺析
  2. 位運算符
  3. 移位運算符
  4. 優先級
  5. 問題解析
  6. 取值範圍

位操作淺析

Java 可在整數類型(integral type)數據上進行位(bit)操作

整數類型:

  • 字節型(byte8 位)
  • 短整型(short16 位)
  • 整型(int32 位)
  • 長整型(long64 位)

原碼,反碼和補碼

參考:

原碼,反碼和補碼的關係?

原碼、反碼、補碼的產生、應用以及優缺點有哪些?

首先原碼,反碼和補碼都是基於二進制數進行的

對於正數而言,其原碼,反碼和補碼一致(無符號數就是正數

Java 中的整數類型都是有符號數,即其最高位爲符號位(正數爲 0,負數爲 1

默認情況下,二進制數就是原碼錶示,所以將十進制數 15 轉換爲二進制原碼就是(假定爲 8 位整數)

00001111

將十進制數 -15 轉換爲二進制原碼就是

10001111

原碼和反碼相互轉換規則:負數保留符號位不變,其它位按位取反

將十進制 -15 轉換爲二進制反碼就是

11110000

原碼和補碼相互轉換規則:負數符號位不變,其餘位求反再加 1

將十進制 -15 轉換爲二進制補碼就是

11110001

加減運算

在計算機中,使用補碼保存整數類型數據因爲補碼格式有利於計算機進行移位運算

加減運算時,補碼直接相加即可(符號位參與運算

進制轉換

Java 不能直接表示二進制整數,但可以表示成八進制(以數字 0 開頭),十進制(沒有前置)和十六進制(以數字 0 和 字符 x 開頭),默認情況使用十進制計算

比如對於整數 15 來說,其八進制表示爲 017,十六進制表示爲 0xf

通常情況下使用 十六進制 來表示 二進制

一個整型數據佔 4 個字節,共 32 位,那麼對於整型數 a = 15 來說,其二進制表示如下

// 前面共 28 個 0
0000...0001111

轉換爲十六進制,就是 0x0000000f


位運算符

Java 提供了 4 種位運算符

  • 位與運算符(bitwise and operator):&
  • 位或運算符(bitwise inclusive or operator):|
  • 位異或運算符(bitwise exclusive or operator):^
  • 位取反運算符(bitwise invert operator):~

這些運算符是在二進制補碼上進行操作

測試程序如下:

public static void main(String[] args) {
    byte a = 15;
    byte b = -15;

    System.out.println(a & b);
    System.out.println(a | b);
    System.out.println(a ^ b);
    System.out.println(~a);
    System.out.println(~b);
}

這裏寫圖片描述

一個字節數佔 8 位,將 ab 轉換爲二進制:

a = 0000 1111
b = 1111 0001

Note:計算機使用補碼錶示

位與運算符:僅當兩個操作數同一下標的值均爲 1 時,結果才爲 1

a & b = 0000 1111 & 1111 0001 = 0000 0001(補) = 0000 0001(原) = 1

位或運算符:只要兩個操作數同一下標的值有一個爲 1 時,結果就爲 1

a | b = 0000 1111 & 1111 0001 = 1111 1111(補) = 1000 0001(原) = -1

位異或運算符:只有兩個操作數同意下標的值不相等時,結果才爲 1

a ^ b = 0000 1111 ^ 1111 0001 = 1111 1110(補) = 1000 0010(原) = -2

位取反運算符:按位取反每一位

~a = ~0000 1111 = 1111 0000(補) = 1001 0000(原) = -16
~b = ~1111 0001 = 0000 1110(補) = 0000 1110(原) = 14

Note 1:byte 或者 short 類型數值進行位運算後,返回的是 int 類型數值(沒有找到資料說明在位運算之前是否已經進行了轉換,不過先將 ab 轉換爲 int 類型二進制再進行計算的結果和上面一致)

Note 2:位運算符的操作不排除符號位


移位運算符

Java 提供了 3 種移位運算符

  • 左移運算符(left shift operator):<<
  • 右移運算符(right shift operator):>>
  • 無符號右移運算符(unsigned right shift operator):>>>

示例程序如下:

public static void main(String[] args) {
    System.out.println("正數移位");
    compute(15);
    System.out.println("負數移位");
    compute(-15);
}

public static void compute(int a) {
    println(a << 3);
    println(a << -61);
    println(a << 35);

    println(a >> 3);
    println(a >> -61);
    println(a >> 35);

    println(a >>> 3);
    println(a >>> -61);
    println(a >>> 35);
}

public static void println(int n) {
    System.out.println(n);
}

對於移位運算符而言,左側操作數表示要移動的二進制數,右側操作數表示要移動的位數

進行移位操作時,需要注意以下幾點:

  • 對於 byte 或者 short 類型數值,進行移位操作時,會先轉換爲 int 類型,然後進行移位(如果是 long 類型,則不變

  • 對於右側操作數而言,在進行移位之前,先轉換爲二進制數(補碼)。如果左側數是 int 類型,則取右側操作數最右端 5 位數值進行移動;如果是 long 類型數值,則取右側操作數最右端 6 位數值進行移動

左移運算符:數值位向左移動指定位數

15 << 3 = 0x0000000f << 3 = 0x00000078(補,原) = 120
15 << -61 = 0x0000000f << 0xffffffc3(左側是 int 類型,取右側 5 位) = 0x0000000f << 3 = 0x00000078(補,原) = 120
15 << 35 = 0x0000000f << 0x00000023(左側是 int 類型,取右側 5 位) = 0x0000000f << 3 = 0x00000078(補,原) = 120

-15 << 3 = 0xfffffff1 << 3 = 0xffffff88(補) = 0x80000078(原) = -120
-15 << -61 = 0xfffffff1 << 0xffffffc3(左側是 int 類型,取右側 5 位) = 0xfffffff1 << 3 = 0xffffff88(補) = 0x80000078(原) = -120
-15 << 35 = 0xfffffff1 << 0x00000023(左側是 int 類型,取右側 5 位) = 0xfffffff1 << 3 = 0xffffff88(補) = 0x80000078(原) = -120

右移運算符:數字位向右移動指定位數(如果左操作數是正數,高位補 0 ;如果是負數,高位補 1

15 >> 3 = 0x0000000f >> 3 = 0x00000001 = 1
-15 >> 3 = 0xfffffff1 >> 3 = 0xfffffffe(補) = 0x80000002(原) = -2

無符號右移運算符:功能和右移運算符一樣,不過無論正負,高位均補 0

15 >>> 3 = 0x0000000f >>> 3 = 0x00000001 = 1
-15 >> 3 = 0xfffffff1 >>> 3 = 0x1ffffffe(補,原) = 2^29 - 2 = 536870910

Note 1:移位運算時,從符號位開始操作

Note 2:由結果可知,左移一位相當於乘以2,右移一位相當於除以 2


優先級

參考:運算符優先級

Java 運算符優先級如下圖所示:

這裏寫圖片描述

由圖中可知,位運算符和移位運算符的優先級從左到右如下:

~,<<,>>,>>>,&,^,|


問題解析

之前學習類 BitSet 時遇到了很多的位運算,但是有一些操作沒搞明白,下面是我總結的一些問題和解答

  • 問題一:移動位數超過其精度如何解決

    • 解答:在進行移位操作之前,先將右側數值轉換成二進制(其實在計算機內部就是以二進制補碼保存的)。如果左側操作數爲 int 類型數值,那麼取右側操作數的最右端 5 位進行移位;或者左側操作數是 long 類型數值,取右側操作數的最右端 6 位進行移位
  • 問題二:移動位數爲負如何解決

    • 解答:和問題一的解答一樣,取右側操作數的最右端 5/6 位進行移位
  • 問題三:如何解決符號位的問題

    • 解答:計算機以二進制補碼形式保存整型數值。
      • 無論是加減 / 位運算 / 移位運算,符號位均參與其中
  • 問題四:已知起始下標 fromIndex 和 終止下標 toIndex,如何在位集中設定這一段連續區間爲 true

    • 解答:以 int 類型爲例,位集長度爲 32 位,假設 fromIndex = 3toIndex = 10,那麼示例程序如下:

      public static final int WORD_MASK = 0xffffffff;
      
      public static void main(String[] args) {
          int fromIndex = 3;
          int toIndex = 10;
      
          int firstWordMask = WORD_MASK << fromIndex;
          int lastWordMask = WORD_MASK >>> -toIndex;
          int res = (firstWordMask & lastWordMask);
          System.out.println(Integer.toBinaryString(firstWordMask));
          System.out.println(Integer.toBinaryString(lastWordMask));
          System.out.println(Integer.toBinaryString(res));
      }
      

      這裏寫圖片描述

      要設定位集中連續區間位值爲 true,可以設定一個輔助常量 WORD_MASK,保證每個位均爲 true

      定義起始下標 fromIndex = 3,結束下標 toIndex = 10

      計算 firstWordMask,使得 WORD_MASK 向左移動 fromIndex 個位置,低位補 0,結果使得區間 [0-fromIndex) 的位值爲 0

      計算 lastWordMask,使得 WORD_MASK 向右移動 n 個位置,高位補 0,結果使得區間 (toIndex-32] 的位值爲 0

      最後進行位與操作,得到區間 [fromIndex-toIndex] 的位值爲 true

      Note:對於 int 值而言,設 a = 3,則取 -3 的後 5 位就是 (32-3)=29;若是 long 值,取 -3 的後 6 位就是 (64-3)=61


取值範圍

字節類型佔 8 位,其中最高位爲符號位,所以其取值範圍爲 [-2^7-1,2^7-1] = [-127,127],其中 0 有兩種表示方式

00000000 或者 10000000

10000000 當作 -128,則 字節類型的取值爲 [-128,127]

public static final byte   MIN_VALUE = -128;
public static final byte   MAX_VALUE = 127;

示例程序如下:

public static void main(String[] args) {
    System.out.println(Integer.toBinaryString(Byte.toUnsignedInt(Byte.MAX_VALUE)));
    System.out.println(Integer.toBinaryString(Byte.toUnsignedInt(Byte.MIN_VALUE)));
}

這裏寫圖片描述

同理,短整型的取值範圍爲 [-2^15,2^15-1],整型的取值範圍爲 [-2^31,2^31-1],長整型的取值範圍爲 [-2^63,2^63-1]

// Short.java
public static final short   MIN_VALUE = -32768;
public static final short   MAX_VALUE = 32767;

// Integer.java
@Native public static final int   MIN_VALUE = 0x80000000;
@Native public static final int   MAX_VALUE = 0x7fffffff;

// Long.java
@Native public static final long MIN_VALUE = 0x8000000000000000L;
@Native public static final long MAX_VALUE = 0x7fffffffffffffffL;

示例程序如下:

public static void main(String[] args) {
    System.out.println(Integer.toBinaryString(Short.toUnsignedInt(Short.MAX_VALUE)));
    System.out.println(Integer.toBinaryString(Short.toUnsignedInt(Short.MIN_VALUE)));

    System.out.println(Integer.toBinaryString(Integer.MAX_VALUE));
    System.out.println(Integer.toBinaryString(Integer.MIN_VALUE));

    System.out.println(Long.toBinaryString(Long.MAX_VALUE));
    System.out.println(Long.toBinaryString(Long.MIN_VALUE));
}

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章