一般來說位運算符只能操作整數類型的 變量或者值。
Java支持的位運算符有以下7個。
● &:按位與。當兩位同時 爲1時才返回1。
● | :按位或。只要有一位爲1即可返回1。
● ~ :按位非。單目運算符,將操作數的每個位(包括符號位)全部取反。
● ^ :按位異或。當兩位相同時返回0,不同時返回1。
● << :左移運算符。
● >> :右移運算符。
● >>> : 無符號右移運算符。
1. 位運算符的運算規則如下表所示:
第一個數 | 第二個數 | 按位與 | 按位或 | 按位異或 |
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 0 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
注意: 按位非只需要一個操作數,該運算符把操作數的二進制碼按位(包括符號位)取反。
下面我們來分享一下按位與和按位或的運算原理。首先看兩句代碼:
System.out.println("按位與:" + (5 & 9));
System.out.println("按位或:" + (5 | 9));
這兩句代碼的運行結果是“按位與:”+1;“按位或:”+13
這裏要分清楚&&和&以及| 和||的區別。&&和||是邏輯運算符,而&和|是位運算符!!!位運算符!!!位運算符!!!(重要的事情說三遍)。所以毋庸置疑對於這兩個運算符我們需要將操作數切換成二進碼制然後再進行運算。
現在我們來分析一下上面兩句代碼的運算過程:
首先:5&9:
5的二進制碼是:00000101(前面還有24個0)
9的二進制碼是:00001001(同樣前面有24個0)
所以:5&9和5 | 9 運算過程如圖
所以將所得結果換算成十進制就分別變成了1和13了。
2. 按位非和按位異或
上面講了按位與和按位或的運算原理,現在們來看一下按位非和按位異或的運算原理。
同樣先看兩句代碼:
System.out.println("按位非:" + (~-5));
System.out.println("按位異或:" + (5 ^ 9));
其運行結果是:按位非:4;按位異或:12。
這裏先普及一下二進制碼有原碼、反碼和補碼之說。
(1)二進制的最高位(最左邊的位)是符號位:0表示正數,1表示負數
(2)正數的原碼、反碼和補碼都是一樣的
(3)負數的原碼爲該數對應的無符號數的二進制將符號位置爲1
(4)負數的反碼爲該數原碼的符號位不變,其他位取反(1變成0;0變成1)
(5) 負數的補碼爲該數的反碼加1
(6)0的反碼補碼都是0
(7)計算機中,都是以補碼的形式來運算的
a)瞭解了負數三碼(原碼、反碼和補碼的簡稱)的由來,現在我們再來分析(~-5)的運算過程。
-5的原碼:
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
-5的反碼(原碼符號位不變,其他位取反):
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |
-5的補碼(反碼加1):
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
所以(~-5)的二進制爲(非就是將所有爲取反)
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
將上面的二進制碼換算成十進制就是4。
負數由反碼得到補碼的過程(加1)我沒有詳細給出,二進制的加法比較簡單,也就類似於十進制的加法,不過是逢二進一。
所以這裏不再囉嗦,想必大家都會,如有不明白的可以參考一下這篇文章。本文所寫的普及知識那幾條也參考了這篇文章。
寫的不錯,我先感謝一下作者。
b)現在我們來分析一下按位異或(5^9)的運算過程
再提一嘴按位異或的運算規則是:兩位相同時返回0,不同時返回1。
下面分析過程:
5的二進制碼(三碼合一):
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
9的二進制碼:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
現在我們按照異或的規則進行計算
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 |
將運算結果換算成十進制就是12。
3. 移位運算符(>>(右移)、<<(左移)和>>>(無符號右移))
上邊分析的那些是比較簡單的也是大家都比較熟悉的。 接下來要講解的內容將是本文的重點,仔細分析一下並不算難,但是我確確實實是剛弄明白沒多久呢。之前沒有認真分析過總是道聽途說可以認爲成<< (左移)就是乘以2的多少多少次方,>>(右移)就是取整2的多少多少次方。至於>>>(無符號右移)那是更加的不知道。然而我並沒有想過負數進行移位運算該怎麼辦。我發現我作爲一個寫代碼的真的挺不合格的,之前都是一味的功能羅列、堆疊,這裏抄點那裏抄點的。 以至於到現在才弄明白移位運算符的運算原理果真無比汗顏。。。
廢話不多說了現在我就把我所理解的移位運算寫一寫,至少能幫助自己在以後溫習的時候方便一些。當然如果能幫到大家那我將非常欣慰,所以熱烈歡迎大家能留言指出我的不足。
a)現在看一下左移運算的運算過程,照樣先看兩句代碼:
System.out.println("左移運算:" + (5 << 2));
System.out.println("左移運算:" + (-5 << 2));
這兩句代碼的運行結果是:20、-20。
得知結果之後心裏先默唸兩遍左移運算的規則,將操作數的二進制碼整體左移指定位數,左移後右邊空出來的位以0補充
過程分析:(以-5爲例)
-5的補碼:
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
下面進行左移操作:
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 0 |
其中綠色字體的(前兩位)是補碼經過左移操作後被移出去的兩位;紅色字體的是原來補碼中有的,藍色字體(最後兩位)的是左移之後缺少的兩位由0填補上的。
由上可知,該二進制碼最高位(紅色字體最左邊一位)是1,所以負數左移運算之後仍是負數。
注意:負數左移之後得到的二進制碼仍舊是補碼,換算成十進制時仍需要先將補碼換成原碼。
上面我們介紹了負數二進制補碼的由來,這樣我們倒着推就可以由補碼得到原碼,也就是補碼減1得到的二進制碼再取反(反碼的首位爲1不變)。不過我自己在分析的時候,負數由補碼換算原碼我不是這樣推算的,所以在這兒我也不按照這個倒推的方式來分析,現在看另一種方法。
負數二進制求十進制可以先將補碼取反然後再加1,然後再轉成十進制所得的數字就是該負數的絕對值。
兩種方式應該是比較相似的,並且也比較簡單,所以這裏不再囉嗦。大家可自行推算。
b)分析完左移我們再來看一下右移運算的過程
二話不說再接着上代碼
System.out.println("右移運算:" + (9 >> 3));
System.out.println("無符號右移運算:" + (9 >>> 3));
System.out.println("右移運算:" + (-9 >> 2));
System.out.println("無符號右移運算:" + (-9 >>> 2));
這次我們換個操作數不用5和-5了。
這幾句代碼的運行結果是1、1、-3和1073741821。還是一樣的在分析運算原理之前先介紹一下右移運算符和無符號右移運算符的規則。
右移運算符(>>)運算規則:把第一個操作數(這裏就是9和-9)的二進制碼右移指定位數(分別是3位和兩位)後,左邊空出來的位以原來的符號位填充,即如果第一個操作數是正數,則左邊補0;如果第一個操作數是負數,則左邊補1。
無符號右移運算符(>>>)運算規則:把第一個操作數的二進制碼右移指定位數後,無論是正數還是負數左邊空出來的位總是以0補充。
下面針對上面的幾句代碼我們類分別分析其運算過程。
1)9>>3:
9的二進制碼(三碼合一):
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
向右移三位後的結果:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | 1 |
其中藍色字體(前三位)是右移之後彌補的左邊的空缺,紅色字體是原來9的二進制碼中含有的,綠色字體(最低三位)是右移移除的三位。所以9>>3運算其結果的二進制碼就是藍色和紅色字體組成的二進制碼。我覺得只看這個二進制碼不用分析我們也能知道結果是什麼了吧。
2)9>>>3:
根據右移運算符和無符號右移運算符的運算規則我們可以知道,(>>)和(>>>) 的區別就在於右移之後左邊的空缺位補充的是什麼。對於正數而言無論這兩種運算符中的哪一種其運算結果和過程都是一致的。
3)-9>>2:
-9的二進制補碼:
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
向右移兩位的結果是:
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
其中藍色字體(前兩位)是右移之後彌補的左邊的空位,紅色字體是原來-9的二進制補碼中含有的,綠色字體(最右邊兩位)是右移移除的兩位。所以-9>>2運算其結果的二進制碼就是藍色和紅色字體組成的二進制補碼。再根據上邊介紹的負數由補碼計算原碼的方法來計算出原碼。
補碼取反:
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 |
反碼加1得原碼:
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
得到原碼之後轉換成十進制就是3,注意首位是1,所以這個數得是個負數,上面也提到了按這個方法得到的十進制是原來負數換算成十進制的絕對值,所以-9>>2(右移兩位)的運算結果是-3。
4)-9>>>2:
-9的二進制碼上邊給出了,根據無符號右移運算的規則可知,-9>>>2運算結果的二進制碼是:
0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 1 |
同樣最後兩位綠色字體的是右移之後右邊移除的兩位。跟-9>>2的不同就在於,右移兩位之後左邊空餘的兩位補位補誰的問題。無符號運算法則我們已然知道,-9是負數所以這裏需要補兩個0。看到這個二進制碼,首先我們不管什麼運算規則和運算過程,至少我們能知道兩點:
第一:結果一定是一個正數
第二:結果值一定不小。因爲這個二進制碼高兩位是0,其他位中只有一個0其他都是1。
這個二進制碼換算成十進制就是1+2²+2³+...+2的29次方,所得的結果就是一個比較大的數字1073741821。
通過上面的分析,我還是驗證了我的那個道聽途說<<(左移運算符),確實是相當於用操作數乘以2的所左移位數次方,無論操作數是正數還是負數都成立;對於>>(右移運算符)相當於用操作數取整2的所右移位數次方,在這裏正數和負數就有區別了。正數做右移操作,不論該操作數取整2的右移位數次方有餘數與否都只取取整的結果不與餘數做任何操作;負數做右移操作,如果該操作數取整2的右移位數次方有餘數則需要將商再與餘數做相加的操作。
至於無符號右移運算符(>>>),正數做該操作與正數做右移運算操作所得結果是一致的,這一點如果不明白那再去仔細看一看右移運算符和無符號右移運算符的運算規則就行了。負數做該操作首先一定會是一個很大的正數,這個大家去舉個例子分析一下估計也就能明白了。如果大家覺得我所總結的這些結論有偶然性那大家可以再自行舉例驗證,我就不再舉例驗證了。
本文到這裏也就該結束了,篇幅不算長但用的時間不短了。歡迎大家評論留言。
拜拜6.。。。