Java位運算符詳解

前言

以前瞭解過位運算符,左移<<等於乘以2,右移>>等於除以2。可是我在看jdk源碼的時候發現了一個>>>三個符號的,不明白這是什麼意思,就去搜了一下,發現還挺多的知識點的,就整理了一下。java

首先咱們知道,咱們編寫的程序最終都是在計算機底層進行的,計算機底層也僅支持0、1兩種符號。因此當時網上有個鍵盤只有0、1兩個鍵,那纔是大佬用的鍵盤。扯遠了。。。ide

先來複習一下java的基本類型都佔多少字節,佔多少位(1字節等於8位):code

類型 字節數 位數 大小範圍
byte 1 8 -2^8^~2^8^-1
short 2 16 -2^16^~2^16^-1
int 4 32 -2^32^~2^32^-1
long 8 64 -2^64^~2^64^-1
float 4
double 8
char 2 16 一個char類型能夠存儲一個漢字
boolean 1 true or false

移位操做是把數據看做二進制數,而後將其向左或向右移動若干位的運算。在Java中,移位操做符包含三種:<<左移運算符,>>帶符號右移運算符,>>>無符號右移運算符。這三種操做符都只能做用於long,int,short,byte這四種基本整形類型上和char類型上。其餘類型如double都沒法使用位運算符,你們能夠在ide中自行試驗一下。get

在java中,第一位用來表示數字的正負,第一位爲零時表示正數,第一位爲1時表示負數。咱們拿最簡單的8位byte類型舉例:0000 0000表示0,0111 1111這個表示最大值(2^8^-1),再進行加一後就變成了1000 0000這時就變成了最小值(-2^8^)。再加一後變成1000 0001這時的值爲-127。也就是從0到最大值而後轉爲最小值,而後再從最小值向零靠近。源碼

左移操做符<<

左移操做符<<是將數據轉換爲二進制後,向左移動若干位,高位丟棄,低位補零博客

首先咱們能夠利用java中的方法獲取一個數的二進制:Integer.toBinaryString(int val)it

而後咱們看下面這個例子:table

public static void main(String[] args) {
  int a = 10;
        System.out.println("左移前的二進制:"+Integer.toBinaryString(a));
        a <<= 2;
        System.out.println("左移後的二進制:"+Integer.toBinaryString(a));
        System.out.println("左移後的十進制:"+a);
}

首先定義一個數,值爲10,打印它的二進制(1010),而後進行左移操做2位。打印移位後的結果和二進制。class

左移前的二進制:1010
左移後的二進制:101000
左移後的十進制:40

能夠看出,將原來的二進制向左移動了兩位,後面進行了補零。40=10 * 2 * 2。因此一次左移等於將這個數擴大了兩倍。再來看一個負數的左移:變量

int b = -8;
System.out.println("左移前的二進制:" + Integer.toBinaryString(b));
b <<= 2;
System.out.println("左移後的二進制:" + Integer.toBinaryString(b));
System.out.println("左移後的十進制:" + b);

咱們定義了一個負數(-8),打印出它的二進制,進行左移2位,左移後打印它的二進制,再將10進制打印出來查看。

左移前的二進制:11111111111111111111111111111000
左移後的二進制:11111111111111111111111111100000
左移後的十進制:-32

能夠明顯的看出二進制向左移動了兩位,前面的位置丟棄,後面的位置補零。轉換爲10進制也符合咱們以前的運算:-32 = -8 * 2 *2。

帶符號右移操做符>>

剛纔的左移中,它向左移動,高位進行了丟棄,低位進行補零。可是右移操做時有一個符號位,操做不當將形成答案與預期結果不一樣。

帶符號右移就是在向右移動若干位,低位進行丟棄,高位按照符號位進行填補。對於正數作右移操做時,高位補充0;負數進行右移時,高位補充1

再來用例子證實一下:

public static void main(String[] args) {
   int a = 1024;
   System.out.println("a右移前的二進制:" + Integer.toBinaryString(a));
   a >>= 4;
   System.out.println("a右移後的二進制:" + Integer.toBinaryString(a));
   System.out.println("a右移後的十進制:"+a);
   int b = -70336;
   System.out.println("b右移前的二進制:" + Integer.toBinaryString(b));
   b >>= 4;
   System.out.println("b右移後的二進制:" + Integer.toBinaryString(b));
   System.out.println("b右移後的十進制:"+b);
}

定義了兩個變量,a=1024,而後向右移動4位。b=-70336也向右移動4位。分別將它們的移動先後二進制和十進制打印出來查看。

a右移前的二進制:10000000000
a右移後的二進制:1000000
a右移後的十進制:64
b右移前的二進制:11111111111111101110110101000000
b右移後的二進制:11111111111111111110111011010100
b右移後的十進制:-4396

a原來的二進制向右移動後,低位被丟棄,高位補充符號位也就是0。b原來的二進制向右移動後,低位被丟棄,高位補充符號位1。這也符號咱們以前的運算規律:
1024 / 2^4^ =16 ;-70336/ 2^4^ = -4396。

無符號右移操做符>>>

剛纔的帶符號右移操做符,咱們在向右移動時帶着高位的符號,正數填充0,負數填充0。如今不帶符號的右移操做符大致與右移操做符一致,只不過再也不區分正負數,結果都是高位補零,低位丟棄。

再用例子來證實一下:

public static void main(String[] args) {
   int a = 1024;
   System.out.println("a右移前的二進制:" + Integer.toBinaryString(a));
   a >>>= 4;
   System.out.println("a右移後的二進制:" + Integer.toBinaryString(a));
   System.out.println("a右移後的十進制:"+a);
   int b = -70336;
   System.out.println("b右移前的二進制:" + Integer.toBinaryString(b));
   b >>>= 4;
   System.out.println("b右移後的二進制:" + Integer.toBinaryString(b));
   System.out.println("b右移後的十進制:"+b);
}

仍是剛纔帶符號右移的例子:此次咱們僅僅把操做符換成無符號的右移操做符。

按照定義,其實在正數時不會有變化,由於在帶符號的右移中正數也是高位補零。只不過當值爲負數時會有變化,讓咱們看一下輸出是否是符合猜測。

a右移前的二進制:10000000000
a右移後的二進制:1000000
a右移後的十進制:64
b右移前的二進制:11111111111111101110110101000000
b右移後的二進制:1111111111111110111011010100
b右移後的十進制:268431060

確實正數沒有變化,驗證了咱們的猜測。而後是負數,此次向右移動時高位進行了補零,低位丟棄。改變後的數值再也不符合咱們以前的規律。

在無符號右移中,當值爲正數時,依然符合以前的規律移動一位至關於除以2。可是當值爲負數時再也不符合規律。

當移位的位數超過數值所佔用的位數會怎麼樣?

這個問題頗有意思,咱們剛剛都僅僅移動了2位或者4位,若是咱們超過了int的位數也就是32位後會怎麼樣?咱們若是對一個正數左移32位,低位補零補充了32次就變成0了,就如同下面代碼所寫的同樣,最終a的結果會是什麼。會變成0嗎?

public static void main(String[] args) {
  int a = 10;
  a <<= 32;
  System.out.println(a);
}

通過咱們運行後發現a的結果最終沒變仍是10。咱們若是改爲左移33位,它的結果會變成20。那麼它的運算規律會不會是當超過位數後僅僅移動對位數的餘數呢?好比對int作操做,它實際是運算 位數%32次。

通過屢次試驗發現答案確實就是這個猜測,當對int類型處理時,右移x位的運算爲x%32位。

對其餘類型也是同樣嗎?

咱們剛纔都是用的int類型,那麼對於byte,short,char,long都同樣嗎?

先看一下byte類型。

public static void main(String[] args) {
   byte b = -1;
     System.out.println("操做前:"+b);
     b >>>= 6;
     System.out.println("操做後:"+b);
}

定義了byte的值爲-1,即1111 1111,而後無符號右移6位,高位補零,低位丟棄,那麼應該變成0000 0011也就是是3。讓咱們運行一下這段代碼看一下打印出來的信息是否是3呢?

操做前:-1
操做後:-1

運行結果與咱們預期的結果不一致!

咱們將它的二進制也一塊兒打印出來看一下究竟:

public static void main(String[] args) {
   byte b = -1;
   System.out.println("操做前十進制:"+b);
   System.out.println("操做前二進制:"+Integer.toBinaryString(b));
   b >>>= 6;
   System.out.println("操做後二進制:"+Integer.toBinaryString(b));
   System.out.println("操做後十進制:"+b);
}

這時再看一下運行結果

操做前十進制:-1
操做前二進制:11111111111111111111111111111111
操做後二進制:11111111111111111111111111111111
操做後十進制:-1

原來,Java在對byte,short,char這三種類型進行移位操做前,會將其先轉型爲int類型,而後再進行位操做。因爲咱們有進行了從新賦值將其賦值爲原來的byte類型,因此又進行了從intbyte的先下轉型,也就是截斷。咱們對上面的例子進行一下修改能夠更直觀的發現運行過程:

public static void main(String[] args) {
   byte b = -1;
   System.out.println("操做前十進制:"+b);
   System.out.println("操做前二進制:"+Integer.toBinaryString(b));
   System.out.println("進行無符號右移6位後的十進制:"+(b>>>6));
   System.out.println("操做後二進制:"+Integer.toBinaryString(b>>>6));
}

在這裏我沒有使用=進行從新賦值,而是計算完成後直接打印十進制和二進制的結果。

操做前十進制:-1
操做前二進制:11111111111111111111111111111111
進行無符號右移6位後的十進制:67108863
操做後二進制:11111111111111111111111111

從打印結果中能夠明顯的看出是先轉換爲int類型,而後進行位運算,位運算結束後因爲從新賦值因此進行的截斷。

對於long類型,它是64位,不用先轉換。

總結

移位符是Java中的基本操做符,實際支持的類型只有intlong。在對byte,short,char類型進行移位操做時,都會先將其轉換爲int類型再進行操做。左移<<操做符至關於乘以2。帶符號右移操做符>>至關於除以2。在Java中使用位運算符會比乘*,除/運算符更高效一些。而無符號右移符>>>在移動時高位補零,低位丟棄,在正數時仍然至關於除以2,可是在負數時結果倒是變大了(由負數變爲正數)。

本文由博客一文多發平臺 OpenWrite 發佈! 博主郵箱:liunaijie1996@163.com,有問題能夠郵箱交流。

相關文章
相關標籤/搜索