計算機中的進制&位運算

爲何計算機用二進制計數:

計算機是由電路構成的,電路只有0和1 兩種狀態。程序員

不一樣進制間的換算:

在十進制中,個位的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位。上式就是從二進制到十進制的換算公式。bash

下面來看十進制怎麼換算成二進制。咱們知道ide

13=1×2³+1×2²+0×2¹+1×2⁰ 因此13換算成二進制應該是(1101)₂。問題是怎麼把13分解成等號右邊的形式呢?注意到等號右邊能夠寫成優化

13=(((0×2+1₃)×2+1₂)×2+0₁)×2+1₀ui

咱們將13反覆除以2取餘數就能夠提取出上式中的1101四個數字,爲了讓讀者更容易看清楚是哪一個1和哪一個0,上式和下式中對應的數字都加了下標:spa

13÷2=6...1₀ 6÷2=3...0₁ 3÷2=1...1₂ 1÷2=0...1₃debug

把這四步獲得的餘數按相反的順序排列就是13的二進制表示,所以這種方法稱爲除二反序取餘法。code

計算機用二進制表示數,程序員也必須習慣使用二進制,但二進制寫起來太囉嗦了,因此一般將二進制數分紅每三位一組或者每四位一組,每組用一個數字表示。好比把(10110010)₂從最低位開始每三位分紅一組,十、1十、010,而後把每組寫成一個十進制數字,就是(262)₈,這樣每一位數字的取值範圍是0~7,逢八進一,稱爲八進制(Octal)。相似地,把(10110010)₂分紅每四位一組,10十一、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位進行操做,舉例以下。

一、取出8~15位。

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = (a & mask) >> 8; /* 0x00000056 */
複製代碼

二、將8~15位清0。

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a & ~mask; /* 0x12340078 */
複製代碼

三、將8~15位置1。

unsigned int a, b, mask = 0x0000ff00;
a = 0x12345678;
b = a | mask; /* 0x1234ff78 */
複製代碼

位運算在雪花算法的應用:

  1. 12bits 毫秒增量的最大值:1右移12位減去1,能夠本身用等比數列計算下12bit的最大值,看是否和位移的結果一致;二進制表示:(...001111 1111 1111)(14位之後的0用省略號代替)
  2. 10bits 工做進程Id :1右移10位,這裏我有個疑問,不該該再減去1,纔是最大值麼
  3. 判斷是否須要獲取下一個時間的依據:0L == (sequence = ++sequence & SEQUENCE_MASK) sequence和最大值兩個數 按位與,只有當sequence大於SEQUENCE_MASK 的時候,&的結果是0,獲取下一個時間戳
  4. 41bits:(currentMillis - EPOCH) << TIMESTAMP_LEFT_SHIFT_BITS,經過右移22位,空出10bits的進程ID位、12bits的毫秒自增量
  5. ((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;
    }
}
複製代碼

最後:

小尾巴走一波,歡迎關注個人公衆號,不按期分享編程、投資、生活方面的感悟:)

相關文章
相關標籤/搜索