位運算,不管是計算機底層處理編碼的時候,仍是咱們看源碼的時候都必定有機率可以看見它。某種程度來講也算是比較熟悉了吧。 我的認爲,位運算某種程度上是契合了數字電路之中的邏輯運算,經過必定的邏輯關係將二進制的數據進行快速處理。 雖然本質來講,位運算就是直接操做內存中的整數進行邏輯運算。這個從代碼到硬件的過程大概從代碼到內存到不一樣的芯片之間,不一樣的邏輯門之間進行運算從而獲得結果,再反饋給計算機自己,不過這些東西並非重點,點到即止就是。算法
位運算之中的運算步驟都須要將數字轉換爲二進制以後才進行操做,畢竟是針對計算機內部自己的一個運算,使用二進制來進行處理也情有可原。因此說這跟邏輯電路也有那麼些聯繫。既然是要轉換爲二進制來計算,那麼確定有兩個數字轉換爲二進制以後位數不相同的狀況(指人爲進行計算),在這裏,一般的解決方式是在位數少的那個數前面補零。bash
要使用位運算以前,確定是要了解下面的這些運算符號以及做用的。編碼
與運算(and) &
表示,當兩個相同位對應的數都是1的時候,該位得到的結果纔是1,不然爲0 例如說6 & 11
,轉換爲二進制就是0110 & 1011
,結果爲0010
spa
或運算(or) |
表示,當兩個相同位對應的數只要有一個數1的時候,該位得到的結果爲1,不然爲0 仍是用6和11這兩個數作例子,就是0110 | 1011 = 1111
設計
非運算(not) ~
表示,這個運算只針對一個數字,將數字所有取反(原來是0結果就是1,原來是1結果就是0) 例如說:~110 = 001
code
異或運算(xor) ^
表示,當兩個相同位對應的數字不一樣的時候爲1,不然爲0 例如說:0110 ^ 1011 = 1101
cdn
左移(shl) <<
表示,a << b
表示a左移b位,因爲移位在末位多出來的未知數字補零。 在這裏面能夠等價爲a * 2^b
這個運算(針對十進制)。blog
右移(shr) >>
表示,a >> b
表示將a右移b位,本來的末位進行右移後會被捨棄,如有需求會在高位進行補零。 一樣的,右移在十進制裏面也能夠近似爲a / (2^b)
的形式,不過要對結果取整,也不必定準確,只可以說意思大概如此。內存
針對位運算的左移右移,民間一直有一種說法,就是若對數字作對2以及二的倍數的乘法或者除法,使用位運算會比直接使用乘號或者除號的處理速度來的快。對此他們的解釋一直都是以位運算是直接對內存進行操做致使運算效率來解釋的,說乘號或者除號都是對位運算的一種包裹,固然我一直也是這麼認爲的,不過總以爲這種東西很微妙。未必這真的在必定程度上影響整個程序的運行效率?開發
仍是廢話很少說,直接代碼驗證。(這裏指Java)
想法其實很簡單粗暴,也就不貼代碼上來了,思路就是對相同的一個數字進行相同次數(這個數足夠大)的右移或者左移,記錄時間差值,另一邊就是作等價的乘除法運算,記錄時間差值並比較。
結果以下:(運算次數的話是999999999)
a/2,8281
a>>1,682
a*2,354
a<<1,345
複製代碼
就結果來講,針對乘法和左移,二者的效率相近(運行速度差別不大) 可是就針對除法和右移來講,顯然右移效率遠高於除法,某種程度來講是至關微妙了。 不過並無去了解產生這種差別的根本緣由。順便一提,假如說這個次數不大的話總體的移位和乘除法的效率是基本類似的。因此說,非必要狀況仍是別用位運算了,影響代碼閱讀體驗。
在某些工做情境下,位運算是妙用,有些狀況下就是影響閱讀了。因此使用它的時候也要考慮情境(若是是效率高於一切的選手這句話當我沒說…)
寫這篇博客的初衷天然不是來探究左右移的效率的,更想談談的是這個東西在Android裏面的運用。在看書的時候發現不少部分的源碼都有位運算的成分在裏面,假如說只知其一;不知其二的話觀看體驗極差就是了,因此說寫一寫具體應用。
在Android源碼裏面對於它的應用仍是算是較多的,通常是用於存儲多種變量,或者說是flag的存儲和判斷,在這裏也就舉幾個例子。
MeasureSpec
興許最先在Android源碼裏面接觸位運算的話就是在自定義View部分的時候,當書裏面說起MeasureSpec
這個變量的時候採用以下描述:
MeasureSpec
表明一個32位int值,高2位表明SpecMode
,低30位表明SpecSize
,SpecMode
是指測量模式,而SpecSize
是指在某種測量模式下的規格大小。(引用自《Android開發藝術探索》)
這裏面就是典型的位運算的運用,不管是這個變量須要分別拆分得到SpecMode
和SpecSize
,仍是其餘的一些相關的操做,都須要位運算。
首先是SpecMode
,在描述之中是高2位的部分,那麼在處理之中必定會運用到左移或者右移來完成需求。
Talk is cheap, show me the code. :)
在這個類裏面,一開始就聲明瞭兩個基本變量以及不一樣測量模式的值,已經用到了位運算中的左移
private static final int MODE_SHIFT = 30;
// 聲明位移量
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// 後期截取SpecMode或SpecSize時使用的變量
// 3對應的二進制是11,左移30位後,int值的前2位就都是1,後30位爲0
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
// 三種測量模式對應的值
複製代碼
先看看是如何經過位運算來獲取SpecMode
和SpecSize
:
@MeasureSpecMode
// 等價於@IntDef(value={...})。一種枚舉類註釋
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
// 讓低30位的值變爲0,只保留高2位的值
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
// 非運算直接讓MASK值變成int值高2位爲0,低30位爲1
// 進行與運算,直接將高2位的值變爲0
}
複製代碼
這裏就是典型的經過位運算來截取對應值,利用的是x & 1 = x, x & 0 = 0
,其中x表明0與1兩種值。 這種方式讓一個變量可以存儲多個內容方式實現,甚至也可使用這樣的方式將合成的值做爲特定的key來作匹配或者類似需求。
接下來是如何得到MeasureSpec
值:
public static int makeMeasureSpec( @IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size, // 要求傳入的size值在指定範圍內 @MeasureSpecMode int mode) {
if (sUseBrokenMakeMeasureSpec) {// 是否用原來的方法對MeasureSpec進行構建
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
複製代碼
在API17及其如下的時候,是按照size + mode
的方式進行構建,一個是隻有int前2位有值,一個是隻有int後30位有值,這麼思考處理也情有可原。但倘若兩個值有溢出狀況就會嚴重影響MeasureSpec
的結果。故Google官方在API17以後就對該方法進行了修正,也是採用的位運算的形式: (size & ~MODE_MASK) | (mode & MODE_MASK)
至關於就是分別得到了SpecSize
和SpecMode
後經過或運算得到結果。
上文提到過能夠經過一些操做來實現一個變量存儲多個內容,而在Android源碼之中在不少也確實作到了,下面就簡單舉個例子。
由於源碼中涉及這種運算的太多了,就不具體拿源碼中的某個內容舉例子了,想深究的能夠去看看源碼…大概Flag關鍵字就能找到挺多相關的內容。
使用的時候須要注意,對應的標誌位須要設計好,假如說有內容交叉的話就會很是影響結果。Google官方工程師爲了保證這一點也是費盡心思
在這以前,得先知道位運算的運算優先級:
~
><</>>
>&
>^
>|
>&=/^=/|=
A | B
添加標誌B,能夠添加多個(只要不衝突) 例如:mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED | FOCUSABLE_AUTO;
A & B
判斷A中是否有標誌B 原理就是與運算之中的1 & 1 = 1, 0 & 1 = 0
,以此來判斷flag對應位是否存在該flag 以(mViewFlags & FOCUSABLE_AUTO) != 0
這個判斷語句爲例: 若爲真,則與的結果不等於0,表示flag之中有該標誌
A & ~B
去除標誌B 上者的逆運算 例如:mViewFlags = (mViewFlags & ~FOCUSABLE) | newFocus
用於去除原有的標誌位並附上新的標誌位(至關於更新)
A ^ B
取出A與B不一樣的部分,通常用於判斷A是否發生改變
int changed = mViewFlags ^ old;
if (changed == 0) {
return;
}
複製代碼
(mViewFlags & ENABLED_MASK) == ENABLED
相似於這種類型的原理就等同於在MeasureSpec
中得到SpecMode
和SpecSize
,採起對應位直接截斷的方式拿到對應值,而後跟指定flag進行比較。
雖說位運算經常使用於算法裏面,不過在開發過程之中的某些需求之下仍是能夠巧用位運算獲得高效率。可是不要濫用,畢竟影響觀感。
由於本人算接觸Android方面以及相關內容不久,有些言論可能會有錯誤或誤差。如有疑問或者是有地方有誤須要指正,歡迎下方留言討論