文章看事後感受受益不淺,因此留下了以備溫故:http://www.congmo.net/blog/2012/03/11/Long-ByteShifting/
java
本篇主要講述位移運算在Long中所扮演的重要角色,有些出神入化的我根本沒法理解,可是我一直秉承着不管對錯都要記錄思考的過程的宗旨寫每一篇文章。這一篇也不例外,讀Long這個類確實須要比較普遍的知識面,我也是一邊在OSChina和stackoverflow上提問,一邊慢慢的鑽研,不免會存在誤差。web
先來看個簡單的。
算法
public static int signum(long i) { // HD, Section 2-7 return (int) ((i >> 63) | (-i >>> 63)); }
這個函數做用就是返回參數i的符號,若是返回-1則是負數,若是返回0則是0,若是返回1則是正數。算法就是(int) ((i >> 63) | (-i >>> 63)),若是是正數的話i有符號右移63位後爲0,-i無符號右移63位以後結果爲1,或操做以後結果就是1.若是i爲負數,那麼有符號右移63位後就變成了1,而後-i無符號右移63位後就只剩下符號位,最後作或(|)操做結果就是-1. 若是參數i爲0,那麼移位後結果就是0.
函數
System.out.println(Long.signum(100L)); System.out.println(Long.signum(0L)); System.out.println(Long.signum(-100L));
輸出結果:
1
0
-1
接着是一個不多用到,可是實現方式不錯的兩個方法,循環左移和循環右移方法。
ui
public static long rotateLeft(long i, int distance) { return (i << distance) | (i >>> -distance); } public static long rotateRight(long i, int distance) { return (i >>> distance) | (i << -distance); }
實現的代碼量能夠說已經精簡到最少了,有一點要注意的是,循環移位時,參數distance能夠接受負數,當distance爲負數時,這個等式是成立的,rotateLeft(i, distance) = rotateRight(i, -distance)。這個方法中有兩點值得借鑑的,第一從總體上講循環移位的實現方式;第二是distance與-distance的巧妙運用。google
就拿循環左移先來講說第二點吧,前置條件,咱們首先假設distance大於0,起先我是很不理解i >>> -distance的,後來在stackoverflow上發問,有人給出瞭解釋,在移位的時候,若是distance小於0,會根據被移位數的長度進行轉換。就好比說這裏咱們對long進行移位,那麼-distance就會被轉換成(64 + distance)(注,這裏的distance是小於0的)。這樣的話,若是distance大於0時,(i << distance) | (i >>> -distance);就會被轉化成(i << distance) | (i >>> 64 + distance);spa
清楚了第二點,那麼第一點也就不難理解了。用一幅圖來解釋循環左移。
在distance大於0的前提下,先左移distance位,而後再右移64-distance,最終用或運算相加,就是循環移位的結果。圖中爲了省事兒用了8位作了個演示,先左移3位,而後右移(8-3)位,或運算以後就是結果啦。關於-distance在stackoverflow上的提問在這裏。.net
下面是個更給力的方法-reverse(long i),能夠說就是高效率的化身。code
public static long reverse(long i) { // HD, Figure 7-1 i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L; i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL; i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL; i = (i << 48) | ((i & 0xffff0000L) << 16) | ((i >>> 16) & 0xffff0000L) | (i >>> 48); return i; }
從總體上說,這個reverse方法集移位與二分算法於一身,堪稱經典。 第一步以單位爲單位,奇偶位交換 第二步以兩位爲單位,完成先後兩位的交換。 第三步以四位爲單位,完成先後四位的交換。 第四步以八位爲單位,完成先後八位的交換。 最後一步沒有按常理繼續二分,而是經過一個轉換一步就完成了以16和32位爲單位的交換。進而結束了整個64位的反轉。orm
如今一步一步剖析都是如何實現的。
i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L;
16進制的5爲0101,或操做前半部分首先取出i的全部奇數位,而後總體左移一位,這樣實現i的奇數位左移一位變成偶數位;或操做後半部分先右移,即將偶數位右移變成奇數位,而後再取出奇數位。這樣就完成了64位中奇數位與偶數位的交換。
i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L;
(i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL; (i << 48) (i & 0xffff0000L) << 16) (i >>> 16) & 0xffff0000L) (i >>> 48);這幅圖描述每一個編號代碼執行以後64位的變化。
public static long reverse(long i) { // HD, Figure 7-1 i = (i & 0x5555555555555555L) << 1 | (i >>> 1) & 0x5555555555555555L; i = (i & 0x3333333333333333L) << 2 | (i >>> 2) & 0x3333333333333333L; i = (i & 0x0f0f0f0f0f0f0f0fL) << 4 | (i >>> 4) & 0x0f0f0f0f0f0f0f0fL; i = (i & 0x00ff00ff00ff00ffL) << 8 | (i >>> 8) & 0x00ff00ff00ff00ffL; i = (i & 0x0000ffff0000ffffL) << 16 | (i >>> 16) & 0x0000ffff0000ffffL; i = (i & 0x00000000ffffffffL) << 32 | (i >>> 32) & 0x00000000ffffffffL; return i; }
至於爲何要採用那種方式,而不是用」純粹」的二分法,在stackoverflow上有人提到,多是由於前一種實現方式須要9個操做,然後一種實現方式須要10個操做。具體是出於怎樣的目的可能只有做者才知道。關於reverse我在stackoverflow上的提問在這裏。
最後看一個方法。
public static int bitCount(long i) { // HD, Figure 5-14 i = i - ((i >>> 1) & 0x5555555555555555L); i = (i & 0x3333333333333333L) + ((i >>> 2) & 0x3333333333333333L); i = (i + (i >>> 4)) & 0x0f0f0f0f0f0f0f0fL; i = i + (i >>> 8); i = i + (i >>> 16); i = i + (i >>> 32); return (int)i & 0x7f; }這個方法是返回一個long類型的數字對應的二進制中1的個數,其實google上有不少種,這裏採用的叫平行算法實現的,算法以下圖。