1. 程式人生 > 其它 >一、java基礎-9.1_Java語言位運算子詳解

一、java基礎-9.1_Java語言位運算子詳解

很多程式語言都有位運算子,Java語言也不例外。在Java語言中,提供了7種位運算子,分別是按位與(&)、按位或(|)、按位異或(^)、取反(~)、左移(<<)、帶符號右移(>>)和無符號右移(>>>)。這些運算子當中,僅有~是單目運算子,其他運算子均為雙目運算子。在講解這些運算子的使用之前,必須瞭解一個常識,那就是:位運算子是對long、int、short、byte和char這5種類型的資料進行運算的,我們不能對double、float和boolean進行位運算操作。下面就來詳細講解這7種位運算子的使用方法。

一、按位與運算子

按位與運算子的寫法是一個”&”符號,與”不短路的邏輯與運算子”寫法是完全一樣的,但意義不同。邏輯與運算是對布林型資料進行運算,而按位與運算是對二進位制位上的數值進行計算。按位與運算子的運算規則如下圖所示:

運算規則總結成一句話就是:如果兩個二進位制位上的數都是1,那麼運算結果為1,其他情況運算結果均為0。下面舉例說明按位與運算子的運算過程,我們用數字5和6進行按位與運算。這個過程可以用下圖表示:

運算過程中,首先把5和6這兩個數字轉換為補碼,之後還要把這兩個數字按位對齊,然後一一把兩個相應的二進位制位上的數字進行按位與運算,運算得到的二進位制串就是最終的結果。按照補碼反向轉換為十進位制數字的規則,可以計算出5&6的運算結果是4。在這裡要提醒大家一句:進行位運算的時候,最左邊的符號位也是要參與運算的。

二、按位或運算子

按位或運算子的寫法是一個”|”符號,與”不短路的邏輯或運算子”寫法相同,它的運算規則也很簡單,如下圖所示:

運算規則概括成比較好記的一句話就是:兩個二進位制位上的數字如果都為0,那麼運算結果為0,否則運算結果是1。同按位與運算一樣,符號位也要參與運算。下面我們還是用5和6為例來講解一下按位或的運算過程,如下圖所示:

首先還是把這兩個數字轉換成補碼形式,之後把相應的二進位制位上的數字進行按位或運算:如果兩個二進位制數都是0,計算結果為0,其他情況計算結果均為1。按照這個規則把每一位上的數字都計算一遍後,得到二進位制的運算結果是111,這個運算結果轉換為十進位制數是7。

三、按位異或運算子

按位異或運算子寫法是”^”,它的運算規則如下圖:

如上圖,運算規則為:兩個二進位制位上的數字如果相同,則運算結果為0,如果兩個二進位制位上的數字不相同,則運算結果為1。下面我們還是用5和6為例來講解一下異或的運算過程,如下圖:

首先還是把這兩個數字轉換成補碼形式,之後把相應的二進位制位上的數字進行異或運算,如果對應的兩個二進位制位上的數相同,計算結果為0,否則計算結果為1。按照這個規則把每一位上的數字都計算一遍後,得到二進位制的運算結果是11,這個運算結果轉換為十進位制數是3。

關於異或運算子,有很多非常有用的特性,我們在這裡梳理總結一下。

Ⅰ、異或運算子滿足交換律

也就是說,a^b與b^a是等價的,雖然a和b交換了位置,但還是會運算出相同的結果。這個規律還可以推廣到N個運算元,也就是說,如果有N個變數都參與了異或運算,那麼它們的位置無論如何交換,運算的結果都是相同的。

Ⅱ、任何兩個相同的數字進行異或操作,所得到的結果都必然為0

這個特性並不難理解,因為兩個相同的數字,換算成補碼後,每個二進位制位上的數也都相同,這樣在進行異或運算時,按照運算規則,每個二進位制位上得到的運算結果也都是0,這N個0所組成的二進位制串就是數字0的補碼。我們可以利用這個特性快速的判斷兩個整數是否相同。另外,利用這個特性還可以實現記憶體的快速清零操作,比如我們可以在程式碼中寫上a=a^a;這條語句能快速的把變數a所佔據的那幾個位元組的記憶體迅速清零。

Ⅲ、對於任意一個二進位制位來說,這個位上的數與0進行異或運算,運算結果與這個二進位制位上的數是相同的,而與1進行異或運算,結果與這個二進位制位上的數字相反

注意,我們現在說的是二進位制位上的數字,所謂相反不是說原來這個位上是1,運算結果是-1,而是說原來是1,運算結果為0,原來如果是0,運算結果是1,這才是此處所說的”相反”的概念。這個特性也非常好理解,小夥伴們一定要記住它,在以後進行一些位運算操作的時候經常會用到這個特性。

Ⅳ、對於任何兩個整數a和b,a^b^b等於a

這個結論為什麼成立呢?簡單說來,就是因為這個表示式中有b^b,而b^b的結果為0,前文已講過,任何一個數與0進行按位異或操作,結果仍然是這個數本身,所以,a^b^b等於a。這個特性在加密運算方面有著很普遍的應用。我們可以把a當作要加密的資料,而把b當作金鑰。a異或b就是把a用金鑰b進行了加密操作,當需要解密時,仍然以b作為金鑰,再進行一次異或就實現瞭解密。

這個特性還可以推出另外一個結論:對於任何兩個整數a和b,a^b^a等於b。我們能夠得到這個結論的原因也很簡單,就是因為按照交換律,a^b與b^a的運算結果是一樣的,所以a^b^a等價於b^a^a,這個表示式中出現了a^a,a^a的值也為0,所以整個表示式的其實就相當於b^0,最終結果還是b。

希望大家能夠牢記以上這些結論,在後續的文章中,會講解如何用這些結論去解決實際問題。

四、按位取反運算子

按位取反運算子寫法是”~”,它的運算規則是:對每個二進位制位進行取反操作,所謂取反就是原來二進位制位上如果是0,那麼就變成1,反之,如果原來二進位制位上是1,那麼就變為0。取反運算子是一個單目運算子,所以只需要一個運算元就可以了。我們以數字5為例講解按位取反的運算過程:

首先把數字5轉換成補碼形式,之後把每個二進位制位上的數字進行取反,如果是0就變成1,如果1就變成0,經過取反後得到的二進位制串就是運算結果,這個運算結果被還原為十進位制數是-6。取反運算子的運算規則也非常容易理解,但是在這裡老師需要提醒各位讀者注意:如果是對變數進行取反操作,那麼經過操作之後,變數的值並不會發生變化!為方便小夥伴們理解,請看下圖:

從程式執行的結果可以看出:輸出a的值還是5,這說明變數a經過取反得到的那個-6並沒有被賦值到變數a中,通過這個例子可以證明,取反運算並沒有對變數重新賦值的功能,取反運算的結果只是臨時儲存在運算元棧中,變數本身的值不會因取反操作而發生改變。

下面再來講解一下與位移相關的運算子。所謂”位移”就是指在記憶體中對二進位制串進行移動的操作。只要是移動操作,就必然會涉及到以下幾個問題,怎樣表示移動方向?怎樣表示移動的位數?移動之後空出來的二進位制位用什麼來填充?移動之後跑到原來記憶體單元外面的那些數字怎麼處理?符號位要不要跟著一起移動?這些問題都是我們學習位移操作要弄清楚的細節。各位小夥伴可以帶著這些問題來學習位移相關的運算子。與位移相關的運算子有三個,分別是左移(<<)、帶符號右移(>>)、無符號右移(>>>)。

五、左移運算子

左移運算子的寫法是”<<“,看上去向兩個向左的箭頭,表示要把二進位制資料在記憶體空間中向左邊移動。使用左移運算子時,把想進行位移操作的運算元放最左面,之後寫上左移運算子,在左移運算子的右邊寫上移動的位數。例如:5<<2就表示對數字5進行左移2位的操作。下圖展示了進行左移操作之後,二進位制串在記憶體中是怎樣變化的:

可以看到這個二進位制串在記憶體中整體向左移動了兩位,那麼最左邊的兩位就跑到記憶體單元的外面去了,這兩位數字將會被捨棄,右邊空出的兩位用0補齊。

左移運算有乘以2的N次方的效果。一個數向左移動1位,就相當於乘以2的1次方,移動兩位就相當於乘以2的2次方,也就是乘以4。位移操作在實際運算時遠遠快於乘法操作,所以在某些對運算速度要求非常高的場合,可以考慮用左移代替乘以2的N次方的乘法操作。但是需要提醒大家注意三個細節:

首先:位移操作同取反操作一樣,並不能改變變數本身的值,所能改變的僅是儲存在運算元棧中那個資料的值,不理解這句話意思的小夥伴看下圖:

其次:當位移的位數很多時,導致最左邊的符號位發生變化,就不再具有乘以2的N次方的效果了。比如十進位制的5轉換為補碼形式是:前面29個0最後3位是101,如果移動29位,那麼最前面的符號位就變成了1,此時運算的結果就成為了一個負數,不再是5乘以2的29次方的乘法結果。

最後:對於byte/short/int三種類型的資料,Java語言最多支援31位的位移運算。如果位移數超過31,則虛擬機器會對位移數按連續減去32,直到得到一個小於32並且大於等於0的數,然後以這個數作為最終的位移數。例如對int型變數進行位移97位的操作,虛擬機器會首先對97連續減去3個32,最終得到數字1,實際進行位移運算時只對變數位移1位。而對於long型別的資料而言,最多支援63位的位移運算,如果位移數超過63,則連續減去64,以最終得到的小於64並且大於等於0的數作為位移數。小夥伴們可以試一下數字5左移32位是什麼結果。

六、帶符號右移運算子

右移運算分為兩種,分別是帶符號右移和無符號右移。首先我們來說說帶符號右移運算子。帶符號右移運算子的寫法是”>>“,與左移運算子的方向恰好相反。所謂帶符號右移就是指當二進位制串向右邊移動以後,左邊空出的位用”符號位上的數字”填充,說的更直白一點,如果是正數,二進位制串右移的時候用0來填充左邊的空位,而對於負數而言,右移的時候用1來填充左邊的空位,如下圖:

從圖上可以清楚的看到帶符號右移操作在二進位制串移動之後左邊空位是怎樣被填充的。之前強調過,左移N位的操作具有乘以2的N次方的效果,其實帶符號右移也具有”類似”除以2的N次方的效果。請注意,這裡說的是“類似”除以2的N次方的效果,為什麼要加上“類似”兩個字呢?就是因為對於正數而言,帶符號右移之後產生的數字確實等於除以2的N次方,比如說我們把N的值設為3,對於正15,帶符號右移3位的結果是1,這個結果與“15除以2的3次方”的結果是相同的。

但是對於負數而言,帶符號右移的效果分為兩種情況,我們分別來討論。

如果這個負數是“2的N次方”的整數倍,那麼帶符號右移N位的效果也等於除以2的N次方。舉個例子:我們還是把N的值設為3,如果對於“-16”來說,它是“2的3次方”的整數倍,那麼帶符號右移3位的結果是-2,這個結果相當於“-16除以2的3次方”。

而如果這個負數不是“2的N次方”的整數倍,那麼右移N位之後,是在除以2的N次方的結果之上還要減去1。比如,對於-15來說,它不是“2的3次方”的整數倍,那麼帶符號右移3位的結果是-2,這個運算結果其實就是“-15被2的3次方整除再減去1”。小夥伴們也可以用其他負整數來驗證一下這個結論。因為並非每個負整數帶符號右移的結果都等於除以“2的N次方”,所以我們才在文中添加了“類似”這兩個字。

帶符號右移的操作可以保證移動之前和移動之後數字的正負屬性不變,原來是正數,不管移動多少位,移動之後還是正數,原來是負數,移動之後還是負數。另外,我們還可以繼續深挖一下這個特性,從而得到一個結論:對於任何一個byte、short或者int型別的資料而言,帶符號右移31位之後,得到的必然是0或者是-1。對於long型別的資料而言,帶符號右移63位之後,得到的也必然是0或者是-1。能夠得出這個結論的依據也很簡單,就是因為對於byte、short和int型別的變數而言,如果是正數,帶符號右移31位之後產生的二進位制串必然全部是0,轉換成對應的十進位制數就是0;而對於負數而言,帶符號右移31位之後產生的二進位制串必然全部是1,轉換成十進位制數就是-1。對於long型別的資料,帶符號右移63位也具有相同效果。

七、無符號右移運算子

前文已說過:右移運算分為兩種,分別是帶符號右移和無符號右移。現在再來講解無符號右移。無符號右移運算子的寫法是”>>>”,比帶符號右移多了一個”>”。帶符號右移的運算規則與無符號右移的運算規則差別就在於:無符號右移在二進位制串移動之後,空位由0來補充,與符號位是0還是1毫無關係,如下圖:

以上圖片展示了無符號右移的運算規則。對於正數而言,無符號右移和帶符號右移沒有什麼區別,而對於負數而言,經過無符號右移會產生一個正數,因為最左邊的符號位被0填充了。

 

原文連線:https://wjrsbu.smartapps.cn/zhihu/article?id=106893096&isShared=1&_swebfr=1&_swebFromHost=baiduboxapp