[基礎鞏固]從Android 源碼跟蹤到的Java位運算的一些事兒

前言

在咱們Java程序員的平常開發中由於面向對象,其實關於位運算仍是接觸的比較少的,但其實看看有些框架的源碼,發現還有經過位運算實現的比較巧妙的設計,今天咱們就來稍微瞭解一下位運算。

基礎回顧

bit 和 byte

 1)bit指「位」,是數據傳輸速度的計量單位,常簡寫爲「b」;Byte指「字節」,是文件大小的計量單位,常簡寫爲「B」。java

  2)Byte和bit的換算關係是,1 Byte=8 bits。在電腦上,一個英文字母須要佔用1 Byte的硬盤空間,一個漢字則需佔用2 Byte。 以下圖: imageandroid

例如,在咱們java語言中,一個int 佔4 byte,也就是佔32bit,後面我會講到int在一些源碼裏面的妙用程序員

機器數

一個數在計算機中的二進制表示形式, 叫作這個數的機器數。機器數是帶符號的,在計算機用一個數的最高位存放符號, 正數爲0, 負數爲1.markdown

原碼,反碼,補碼
原碼

將一個數字轉換成二進制(機器數)就是這個數值框架

反碼

反碼的表示方法是:正數的反碼是其自己;負數的反碼是在其原碼的基礎上, 符號位不變,其他各個位取反。ide

補碼

補碼的表示方法是:正數的補碼就是其自己;負數的補碼是在其原碼的基礎上, 符號位不變, 其他各位取反, 最後+1。 (即在反碼的基礎上+1)函數

十進制原數 原碼 反碼 補碼
10 0000 1010 0000 1010 0000 1010
-10 1000 1010 1111 0101 1111 0110
5 0000 0101 0000 0101 0000 0101
-5 1000 0101 1111 1010 1111 1011
設計意義

簡化了計算機的設計,計算機只能進行加法運算,經過補碼的設計,使之能夠在這種設計下,進行減法運算。 好比 1-1 在計算機中執行的 其實是 1 +(-1) 即補碼運算,也就是說,全部計算都是使用該數的補碼,計算完成之後再換回源碼。後面基於負數的計算能夠詳細瞭解。測試

位運算符( &、|、^、~、>>、<<、>>>)

& 「與」

兩個數,從最低位到最高位,一一對應。若是某 bit 的兩個數值對應的值都是 1,則結果值相應的 bit 就是 1,不然爲 0.this

int x = 1; // 0000 0001 
int y = 2; // 0000 0010 
複製代碼

爲方便後續運算和對比,我後面的運算符均使用這兩個數加密

x&y = 0001 & 0010  = 0000 = 0
y&x = 0010 & 0001  = 0000 = 0
x&x = 0001 & 0001  = 0001 = 1
y&y = 0010 & 0010  = 0010 = 1
複製代碼
| 「或」

兩個數,從最低位到最高位,一一對應。若是某 bit 的兩個數值其中一個是 1,則結果值相應的 bit 就是 1,不然爲 0.

x|y = 0001 | 0010  = 0011 = 3
y|x = 0010 | 0001  = 0011 = 3
x|x = 0001 | 0001  = 0001 = 1
y|y = 0010 | 0010  = 0010 = 2
複製代碼
^ 「異或」

兩個操做數進行異或時,對於同一位上,若是數值相同則爲 0,數值不一樣則爲 1。

x^y = 0001 ^ 0010  = 0011 = 3
y^x = 0010 ^ 0001  = 0011 = 3
x^x = 0001 ^ 0001  = 0000 = 0
y^y = 0010 ^ 0010  = 0000 = 0 
複製代碼
~ 「取反」

對於這個數每一位 1變0、0變1

~x = ~0000 0001 =  1111 1111 ......(省略) 1111 1110 
複製代碼
>> 「右移運算符」

規則 a >> b 將數值 a 的二進制數值從 0 位算起到第 b - 1 位,總體向右方向移動 b 位,符號位不變,高位空出來的位補數值 0。

y>>1 = 0000...0010(源碼) >>1 = 0000...0010(補碼)>>1 = 0000...0001(運算後的補碼)=0000 ... 0001(源碼)= 1
-y>>1 = 1000 ... 0010(源碼) >>1 = 1111 ... 1110(補碼)>>1 = 1111 ...1111(運算後的補碼)= 1000...0001(源碼)= -1
//其實全部運算都經歷了源碼-補碼-計算-源碼的過程,下面就省略這個過程直接給結論
複製代碼
<< 「左移運算符」

規則 a << b 將數值 a 的二進制數值從 0 位算起到第 b - 1 位,總體向左方向移動 b 位,符號位不變,低位空出來的位補數值 0。

y<<1 =  0000 ...  0010<<1= 0000 ... 0100 = 4
-y<<1 =  1000 ...  0010<<1= 1000 ... 0100 = -4
複製代碼

公式總結:

  • a >> b = a / ( 2 ^ b )
  • a << b = a * (2 ^ b)
>>> 「無符號右移」
  • 忽略符號位,空位都以0補齊

無符號右移規則和右移運算是同樣的,只是填充時無論左邊的數字是正是負都用0來填充,無符號右移運算只針對負數計算,而且結果必定是一個正數,由於對於正數來講這種運算沒有意義

y>>>1 =  0000 ... 0010 >>>1 = 0000 ... 0001 = 1
-y>>>1 =  1000 ... 0010 (源碼)>>1 = 1111 ... 1110 (補碼)>> 1 == 0111 ... 1111(計算以後的補碼) = 0111 ... 1111(源碼)= 2147483647
//由於負數最高位補0 變成了正數,正數的補碼源碼都是它本身,因此變成了一個很大的數,這一點要特別注意。
複製代碼
應用示例
  • 兩個數互換
x = x^y = 0001 ^ 0010 = 0011 = 3
y = y^x = 0010 ^ 0011 = 0001 = 1
x = x^y = 0011 ^ 0001 = 0010 = 2 
複製代碼

正好互換了,因此之後就能夠這麼寫:

x^=y,
y^=x,
x^=y
複製代碼

基於以上特性,能夠實現對一個數字進行加密,一串數字,對一箇中間數字進行異或運算,獲得加密數據,解密再次對中間數字進行異或便可。

  • | 與 & 結合起來,一個int 表示多個屬性

平時你們寫代碼是否遇到過這樣的場景:一個類,有一個屬性是用boolean表示,隔了一段時間,又須要新加一個boolean表示新的屬性。。。因此就如同下面的代碼:

public class Human {
    /** * 是不是學生 */
    private boolean isStudent;

    /** * 是否已經成年 */
    private boolean isAdult;

    /** * 是否單身 */
    private boolean isSingle;

    // private boolean is.....


    public void setStudent(boolean student) {
        isStudent = student;
    }

    public boolean isStudent() {
        return isStudent;
    }
    
    // setter and getter...
}
複製代碼

那如今,經過位運算,咱們能夠這麼寫:

public class Human {
    /** * 是不是學生 */
    public static final int IS_STUDENT = 1;

    /** * 是否已經成年 */
    public static final int IS_ADULT = 2;

    /** * 是否單身 */
    public static final int IS_SINGLE = 4;

    private int properties;

    public void setProperties(int properties) {
        this.properties = properties;
    }

    public boolean isStudent() {
        return (properties & IS_STUDENT) != 0;
    }

    public boolean isAdult() {
        return (properties & IS_ADULT) != 0;
    }

    public boolean isSingle() {
        return (properties & IS_SINGLE) != 0;
    }

    @Override
    public String toString() {
        return "是不是學生 " + isStudent() + " 是否成年 " + isAdult() + " 是否單身 " + isSingle();
    }
}
複製代碼

咱們在傳入參數只提供一個setProperties 方法,咱們在傳入參數的地方用 「|」運算符,分隔咱們想要指定的屬性,下面是測試代碼:

public static void main(String args[]) {
        Human human = new Human();
        human.setProperties(Human.IS_STUDENT);
        System.out.println(human.toString());

        human.setProperties(Human.IS_STUDENT | Human.IS_SINGLE);
        System.out.println(human.toString());

        human.setProperties(Human.IS_SINGLE | Human.IS_ADULT);
        System.out.println(human.toString());

        human.setProperties(Human.IS_STUDENT | Human.IS_SINGLE | Human.IS_ADULT);
        System.out.println(human.toString());
    }
複製代碼

輸出結果

是不是學生 true 是否成年 false 是否單身 false
是不是學生 true 是否成年 false 是否單身 true
是不是學生 false 是否成年 true 是否單身 true
是不是學生 true 是否成年 true 是否單身 true
複製代碼

原理分析: 首先,注意看,我定義的常量除了0以外都是一、二、4 、即2 ^ n

a = 1 =  0000 0001
b = 2 =  0000 0010
c = 4 =  0000 0100
d = 8 =  0000 1000
e = 16 = 0001 0000
f = 32 = 0010 0000
//......即我能夠定義最多31個數(第32位表正負)
複製代碼

這樣一來,咱們用「|」 運算符將其中任意兩個或者多個進行計算的時候,其實是把它們按照本身的佔位保存了例如:

int x = c|d|e = 0001 1100 = 28
//此時判斷x是否包含 c 或者 d 
 //用&便可
 
 int y = z&c = 0000 0100 = 4 = c
 int z = z&d = 0000 1000 = 8 = d
 其實只要結果不爲 0000 0000 也就是0 表示&運算符成立 ,就能夠判斷是否包含該數字,即上面函數的方法的定義。
複製代碼

下面我看看,衆所周知,咱們Android LinearLayout 有這樣一個屬性"showDividers":

<LinearLayout
        android:showDividers="beginning|middle|end"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
複製代碼

分別表示你能夠在view的幾個位置展現分割線,爲何能夠用這個屬性表示三個位置呢?咱們進入源碼看看:

private int mShowDividers;

    public static final int SHOW_DIVIDER_NONE = 0;

    public static final int SHOW_DIVIDER_BEGINNING =1;
    
    public static final int SHOW_DIVIDER_MIDDLE = 2;
    
    public static final int SHOW_DIVIDER_END = 4;

    public void setShowDividers( int showDividers) {
        if (showDividers == mShowDividers) {
            return;
        }
        mShowDividers = showDividers;

        setWillNotDraw(!isShowingDividers());
        requestLayout();
    }
    
    protected boolean hasDividerBeforeChildAt(int childIndex) {
        if (childIndex == getVirtualChildCount()) {
            return (mShowDividers & SHOW_DIVIDER_END) != 0;
        }
        boolean allViewsAreGoneBefore = allViewsAreGoneBefore(childIndex);
        if (allViewsAreGoneBefore) {
            return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
        } else {
            return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0;
        }
    }
複製代碼

我只列出了上面核心的幾行代碼,首先,全部的的顯示屬性都是 一個int 的 mShowDividers 表示,set方法用「|」 進行指定,在看核心的代碼在onDraw 方法中繪製分割線的時候,會調用這個方法,判斷方法就是用&運算符。 另外,View.MeasureSpec 和Gravity 的類也用到了位運算符,具體這裏就不深刻探討了。

參考源碼: Android :

  • LinearLayout
  • View.MeasureSpec
  • Grivaty
相關文章
相關標籤/搜索