計算機中的進位制&位運算
在十進位制中,個位的1代表10⁰=1,十位的1代表10¹=10,百位的1代表10²=100,所以:123=1×10²+2×10¹+3×10⁰
同樣道理,在二進位制中,個位的1代表2⁰=1,十位的1代表2¹=2,百位的1代表2²=4,所以:(A3A2A1A0)₂=A3×2³+A2×2²+A1×2¹+A0×2⁰
如果二進位制和十進位制數出現在同一個等式中,為了區別我們用(A3A2A1A0)₂這種形式表示A3A2A1A0是二進位制數,每個數字只能是0或1,其它沒有套括號加下標的數仍表示十進位制數。對於(A3A2A1A0)₂這樣一個二進位制數,最左邊的A3位稱為最高位(MSB,Most Significant Bit),最右邊的A0位稱為最低位(LSB,Least Significant Bit)。以後我們遵循這樣的慣例: LSB稱為第0位而不是第1位,所以如果一個數是32位的,則MSB是第31位 。上式就是從二進位制到十進位制的換算公式。
下面來看十進位制怎麼換算成二進位制。我們知道
13=1×2³+1×2²+0×2¹+1×2⁰ 所以13換算成二進位制應該是(1101)₂。問題是怎麼把13分解成等號右邊的形式呢?注意到等號右邊可以寫成
13=(((0×2+1₃)×2+1₂)×2+0₁)×2+1₀
我們將13反覆除以2取餘數就可以提取出上式中的1101四個數字,為了讓讀者更容易看清楚是哪個1和哪個0,上式和下式中對應的數字都加了下標:
13÷2=6...1₀ 6÷2=3...0₁ 3÷2=1...1₂ 1÷2=0...1₃
把這四步得到的餘數按相反的順序排列就是13的二進位制表示,因此這種方法稱為除二反序取餘法。
計算機用二進位制表示數,程式員也必須習慣使用二進位制,但二進位制寫起來太囉嗦了,所以通常將二進位制數分成每三位一組或者每四位一組,每組用一個數字表示。比如把(10110010)₂從最低位開始每三位分成一組,10、110、010,然後把每組寫成一個十進位制數字,就是(262)₈,這樣每一位數字的取值範圍是0~7,逢八進一,稱為八進位制(Octal)。類似地,把(10110010)₂分成每四位一組,1011、0010,然後把每組寫成一個數字,這個數的低位是2,高位已經大於9了,我們規定用字母A~F表示10~15,這個數可以寫成(B2)₁₆,每一位數字的取值範圍是0~F,逢十六進一,稱為十六進位制(Hexadecimal)。所以,八進位制和十六進位制是程式設計師為了書寫二進位制方便而發明的簡便寫法,好比草書和正楷的關係一樣。
位運算:
整數在計算機中用二進位制的位來表示,C語言提供一些運算子可以直接操作整數中的位,稱為位運算。
1.1. 按位與、或、異或、取反運算
- 按位與(&):兩個運算元都是1,結果是1,否則是0
- 或(|):兩個運算元有一個1,結果是1
- 異或(^):兩個運算元相同則結果為0,兩個運算元不同則結果為1
- 取反運算(~):對運算元取反

1.2. 移位運算
移位運算子(Bitwise Shift)包括左移<<和右移>>。左移將一個整數的各二進位制位全部左移若干位,例如0xcfffffff3<<2得到0x3fffffcc:

Java 平臺都是有符號整數,所以上述圖一操作在Java中符號位發生了變化值由(-805306381)變為(1073741772)
在一定的取值範圍內,將一個整數左移1位相當於乘以2。比如二進位制11(十進位制3)左移一位變成110,就是6,再左移一位變成1100,就是12。讀者可以自己驗證這條規律對有符號數和無符號數都成立,對負數也成立。當然,如果左移改變了最高位(符號位),那麼結果肯定不是乘以2了,所以我加了個前提“ 在一定的取值範圍內 ”。由於計算機做移位比做乘法快得多,編譯器可以利用這一點做優化,比如看到原始碼中有i * 8,可以編譯成移位指令而不是乘法指令。
當運算元是無符號數時,右移運算的規則和左移類似,例如0xcffffff3>>2得到0x33fffffc:

Java 平臺執行結果:值由-805306381 變成 -201326596 仍然保留負數的符號位,相當於除以4
最低兩位的11被移出去了,最高兩位又補了兩個0,其它位依次右移兩位。和左移類似,移動的位數也必須小於左運算元的總位數,否則結果是Undefined。在一定的取值範圍內,將一個整數右移1位相當於除以2,小數部分截掉。
當運算元是有符號數時,右移運算的規則比較複雜:
- 如果是正數,那麼高位移入0
- 如果是負數,那麼高位移入1還是0不一定,這是Implementation-defined的。對於x86平臺的gcc編譯器,最高位移入1,也就是仍保持負數的符號位,這種處理方式對負數仍然保持了“右移1位相當於除以2”的性質。
綜上所述,由於型別轉換和移位等問題,用有符號數做位運算是很不方便的,所以, 建議只對無符號數做位運算,以減少出錯的可能 。
1.3. 掩碼:
如果要對一個整數中的某些位進行操作,怎樣表示這些位在整數中的位置呢?可以用掩碼(Mask)來表示。比如掩碼 0x0000ff00 表示對一個32位整數的8~15位進行操作,舉例如下。
1、取出8~15位。
unsigned int a, b, mask = 0x0000ff00; a = 0x12345678; b = (a & mask) >> 8; /* 0x00000056 */ 複製程式碼
2、將8~15位清0。
unsigned int a, b, mask = 0x0000ff00; a = 0x12345678; b = a & ~mask; /* 0x12340078 */ 複製程式碼
3、將8~15位置1。
unsigned int a, b, mask = 0x0000ff00; a = 0x12345678; b = a | mask; /* 0x1234ff78 */ 複製程式碼
位運算在雪花演算法的應用:
- 12bits 毫秒增量的最大值:1右移12位減去1,可以自己用等比數列計算下12bit的最大值,看是否和位移的結果一致;二進位制表示:(...001111 1111 1111)(14位以後的0用省略號代替)
- 10bits 工作程序Id :1右移10位,這裡我有個疑問,不應該再減去1,才是最大值麼
- 判斷是否需要獲取下一個時間的依據: 0L == (sequence = ++sequence & SEQUENCE_MASK) sequence和最大值兩個數 按位與 ,只有當sequence大於SEQUENCE_MASK 的時候,&的結果是0,獲取下一個時間戳
- 41bits:(currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS,通過右移22位,空出10bits的程序ID位、12bits的毫秒自增量
- ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence; 41bits|10bits|12bits,因為右移低位補0,按位或操作,其實就是運算元本身。
shrding-jdbc預設的實現:
/** * 預設的主鍵生成器. * * <p> * 長度為64bit,從高位到低位依次為 * </p> * * <pre> * 1bit符號位 * 41bits 時間偏移量從2016年11月1日零點到現在的毫秒數 * 10bits 工作程序Id * 12bits 同一個毫秒內的自增量 * </pre> * * <p> * 工作程序Id獲取優先順序: 系統變數{@code sharding-jdbc.default.key.generator.worker.id} 大於 環境變數{@code SHARDING_JDBC_DEFAULT_KEY_GENERATOR_WORKER_ID} * ,另外可以呼叫@{@code DefaultKeyGenerator.setWorkerId}進行設定 * </p> * * @author gaohongtao */ @Getter @Slf4j public final class DefaultKeyGenerator implements KeyGenerator { public static final long EPOCH; public static final String WORKER_ID_PROPERTY_KEY = "sharding-jdbc.default.key.generator.worker.id"; public static final String WORKER_ID_ENV_KEY = "SHARDING_JDBC_DEFAULT_KEY_GENERATOR_WORKER_ID"; private static final long SEQUENCE_BITS = 12L; private static final long WORKER_ID_BITS = 10L; private static final long SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1; private static final long WORKER_ID_LEFT_SHIFT_BITS = SEQUENCE_BITS; private static final long TIMESTAMP_LEFT_SHIFT_BITS = WORKER_ID_LEFT_SHIFT_BITS + WORKER_ID_BITS; private static final long WORKER_ID_MAX_VALUE = 1L << WORKER_ID_BITS; @Setter private static TimeService timeService = new TimeService(); @Getter private static long workerId; static { Calendar calendar = Calendar.getInstance(); calendar.set(2016, Calendar.NOVEMBER, 1); calendar.set(Calendar.HOUR_OF_DAY, 0); calendar.set(Calendar.MINUTE, 0); calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); EPOCH = calendar.getTimeInMillis(); initWorkerId(); } private long sequence; private long lastTime; public static void initWorkerId() { String workerId = System.getProperty(WORKER_ID_PROPERTY_KEY); if (!Strings.isNullOrEmpty(workerId)) { setWorkerId(Long.valueOf(workerId)); return; } workerId = System.getenv(WORKER_ID_ENV_KEY); if (Strings.isNullOrEmpty(workerId)) { return; } setWorkerId(Long.valueOf(workerId)); } /** * 設定工作程序Id. * * @param workerId 工作程序Id */ public static void setWorkerId(final long workerId) { Preconditions.checkArgument(workerId >= 0L && workerId < WORKER_ID_MAX_VALUE); DefaultKeyGenerator.workerId = workerId; } /** * 生成Id. * * @return 返回@{@link Long}型別的Id */ @Override public synchronized Number generateKey() { long currentMillis = timeService.getCurrentMillis(); Preconditions.checkState(lastTime <= currentMillis, "Clock is moving backwards, last time is %d milliseconds, current time is %d milliseconds", lastTime, currentMillis); if (lastTime == currentMillis) { if (0L == (sequence = ++sequence & SEQUENCE_MASK)) { currentMillis = waitUntilNextTime(currentMillis); } } else { sequence = 0; } lastTime = currentMillis; if (log.isDebugEnabled()) { log.debug("{}-{}-{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(lastTime)), workerId, sequence); } return ((currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS) | (workerId << WORKER_ID_LEFT_SHIFT_BITS) | sequence; } private long waitUntilNextTime(final long lastTime) { long time = timeService.getCurrentMillis(); while (time <= lastTime) { time = timeService.getCurrentMillis(); } return time; } } 複製程式碼