
要時刻對自己說:
回到正題,今天是講位運算的,肯定有你不知道的。
提綱:
2.2 異或基本算法
2.2.1 補充例子異或加密解密
2.3 ‘按位與’運算 就是那麼簡單
2.4 從非中,學習原碼補碼運算
2.5 綜合算法現實案例
2.6 總結
看題目顧名思義,泥瓦匠要跟你們聊聊這位運算。怎麼聊法,依舊和老套。師傅出招棋盤上馬,你徒弟怎麼對答,然後周而複始。你就青出于藍了。很早的時候在一本《算法競賽入門經典》,有些acmer應該知道的。原題目很簡單,是這樣的:
兩個變量a,b如何交換。
“泥瓦匠,你找抽。定個temp不就搞定了。”罵聲将至,我趕緊說,除了這個方法,還有下面這個:
1
2
3
4
5
6
<code><</code><code>font</code> <code>face="宋體" size="4">a = a + b;</code>
<code>b = a –b;</code>
<code>a = a - b;</code>
<code></</code><code>font</code><code>></code>
這樣子,我們不可全盤否認,在多數情況下,還是能達到目的的。但是問題就來了,這樣做為什麼不行呢。我們考慮下,一個是這試用的範圍很小,不滿足全部類型資料。二個是這個會越界,越界導緻我們算法和程式會bug或者無法進行。
又看看題目,聰明的小夥伴興許想到了:
7
8
<code><</code><code>font</code> <code>face="宋體" size="4">private static void test1()</code>
<code> </code><code>{</code>
<code> </code><code>int a = 11;</code>
<code> </code><code>int b = 222;</code>
<code> </code><code>a = a ^ b;</code>
<code> </code><code>b = a ^ b;</code>
<code> </code><code>}</</code><code>font</code><code>></code>
看着上面這個運算是cpu位運算,是以效率極高,也不會越界。在計算機系統中大量存在,可以反向規則恢複資料本身。這時候有些小夥伴不懂,泥瓦匠就補下異或的知識吧。
異或運算,是按照二進制位進行。異或運算的規則是0⊕0=0,0⊕1=1,1⊕0=1,1⊕1=0。“泥瓦匠,這個也太煩了吧,記公式我最不行了。”别怕,泥瓦匠有高招。這個也是來自前人,古人的武功秘籍。不是嗎什麼易筋經,什麼少林武學。哈哈,太極啊,書法啊,要那個靈性,技巧。哈哈,泥瓦匠喜歡書法,喜歡的可以交流。
記憶宮殿:異或異或,又稱半加法運算。例如,1⊕1可以當成二進下,1+1=10然後取最後一位,正好是異或的結果,0+0、1+1、0+1同理。這隻是前人流傳下來的記憶方法。
泥瓦匠就在補充個例子,利用抑或算法進行加密解密。這是種簡單加密,但是也有它的優勢:速度快,長資料的一般加密任務很适合。不多說,代碼如下:
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<code>/* encrypt using the exclusive or*/</code>
<code> </code><code>private static void test2(string str)</code>
<code> </code><code>byte key = 123;</code>
<code> </code><code>byte strbyte[] = str.getbytes();</code>
<code> </code>
<code> </code><code>/* encrypt */</code>
<code> </code><code>for (int i = 0; i < strbyte.length; i++)</code>
<code> </code><code>{</code>
<code> </code><code>strbyte[i] = (byte) (strbyte[i] ^ key);</code>
<code> </code><code>}</code>
<code> </code>
<code> </code><code>system.out.println("加密後:" + new string(strbyte));</code>
<code> </code><code>/* decrypt */</code>
<code> </code><code>system.out.println("解密後:" + new string(strbyte));</code>
<code> </code><code>}</code>
我們測試下 test2("13958686678");,然後在控制台可以清楚的看出加密後和解密後的狀态:
總結:加密解密的操作方式一樣,這個加密不怎麼棒,而且一看資料都是對應的aabbcc型。但加密後的資料也可以進行存儲或網絡傳輸。如果有不需要很大安全性的,但是資料有長要求速度和效率的可以嘗試下哦
下面泥瓦匠要講這個&了。上面講了異或,下面我就講講這個,在很人眼裡仿佛和異或有着某種,或是對立或是啥的關系的按位與運算符。其實沒有,他們
隻是在cpu級别的,一些門電路設計下的規律而已。哈哈,雖然學過電子技術,但經常逃課,還挂科了,泥瓦匠也就不顯擺我電子技術多牛逼了。舉個例子吧:
<code> </code><code>private static void test3()</code>
<code>{</code>
<code> </code><code>int a = 6550; // 1100110010110</code>
<code> </code><code>a = a & 255; // 11111111</code>
<code> </code><code>system.out.println(a);// 10010110</code>
<code>}</code>
例子中,看了注釋有些人懂了。其執行個體子很簡單,就是一個數字截取二進制下從低到高的8位。和上面的一樣,這運算符廣泛的運用在系統内部。效率極高,但我們
也可以在某種算法中去。比如我現在想要6550的二進制的不要二進制下從低到高的8位,那樣你是否馬上就能寫出。隻要把255改成(65535 -
255),然後右移8位即可呢?為什麼是這個兩個數字呢,泥瓦匠告訴你轉化成二進制你就明白了。
按位與運算與(and):對兩個整型操作數中對應位執行布爾代數,兩個位都為1時輸出1,否則0。
這也沒什麼記憶技巧,一與一,唱着歌謠記下去。
累了累了,先去玩一會,再看下面很枯燥的東西吧。我推薦番茄工作法。25分鐘。
下面泥瓦匠和你們一起學習這個枯燥的東西。要學習原碼補碼運算。首先得知道一些基礎的東西:機器數和真值。所謂的機器數就是,一個數在計算機中的二進制形
式。機器數時代符号的,在計算機用一個數的最高位存放符号。正數為0,負數為1。那什麼叫真值呢?真值其實就是實際值,因為最高位的0或者1會導緻形式上
的有變,就像 1000 0011 表示機器數,它的真值為 –3 或者 -000
0001。哈哈?泥瓦匠這個太簡單的。對,什麼事情都是簡單到複雜。簡單的事情做多了就成大事了。
而原碼, 反碼, 補碼是機器存儲一個具體數字的編碼方式。下面簡單介紹這三個:
原碼是人最容易了解和計算的表達式。第一位表示符号,後面的表示值。0為正值,1為負值。
反碼,顧名思義,和原碼有關系。有時候,一個反碼表示負數,我們無法将其計算。可以轉換為原碼在計算。反碼的計算方法如下:
正數的反碼是其本身
負數的反碼是在其原碼的基礎上, 符号位不變,其餘各個位取反.
補碼的表達方式也如下:
正數的補碼就是其本身
負數的補碼是在其原碼的基礎上, 符号位不變, 其餘各位取反, 最後+1. (即在反碼的基礎上+1)
“我去,泥瓦匠,頭暈了。哈哈累了累了。慢慢來”
為什麼要用這些呢。我的建議先死背,然後用領會。因為計算機裡面最基礎的運算需要他們幫助計算機完成。因為一個符号位正負會讓基礎電路設計很複雜。是以就想出來用符号位參與運算。反碼,補碼是用來計算用的。下面舉個簡單的例子。首先是反碼計算十進制的表達式: 1-1=0。下面摘自網絡大牛結論:
<code>1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0</code>
發現用反碼計算減法, 結果的真值部分是正确的. 而唯一的問題其實就出現在"0"這個特殊的數值上. 雖然人們了解上+0和-0是一樣的, 但是0帶符号是沒有任何意義的. 而且會有[0000 0000]原和[1000 0000]原兩個編碼表示0.
于是補碼的出現, 解決了0的符号以及兩個編碼的問題:
<code>1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]補 + [1111 1111]補 = [0000 0000]補=[0000 0000]原</code>
這樣0用[0000 0000]表示, 而以前出現問題的-0則不存在了.而且可以用[1000 0000]表示-128:
<code>(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]補 + [1000 0001]補 = [1000 0000]補</code>
-1-127的結果應該是-128, 在用補碼運算的結果中, [1000 0000]補 就是-128. 但是注意因為實際上是使用以前的-0的補碼來表示-128, 是以-128并沒有原碼和反碼表示.(對-128的補碼表示[1000 0000]補算出來的原碼是[0000 0000]原, 這是不正确的)
使用補碼, 不僅僅修複了0的符号以及存在兩個編碼的問題, 而且還能夠多表示一個最低數. 這就是為什麼8位二進制, 使用原碼或反碼表示的範圍為[-127, +127], 而使用補碼表示的範圍為[-128, 127].
因為機器使用補碼, 是以對于程式設計中常用到的32位int類型, 可以表示範圍是: [-231, 231-1] 因為第一位表示的是符号位.而使用補碼表示時又可以多儲存一個最小值.
深呼吸,還有關于現執行個體子的算法。加油泥瓦匠~
最後一個綜合性算法例子,泥瓦匠就結束這篇位運算的文章。泥瓦匠都口水講完了。看吧看吧,一起看。
業務系統中,我們會發現大量的判定是否。以int資料為例子,如果按照十進制的方式存儲資料,一個32位的int變量隻能存儲一個數值,而如果使用二進制
方式存儲資料(缺點是隻能存儲0或1兩個資料)則可以存儲32個資料,将極大的節約記憶體。利用位運算存儲資料,主要是為了減少程式占用的記憶體。這樣的設計
是沒有錯的,但是如何操作呢?哈哈,泥瓦匠也不賣關子了。當然是位運算。但有些同學用什麼tobinary轉來轉去,都不知道這樣的操作複雜度很高,導緻
得不償失。
例如,在一個int變量的從右側開始倒數第5位存儲資料,則存儲和讀取資料的代碼如下所示:
<code>private static void test4()</code>
<code> </code><code>//在一個int變量的從右側開始倒數第5位存儲資料</code>
<code> </code><code>int bdata = 0;</code>
<code> </code><code>bdata = bdata | (1 << (5 - 1)); //存儲數值1</code>
<code> </code><code>system.out.println(bdata);</code>
<code> </code><code>bdata = bdata & (~(1 << (5 - 1))); //存儲數值0</code>
<code> </code>
<code> </code><code>int n = bdata & (1 << (5 - 1)); //讀取資料 </code>
<code> </code><code>system.out.println(n);</code>
例子可以看出,我們成功的把5位的1和0互換。這就代表着一個是否的是否狀态。是以泥瓦匠想說的是,考慮int的位而不是表面的意義能創造更多财富。财富來源于細節。和武學一樣,最高境界就是 無形。但是要從有形來,組合的有形深不可測。就像這個綜合案例一樣。
精髓:掌握到底層的妙用,方能成就高層建築。
組合拳的組合的有形深不可測。就像這個綜合案例一樣。