Java位運(yùn)算知識點(diǎn)詳解
在日常的Java開發(fā)中,位運(yùn)算使用的不多,使用的更多的是算數(shù)運(yùn)算(+、-、*、/、%)、關(guān)系運(yùn)算(<、>、<=、>=、==、!=)和邏輯運(yùn)算(&&、||、!),所以相對來說對位運(yùn)算不是那么熟悉,本文將以Java的位運(yùn)算來詳細(xì)介紹下位運(yùn)算及其應(yīng)用。
1、 位運(yùn)算起源
位運(yùn)算起源于C語言的低級操作,Java的設(shè)計(jì)初衷是嵌入到電視機(jī)頂盒內(nèi),所以這種低級操作方式被保留下來。所謂的低級操作,是因?yàn)槲贿\(yùn)算的操作對象是二進(jìn)制位,但是這種低級操作對計(jì)算機(jī)而言是非常簡單直接,友好高效的。在簡單的低成本處理器上,通常位運(yùn)算比除法快得多,比乘法快幾倍,有時(shí)比加法快得多。雖然由于較長的指令流水線和其他架構(gòu)設(shè)計(jì)選擇,現(xiàn)代處理器通常執(zhí)行加法和乘法的速度與位運(yùn)算一樣快,但由于資源使用減少,位運(yùn)算通常會(huì)使用較少的功率,所以在一些Java底層算法中,巧妙的使用位運(yùn)算可以大量減少運(yùn)行開銷。
2、 位運(yùn)算詳解
Java位運(yùn)算細(xì)化劃分可以分為按位運(yùn)算和移位運(yùn)算,見下表。
細(xì)化 |
符號 |
描述 |
運(yùn)算規(guī)則 |
按位運(yùn)算 |
& |
與 |
兩位都為1,那么結(jié)果為1 |
| |
或 |
有一位為1,那么結(jié)果為1 |
|
~ |
非 |
~0 = 1,~1 = 0 |
|
^ |
異或 |
兩位不相同,結(jié)果為1 |
|
移位運(yùn)算 |
<< |
左移 |
各二進(jìn)制位全部左移N位,高位丟棄,低位補(bǔ)0 |
>> |
右移 |
各二進(jìn)制位全部右移N位,若值為正,則在高位插入 0,若值為負(fù),則在高位插入 1 |
|
>>> |
無符號右移 |
各二進(jìn)制位全部右移N位,無論正負(fù),都在高位插入0 |
在進(jìn)行位運(yùn)算詳解之前,先來普及下計(jì)算機(jī)中數(shù)字的表示方法。對于計(jì)算機(jī)而言,萬物皆0、1,所有的數(shù)字最終都會(huì)轉(zhuǎn)換成0、1的表示,有3種體現(xiàn)形式,分別是:原碼、反碼和補(bǔ)碼。
原碼:原碼表示法在數(shù)字前面增加了一位符號位,即最高位為符號位,正數(shù)位該位為0,負(fù)數(shù)位該位為1.比如十進(jìn)制的5如果用8個(gè)二進(jìn)制位來表示就是00000101,-5就是10000101。
反碼:正數(shù)的反碼是其本身,負(fù)數(shù)的反碼在其原碼的基礎(chǔ)上,符號位不變,其余各個(gè)位取反。5的反碼就是00000101,而-5的則為11111010。
補(bǔ)碼:正數(shù)的補(bǔ)碼是其本身,負(fù)數(shù)的補(bǔ)碼在其原碼的基礎(chǔ)上,符號位不變,其余各位取反,最后+1。即在反碼的基礎(chǔ)上+1。5的反碼就是00000101,而-5的則為11111011。
了解了這幾個(gè)概念后,我們現(xiàn)在先記住一個(gè)結(jié)論,那就是在計(jì)算機(jī)系統(tǒng)中,數(shù)字一律用補(bǔ)碼來表示、運(yùn)算和存儲,具體的原因可以看這篇文章的討論,這里不做更多討論,因?yàn)椴皇潜疚牡闹攸c(diǎn)。
2.1 與運(yùn)算(&)
規(guī)則:轉(zhuǎn)為二進(jìn)制后,兩位為1,則結(jié)果為1,否則結(jié)果為0。
舉例:
十進(jìn)制 |
二進(jìn)制(正數(shù)原碼、反碼、補(bǔ)碼一致) |
10 |
00000000000000000000000000001010 |
&12 |
&00000000000000000000000000001100 |
= |
= |
8 |
00000000000000000000000000001000 |
十進(jìn)制 |
二進(jìn)制(原碼) |
-6 |
10000000000000000000000000000110 |
&-2 |
&10000000000000000000000000000010 |
十進(jìn)制 |
二進(jìn)制(反碼) |
-6 |
11111111111111111111111111111001 |
&-2 |
&11111111111111111111111111111101 |
十進(jìn)制 |
二進(jìn)制(補(bǔ)碼) |
-6 |
11111111111111111111111111111010 |
&-2 |
&11111111111111111111111111111110 |
= |
= |
-6 |
11111111111111111111111111111010 |
最后的計(jì)算結(jié)果11111111111111111111111111111010還是補(bǔ)碼的形式,要看其十進(jìn)制,還需要先轉(zhuǎn)成二進(jìn)制原碼。
先轉(zhuǎn)反碼:11111111111111111111111111111010-1=11111111111111111111111111111001,得反碼11111111111111111111111111111001。
再轉(zhuǎn)原碼:在反碼的基礎(chǔ)上轉(zhuǎn)原碼,符號位不變,其他各位取反,得10000000000000000000000000000110。第一位1代表負(fù)數(shù),后面0110轉(zhuǎn)成十進(jìn)制是6,得-6。
2.2 或運(yùn)算(|)
規(guī)則:轉(zhuǎn)為二進(jìn)制后,有一位為1,則結(jié)果為1,否則結(jié)果為0。
舉例:
十進(jìn)制 |
二進(jìn)制(正數(shù)原碼、反碼、補(bǔ)碼一致) |
10 |
00000000000000000000000000001010 |
|12 |
|00000000000000000000000000001100 |
= |
= |
14 |
00000000000000000000000000001110 |
十進(jìn)制 |
二進(jìn)制(原碼) |
-6 |
10000000000000000000000000000110 |
|-2 |
|10000000000000000000000000000010 |
十進(jìn)制 |
二進(jìn)制(反碼) |
-6 |
11111111111111111111111111111001 |
|-2 |
|11111111111111111111111111111101 |
十進(jìn)制 |
二進(jìn)制(補(bǔ)碼) |
-6 |
11111111111111111111111111111010 |
|-2 |
|11111111111111111111111111111110 |
= |
= |
-2 |
11111111111111111111111111111110 |
2.3 非運(yùn)算(~)
規(guī)則:轉(zhuǎn)為二進(jìn)制后,~0 = 1,~1 = 0。
舉例:
十進(jìn)制 |
二進(jìn)制(正數(shù)原碼、反碼、補(bǔ)碼一致) |
~7 |
~00000000000000000000000000000111 |
= |
= |
-8 |
11111111111111111111111111111000(補(bǔ)碼需轉(zhuǎn)換為原碼) |
11111111111111111111111111111000-1得反碼,可以把1000看成是0112,得反碼
11111111111111111111111111110111。根據(jù)反碼得原碼10000000000000000000000000001000。
十進(jìn)制 |
二進(jìn)制(原碼) |
~(-6) |
~10000000000000000000000000000110 |
十進(jìn)制 |
二進(jìn)制(反碼) |
~(-6) |
~11111111111111111111111111111001 |
十進(jìn)制 |
二進(jìn)制(補(bǔ)碼) |
~(-6) |
~11111111111111111111111111111010 |
= |
= |
5 |
00000000000000000000000000000101(正數(shù)原碼、反碼、補(bǔ)碼一致) |
2.4 異或運(yùn)算(^)
規(guī)則:轉(zhuǎn)為二進(jìn)制后,兩位不相同,結(jié)果為1,否則為0。
舉例:
十進(jìn)制 |
二進(jìn)制(正數(shù)原碼、反碼、補(bǔ)碼一致) |
15^2 |
00000000000000000000000000001111 ^00000000000000000000000000000010 |
= |
= |
13 |
00000000000000000000000000001101 |
2.5 左移運(yùn)算(<<)
規(guī)則:轉(zhuǎn)為二進(jìn)制后,各二進(jìn)制位全部左移N位,高位丟棄,低位補(bǔ)0。
舉例:
十進(jìn)制 |
二進(jìn)制(正數(shù)原碼、反碼、補(bǔ)碼一致) |
2<<2 |
00000000000000000000000000000010 |
= |
0000000000000000000000000000001000 |
8 |
00000000000000000000000000001000 |
十進(jìn)制 |
二進(jìn)制(先取補(bǔ)碼 再對補(bǔ)碼操作位移) |
-2<<2 |
10000000000000000000000000000010(原碼) |
|
11111111111111111111111111111101(反碼) |
|
11111111111111111111111111111110(補(bǔ)碼) |
|
1111111111111111111111111111111000 |
|
11111111111111111111111111111000(補(bǔ)碼) |
|
11111111111111111111111111110111(反碼) |
-8 |
10000000000000000000000000001000(原碼) |
2.6 右移運(yùn)算(>>)
規(guī)則:轉(zhuǎn)為二進(jìn)制后,各二進(jìn)制位全部右移N位,若值為正,則在高位插入 0,若值為負(fù),則在高位插入 1。
舉例:
十進(jìn)制 |
二進(jìn)制(正數(shù)原碼、反碼、補(bǔ)碼一致) |
2>>2 |
00000000000000000000000000000010 |
= |
0000000000000000000000000000000010 |
0 |
00000000000000000000000000000000 |
十進(jìn)制 |
二進(jìn)制(先取補(bǔ)碼 再對補(bǔ)碼操作位移) |
-6>>2 |
10000000000000000000000000000110(原碼) |
|
11111111111111111111111111111001(反碼) |
|
11111111111111111111111111111010(補(bǔ)碼) |
|
1111111111111111111111111111111010 |
|
11111111111111111111111111111110(補(bǔ)碼) |
|
11111111111111111111111111111101(反碼) |
-2 |
10000000000000000000000000000010(原碼) |
2.7 無符號右移運(yùn)算(>>>)
規(guī)則:轉(zhuǎn)為二進(jìn)制后,各二進(jìn)制位全部右移N位,無論正負(fù),都在高位插入0。
舉例:
十進(jìn)制 |
二進(jìn)制(先取補(bǔ)碼 再對補(bǔ)碼操作位移) |
-1>>>1 |
10000000000000000000000000000001(原碼) |
|
11111111111111111111111111111110(反碼) |
|
11111111111111111111111111111111(補(bǔ)碼) |
|
011111111111111111111111111111111 |
|
01111111111111111111111111111111(補(bǔ)碼) |
|
01111111111111111111111111111110(反碼) |
溢出,只能表示到int的最大值2147483647 |
10000000000000000000000000000001(原碼) |
3、 應(yīng)用
3.1 不用額外的變量實(shí)現(xiàn)兩個(gè)數(shù)字互換
見參考資料中的BitOperationTest,方法reverse通過三次異或操作完成了兩個(gè)變量值的替換。
證明很簡單,我們只需要明白異或運(yùn)算滿足下面規(guī)律(實(shí)際不止如下規(guī)律):
0^a = a,a^a = 0;
a ^ b = b ^ a;
a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;
a ^ b ^ a = b;
假設(shè)a,b兩個(gè)變量,經(jīng)過如下步驟完成值交換:a=a^b,b=b^a,a=a^b。
證明如下:
因?yàn)閍 ^ b = b ^ a,又a=a^b,b=b^a。故b=b^a= b^ (a^b)=a。
繼續(xù)a=a^b,a=(a^b) ^ b^ (a^b),故a=b。完成值交換。
3.2 不用判斷語句實(shí)現(xiàn)求絕對值
公式如下:(a^(a>>31))-(a>>31)
先整理一下使用位運(yùn)算取絕對值的思路:若a為正數(shù),則不變,需要用異或0保持的特點(diǎn);若a為負(fù)數(shù),則其補(bǔ)碼為原碼翻轉(zhuǎn)每一位后+1,先求其原碼,補(bǔ)碼-1后再翻轉(zhuǎn)每一位,此時(shí)需要使用異或1具有翻轉(zhuǎn)的特點(diǎn)。
任何正數(shù)右移31后只剩符號位0,最終結(jié)果為0,任何負(fù)數(shù)右移31后也只剩符號位1,溢出的31位截?cái)?,空出?1位補(bǔ)符號位1,最終結(jié)果為-1.右移31操作可以取得任何整數(shù)的符號位。
那么綜合上面的步驟,可得到公式。a>>31取得a的符號,若a為正數(shù),a>>31等于0,a^0=a,不變;若a為負(fù)數(shù),a>>31等于-1 ,a^-1翻轉(zhuǎn)每一位。
3.3 判斷一個(gè)數(shù)的奇偶性
通過與運(yùn)算判斷奇偶數(shù),偽代碼如下:
n&1 == 1?”奇數(shù)”:”偶數(shù)”
奇數(shù)最低位肯定是1,而1的二進(jìn)制最低位也是1,其他位都是0,所以所有奇數(shù)和1與運(yùn)算結(jié)果肯定是1。
相關(guān)文章
IDEA創(chuàng)建Java項(xiàng)目文件并運(yùn)行教程解析
這篇文章主要介紹了IDEA創(chuàng)建Java項(xiàng)目文件并運(yùn)行教程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11使用FeignClient調(diào)用POST表單Body內(nèi)沒有參數(shù)問題
這篇文章主要介紹了使用FeignClient調(diào)用POST表單Body內(nèi)沒有參數(shù)問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03Java報(bào)錯(cuò):java.util.concurrent.ExecutionException的解決辦法
在Java并發(fā)編程中,我們經(jīng)常使用java.util.concurrent包提供的工具來管理和協(xié)調(diào)多個(gè)線程的執(zhí)行,va并發(fā)編程中,然而,在使用這些工具時(shí),可能會(huì)遇到各種各樣的異常,其中之一就是java.util.concurrent.ExecutionException,本文將詳細(xì)分析這種異常的背景、可能的原因2024-09-09SpringBoot?實(shí)現(xiàn)全局異常處理的示例代碼
本文主要介紹了SpringBoot實(shí)現(xiàn)全局異常處理,全局異常處理器的使用可以顯著提高Spring Boot項(xiàng)目的代碼質(zhì)量和可維護(hù)性,減少冗余代碼,具有一定的參考價(jià)值,感興趣的可以了解一下2024-06-06Springboot hibernate envers使用過程詳解
這篇文章主要介紹了Springboot hibernate envers使用過程詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-06-06