作者: admin

  • 剖析nsq消息隊列(三) 消息傳輸的可靠性和持久化[二]diskqueue

    剖析nsq消息隊列(三) 消息傳輸的可靠性和持久化[二]diskqueue

    ,大概提了一下消息的持久化,--mem-queue-size 設置為 0,所有的消息將會存儲到磁盤。
    總有人說nsq的持久化問題,消除疑慮的方法就是閱讀原碼做benchmark測試,個人感覺nsq還是很靠譜的。
    nsq自己實現了一個先進先出的消息文件隊列是把消息保存到本地文件內,很值得分析一下他的實現過程。

    整體處理邏輯

    go-diskqueue 會啟動一個gorouting進行讀寫數據也就是方法ioLoop
    會根據你設置的參數來進行數據的讀寫,流程圖如下

    這個圖畫的也不是特別的準確 ioLoop 用的是 select 並不是if else 當有多個條件為true時,會隨機選一個進行執行

    nsq 生成的數據大致如下:

    xxxx.diskqueue.meta.dat 元數據保存了未讀消息的長度,讀取和存入數據的編號和讀取位置
    xxxx.diskqueue.編號.dat 消息保存的文件,每一個消息的存儲:4Byte消息的長度+消息

    參數說明

    一些主要的參數和約束說明
    這些參數的使用在後面的處理邏輯中會提到

    // diskQueue implements a filesystem backed FIFO queue
    type diskQueue struct {
        // run-time state (also persisted to disk)
        // 讀取數據的位置    
        readPos      int64
        // 寫入數據的位置
        writePos     int64
        // 讀取文件的編號    
        readFileNum  int64
        // 寫入文件的編號
        writeFileNum int64
        // 未處理的消息總數    
        depth        int64
    
        // instantiation time metadata
        // 每個文件的大小限制    
        maxBytesPerFile int64 // currently this cannot change once created
        // 每條消息的最小大小限制    
        minMsgSize      int32
        // 每條消息的最大大小限制    
        maxMsgSize      int32
        // 緩存消息有多少條後進行寫入    
        syncEvery       int64         // number of writes per fsync
        // 自動寫入消息文件的時間間隔    
        syncTimeout     time.Duration // duration of time per fsync
        exitFlag        int32
        needSync        bool
    
        // keeps track of the position where we have read
        // (but not yet sent over readChan)
        // 下一條消息的位置    
        nextReadPos     int64
        // 下一條消息的文件編號    
        nextReadFileNum int64
    
        // 讀取的文件
        readFile  *os.File
        // 寫入的文件    
        writeFile *os.File
        // 讀取的buffer    
        reader    *bufio.Reader
        // 寫入的buffer    
        writeBuf  bytes.Buffer
    
        // exposed via ReadChan()
        // 讀取數據的channel    
        readChan chan []byte
    
        //.....
    }

    數據

    元數據

    讀寫數據信息的元數據保存在xxxxx.diskqueue.meta.data文件內主要用到代碼里的字段如下
    未處理的消息總數 depth
    讀取文件的編號 readFileNum 讀取數據的位置 readPos
    寫入文件的編號 writeFileNum 寫入數據的位置 writePos
    真實數據如下

    15
    0,22
    3,24

    保存元數據信息

    func (d *diskQueue) persistMetaData() error {
        // ...
        fileName := d.metaDataFileName()
        tmpFileName := fmt.Sprintf("%s.%d.tmp", fileName, rand.Int())
        // write to tmp file
        f, err = os.OpenFile(tmpFileName, os.O_RDWR|os.O_CREATE, 0600)
        // 元數據信息
        _, err = fmt.Fprintf(f, "%d\n%d,%d\n%d,%d\n",
            atomic.LoadInt64(&d.depth),
            d.readFileNum, d.readPos,
            d.writeFileNum, d.writePos)
        // 保存
        f.Sync()
        f.Close()
        // atomically rename
        return os.Rename(tmpFileName, fileName)
    }

    得到元數據信息

    func (d *diskQueue) retrieveMetaData() error {
        // ...
        fileName := d.metaDataFileName()
        f, err = os.OpenFile(fileName, os.O_RDONLY, 0600)
        // 讀取數據並賦值
        var depth int64
        _, err = fmt.Fscanf(f, "%d\n%d,%d\n%d,%d\n",
            &depth,
            &d.readFileNum, &d.readPos,
            &d.writeFileNum, &d.writePos)
        //...
        atomic.StoreInt64(&d.depth, depth)
        d.nextReadFileNum = d.readFileNum
        d.nextReadPos = d.readPos
        return nil
    }

    消息數據

    寫入一條數據

    ioLoop 中發現有數據寫入時,會調用writeOne方法,把消息保存到文件內

            select {
            // ...
            case dataWrite := <-d.writeChan:
                count++
                d.writeResponseChan <- d.writeOne(dataWrite)
            // ...
    func (d *diskQueue) writeOne(data []byte) error {
        var err error
    
        if d.writeFile == nil {
            curFileName := d.fileName(d.writeFileNum)
            d.writeFile, err = os.OpenFile(curFileName, os.O_RDWR|os.O_CREATE, 0600)
            // ...
            if d.writePos > 0 {
                _, err = d.writeFile.Seek(d.writePos, 0)
                // ...
            }
        }
    
        dataLen := int32(len(data))
        // 判斷消息的長度是否合法
        if dataLen < d.minMsgSize || dataLen > d.maxMsgSize {
            return fmt.Errorf("invalid message write size (%d) maxMsgSize=%d", dataLen, d.maxMsgSize)
        }
        d.writeBuf.Reset()
        // 寫入4字節的消息長度,以大端序保存
        err = binary.Write(&d.writeBuf, binary.BigEndian, dataLen)
        if err != nil {
            return err
        }
        // 寫入消息
        _, err = d.writeBuf.Write(data)
        if err != nil {
            return err
        }
    
        // 寫入到文件
        _, err = d.writeFile.Write(d.writeBuf.Bytes())
        // ...
        // 計算寫入位置,消息數量加1
        totalBytes := int64(4 + dataLen)
        d.writePos += totalBytes
        atomic.AddInt64(&d.depth, 1)
        // 如果寫入位置大於 單個文件的最大限制, 則持久化文件到硬盤
        if d.writePos > d.maxBytesPerFile {
            d.writeFileNum++
            d.writePos = 0
    
            // sync every time we start writing to a new file
            err = d.sync()
            // ...
        }
        return err
    }
    

    寫入完消息后,會判斷當前的文件大小是否已經已於maxBytesPerFile如果大,就持久化文件到硬盤,然後重新打開一個新編號文件,進行寫入。

    什麼時候持久化文件到硬盤

    調用sync()方法會持久化文件到硬盤,然後重新打開一個新編號文件,進行寫入。
    有幾個地方調用會調用這個方法:

    • 一個寫入文件的條數達到了syncEvery的值時,也就是初始化時設置的最大的條數。會調用sync()
    • syncTimeout 初始化時設置的同步時間間隔,如果這個時間間隔到了,並且寫入的文件條數>0的時候,會調用sync()
    • 還有就是上面說過的writeOne方法,寫入完消息后,會判斷當前的文件大小是否已經已於maxBytesPerFile如果大,會調用sync()
    • 當讀取文件時,把整個文件讀取完時,會刪除這個文件並且會把needSync 設置為true,ioLoop 會調用sync()
    • 還有就是Close的時候,會調用sync()
    func (d *diskQueue) sync() error {
        if d.writeFile != nil {
            // 把數據 flash到硬盤,關閉文件並設置為 nil
            err := d.writeFile.Sync()
            if err != nil {
                d.writeFile.Close()
                d.writeFile = nil
                return err
            }
        }
        // 保存元數據信息
        err := d.persistMetaData()
        // ...
        d.needSync = false
        return nil
    }

    讀取一條數據

    元數據保存着 讀取文件的編號 readFileNum 和讀取數據的位置 readPos
    並且diskQueue暴露出了一個方法來,通過channel來讀取數據

    func (d *diskQueue) ReadChan() chan []byte {
        return d.readChan
    }

    ioLoop里,當發現讀取位置小於寫入位置 或者讀文件編號小於寫文件編號,並且下一個讀取位置等於當前位置時才會讀取一條數據,然後放在一個外部全局變量 dataRead 里,並把 讀取的channel 賦值監聽 r = d.readChan,當外部有人讀取了消息,則進行moveForward操作

    func (d *diskQueue) ioLoop() {
        var dataRead []byte
        var err error
        var count int64
        var r chan []byte
        for {
            // ...
            if (d.readFileNum < d.writeFileNum) || (d.readPos < d.writePos) {
                if d.nextReadPos == d.readPos {
                    dataRead, err = d.readOne()
                    if err != nil {
                        d.handleReadError()
                        continue
                    }
                }
                r = d.readChan
            } else {
                r = nil
            }
    
            select {
            // ...
            case r <- dataRead:
                count++
                // moveForward sets needSync flag if a file is removed
                d.moveForward()
            // ...
            }
        }
    
    // ...
    }
    

    readOne 從文件里讀取一條消息,4個bit的大小,然後讀取具體的消息。如果讀取位置大於最大文件限制,則close。在moveForward里會進行刪除操作

    func (d *diskQueue) readOne() ([]byte, error) {
        var err error
        var msgSize int32
        // 如果readFile是nil,打開一個新的
        if d.readFile == nil {
            curFileName := d.fileName(d.readFileNum)
            d.readFile, err = os.OpenFile(curFileName, os.O_RDONLY, 0600)
            // ...
            d.reader = bufio.NewReader(d.readFile)
        }
        err = binary.Read(d.reader, binary.BigEndian, &msgSize)
        // ...
        readBuf := make([]byte, msgSize)
        _, err = io.ReadFull(d.reader, readBuf)
        totalBytes := int64(4 + msgSize)
        // ...
        d.nextReadPos = d.readPos + totalBytes
        d.nextReadFileNum = d.readFileNum
        // 如果讀取位置大於最大文件限制,則close。在moveForward里會進行刪除操作
        if d.nextReadPos > d.maxBytesPerFile {
            if d.readFile != nil {
                d.readFile.Close()
                d.readFile = nil
            }
            d.nextReadFileNum++
            d.nextReadPos = 0
        }
        return readBuf, nil
    }
    

    moveForward方法會查看讀取的編號,如果發現下一個編號 和當前的編號不同時,則刪除舊的文件。

    func (d *diskQueue) moveForward() {
        oldReadFileNum := d.readFileNum
        d.readFileNum = d.nextReadFileNum
        d.readPos = d.nextReadPos
        depth := atomic.AddInt64(&d.depth, -1)
    
        // see if we need to clean up the old file
        if oldReadFileNum != d.nextReadFileNum {
            // sync every time we start reading from a new file
            d.needSync = true
    
            fn := d.fileName(oldReadFileNum)
            err := os.Remove(fn)
            // ...
        }
        d.checkTailCorruption(depth)
    

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    ※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    ※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • JS中的相等性判斷===, ==, Object.is()

    JS中的相等性判斷===, ==, Object.is()

    相信剛接觸JS的人都會被他的想等性判斷給整糊塗,看看下面代碼,你能答對幾個?

    NaN === NaN // false
    NaN == NaN // false
    Object.is(NaN, NaN) // true
    0 == false  // true
    1 == true   // true
    Number(null) === 0  // true
    null == 0   // false

    Javascript提供了三種不同的值比較操作,分別是嚴格相等、寬鬆相等、以及Object.is,今天查完資料后做了一下總結,希望下面的內容能夠對大家有所幫助,如果哪些地方有誤的話,也歡迎大家指正。

    1. 嚴格相等 x === y判斷邏輯

    1. 如果x的數據類型和y的數據類型不相同,返回false;
    2. 如果x是Number類型
      • x是NaN,返回false
      • y是NaN,返回false
      • x的值和y的值相等,返回true
      • x是+0,y是-0,返回true
      • x是-0,y是+0,返回true
      • 否則返回false
    3. 其他類型參照SameValueNonNumber(x, y)
      • 斷言:x,y不是Number類型;
      • 斷言: x,y的數據類型相同;
      • x是undefined, y是undefined return true;
      • x是null, y是null,return true;
      • x是字符串類型,當且僅當x,y字符序列完全相同時(長度相同,每個位置上的字符也相同)返回true, 否則返回false;
      • 如果x是布爾類型,當x,y都為true或者都為false時返回true,否則返回false;
      • 如果x是symbol類型,當x,y是相同的symbol值,返回true,否則返回false;
      • 如果x,y是同一個對象值,返回true,否則返回false;
    NaN === NaN // false
    undefined === undefined // true
    null === null   // true
    undefined === null  // false

    2. 寬鬆相等 x == y

    1. 如果x,y的類型相同,返回x===y的結果;
    2. 如果x是null, y是undefined, 返回true;
    3. 如果x是undefined, y是null, 返回true;
    4. 如果x是數值,y是字符串, 返回x == ToNumber(y);
    5. 如果x是字符串,y是數值, 返回ToNumber(x) == y;
    6. 如果x是布爾類型, 返回ToNumber(x)==y 的結果;
    7. 如果y是布爾類型,返回 x==ToNumber(y) 的結果;
    8. 如果x是String或Number或Symbol中的一種並且Type(y)是Object,返回 x==ToPrimitive(y) 的結果
    9. 如果Type(x)是Object並且Type(y)是String或Number或Symbol中的一種,返回 ToPrimitive(x)==y 的結果
    10. 其他返回false
    12 == '0xc' // true, 0xc是16進制
    12 == '12'  // true
    12 == '12c' // false, 說明ToNumber轉換是用的Number()方法

    注意:

    Number(null) === 0
    但是
    null == 0 // false, 

    2.1 ToNumber將一個值轉換為數值類型

    詳情參考

    1. 如果是boolean類型, true返回1,false返回0;
    2. 如果是數值,只是簡單的傳入返回;
    3. 如果是null,返回0
    4. 如果是undefined, 返回NaN;
    5. 如果是字符串,字符串如果只包含数字,則將其轉換成十進制數;如果是有效的浮點格式,將其轉換成對應的浮點數值;如果是二進制或十六進制將其轉換成對應的十進制數值;
    6. 如果是對象,調用對象的valueOf()方法,然後依照前面規則轉換,如果valueOf返回值是NaN,則調用toString()方法,再依照前面的規則轉換返回的字符串

    2.2 ToPrimitive

    toPrimitive(A)通過嘗試調用 A 的A.toString() 和 A.valueOf() 方法,將參數 A 轉換為原始值(Primitive);
    JS中原始類型有:Number、String、Boolean、Null、Undefined;

    不同類型對象的valueOf()方法的返回值:

    對象 返回值
    Array 返回數組對象本身。
    Boolean 布爾值
    Date 存儲的時間是從 1970 年 1 月 1 日午夜開始計的毫秒數 UTC
    Function 函數本身
    Number 数字值
    Object 對象本身。這是默認情況, 可以覆蓋自定義對象的valueOf方法
    String 字符串值
    // Array:返回數組對象本身
    var array = ["ABC", true, 12, -5];
    console.log(array.valueOf() === array);   // true
    
    // Date:當前時間距1970年1月1日午夜的毫秒數
    var date = new Date(2013, 7, 18, 23, 11, 59, 230);
    console.log(date.valueOf());   // 1376838719230
    
    // Number:返回数字值
    var num =  15.26540;
    console.log(num.valueOf());   // 15.2654
    
    // 布爾:返回布爾值true或false
    var bool = true;
    console.log(bool.valueOf() === bool);   // true
    
    // new一個Boolean對象
    var newBool = new Boolean(true);
    // valueOf()返回的是true,兩者的值相等
    console.log(newBool.valueOf() == newBool);   // true
    // 但是不全等,兩者類型不相等,前者是boolean類型,後者是object類型
    console.log(newBool.valueOf() === newBool);   // false
    
    // Function:返回函數本身
    function foo(){}
    console.log( foo.valueOf() === foo );   // true
    var foo2 =  new Function("x", "y", "return x + y;");
    console.log( foo2.valueOf() );
    /*
    ƒ anonymous(x,y
    ) {
    return x + y;
    }
    */
    
    // Object:返回對象本身
    var obj = {name: "張三", age: 18};
    console.log( obj.valueOf() === obj );   // true
    
    // String:返回字符串值
    var str = "http://www.xyz.com";
    console.log( str.valueOf() === str );   // true
    
    // new一個字符串對象
    var str2 = new String("http://www.xyz.com");
    // 兩者的值相等,但不全等,因為類型不同,前者為string類型,後者為object類型
    console.log( str2.valueOf() === str2 );   // false

    3.同值相等

    同值相等由 Object.is 方法判斷:

    • 兩個值都是 undefined
    • 兩個值都是 null
    • 兩個值都是 true 或者都是 false
    • 兩個值是由相同個數的字符按照相同的順序組成的字符串
    • 兩個值指向同一個對象
    • 兩個值都是数字並且
      • 都是正零 +0,
      • 或者都是負零 -0,
      • 或者都是 NaN
      • 都是除零和 NaN 外的其它同一個数字
    Object.is('foo', 'foo');     // true
    Object.is(window, window);   // true
    
    Object.is('foo', 'bar');     // false
    Object.is([], []);           // false
    
    var foo = { a: 1 };
    var bar = { a: 1 };
    Object.is(foo, foo);         // true
    Object.is(foo, bar);         // false
    
    Object.is(null, null);       // true
    
    Object.is(true, 'true')     // false
    
    // 特例
    Object.is(0, -0);            // false
    Object.is(0, +0);            // true
    Object.is(-0, -0);           // true
    Object.is(NaN, 0/0);         // true

    4.零值相等

    與同值相等類似,不過會認為 +0 與 -0 相等。

    小結

    • === 不做類型轉換,當兩邊的數類型不相同時,直接返回false;當前類型相同且都是數值類型的時候,有一個是NaN,那麼結果就是false, 另外 +0 === -0
    • ==運算符,當兩邊操作數類不相同時會做隱式轉換,然後才進行比較,這樣的話就會出現 false == 0, ” == false 等現象, 但是Object.is不會做這種轉換

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

    ※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置,台北網頁設計,各種案例分享

  • 實現 sqrt(x):二分查找法和牛頓法

    實現 sqrt(x):二分查找法和牛頓法

    最近忙裡偷閑,每天刷一道 LeetCode 的簡單題保持手感,發現簡單題雖然很容易 AC,但若去了解其所有的解法,也可學習到不少新的知識點,擴展知識的廣度。

    創作本文的思路來源於:

    簡述題目大意(不想跳轉鏈接,可以看這裏):給定一個非負整數 x,要求計算並返回 x 的平方根(取整)。例如,輸入 4,則輸出 2;輸入 8,則輸出 2(8 的平方根是 2.82842……,由於返回類型是整數,因此小數部分被捨去)。即給定一個 \(x\),我們要計算出 \(\lfloor \sqrt{x} \rfloor\)。

    最簡單最直覺的方法自然是從 0 開始遍歷,直到找到第一個其平方值大於 \(x\) 的數 \(n\),則 \(n-1\) 即是答案。對於任意的 \(x\),其取整后平方根一定在 \([0, x]\) 區間上,代碼如下:

    int sqrt(int x)
    {
        if (x == 0)
            return x;
        int ans = 1;
        while (ans <= x / ans)
            ans++;
        return ans - 1;
    }

    這裏需要注意的有兩點:

    1. 第 6 行代碼中,while 的判斷條件可以避免溢出。很大概率上,你可能會寫成 while (ans * ans <= x),這更自然、更直觀,但當 ans 的值很大時,ans * ans 的結果可能會超過 int 類型的最大表示範圍。舉個例子,比如我們要計算 \(x\) 的取整平方根(其值為 \(n\),即 \(\lfloor \sqrt{x} \rfloor = n\)),算法會將 ans 遍歷到第一個平方超過 \(x\) 的值,即 \(n+1\) 后停止。如果 \(x\) 的值就是 int 類型能夠表示的最大值,那麼當 ans 遍歷到 \(n+1\) 時,計算 ans * ans 的結果就超出了 int 類型的表示範圍。
    2. 由於在 while 的循環判斷中,我們用除法代替了乘法,因此 ans 便不能再從 0 開始遍歷(否則會導致除零錯誤)。為此,我們可以在算法開始單獨處理 \(x = 0\) 的情況,然後讓 ans 從 1 開始遍歷。

    作為一道簡單題,這種暴力樸素的算法自然是可以 AC 的。但其效率極低(需要遍歷 \(O(\sqrt{n})\) 次),在 LeetCode 上的時間效率只能快過約 5% 的用戶,使用 C++ 語言的運行時間平均要 90ms 以上。因此,本文提供了兩種更加高效的算法:二分查找法和牛頓法。

    1. 二分查找法

    如果你在暴力求解的基礎上繼續思考,很大概率會想到用二分搜索求解。

    沒錯,思考暴力求解的策略,我們在區間 \([0, x]\) 上搜索解,而搜索區間 \([0, x]\) 天然是有序的,自然可以用二分搜索代替線性搜索,以大大提高搜索效率。

    更進一步的,我們還可以縮小我們的搜索區間。直覺告訴我們,對於一個非負整數 \(x\),其 \(\sqrt{x}\) 應該不會大於 \(x / 2\)(例如,\(\sqrt{25} = 5\),小於 \(25 / 2 = 12.5\))。我們可以證明:

    \[ \begin{aligned} &\text{設 } y = \frac{x}{2} – \sqrt{x},\text{ 則 } y^\prime = \frac{1}{2} – \frac{1}{2\sqrt{x}}, \\[2ex] &\text{令 } y^\prime = 0, \text{ 可得駐點 } x = 1, \\[2ex] &\text{當 } x > 1 \text{ 時}, y^\prime > 0, \text{ 即當 } x > 1 \text{ 時 }, y = \frac{x}{2} – \sqrt{x} \text{ 的值單調遞增}, \\[2ex] &\text{可推出, 當 } x > 1 \text{ 時}, \lfloor \frac{x}{2} \rfloor – \lfloor \sqrt{x} \rfloor \text{ 的值單調遞增}, \\[2ex] &\text{又因為當 } x = 2 \text{ 時}, \lfloor \frac{x}{2} \rfloor – \lfloor \sqrt{x} \rfloor = 0, \\[2ex] &\text{所以當 } x \geq 2 \text{ 時}, \lfloor \frac{x}{2} \rfloor – \lfloor \sqrt{x} \rfloor \geq 0, \text{ 即 } x \geq 2 \text{ 時},\lfloor \frac{x}{2} \rfloor \geq \lfloor \sqrt{x} \rfloor &\text{(證畢)} \end{aligned} \]

    通過證明我們可以得知,當所求的 \(x\) 大於等於 \(2\) 時,sqrt(x) 的搜索空間為 \([1, x / 2]\),對於 \(x < 2\) 的情況,我們只要特殊處理即可(這裏我們也可以得到結論:當 \(x \geq 1\) 時,\(\lfloor \frac{x}{2} \rfloor + 1 \geq \lfloor \sqrt{x} \rfloor\),然後單獨處理 \(x < 1\) 的情況)。代碼:

    int sqrt(int x)
    {
        if (x < 2)  // 處理特殊情況
            return x;
        
        int left = 1, right = x / 2;
        while (left <= right) {
            # 避免溢出,相當於 mid = (left + right) / 2
            int mid = left + ((right - left) >> 1);
            if (mid == x / mid)
                return mid;
            else if (mid > x / mid)
                right = mid - 1;
            else
                left = mid + 1;
        }
        return right;
    }

    在這裏解釋一下最後的返回值為什麼是 right。對於二分查找,其搜索空間會不斷收縮到 left == right(關於二分查找,這裏不多贅述,自行手動模擬即可),此時 mid、left和 right 三者的值相等(mid = (left + right) / 2)。根據二分查找的搜索範圍的收縮條件可知,left(或 mid)左側的值都小於等於 \(\lfloor \sqrt{x} \rfloor\),right(或 mid)右側的值都大於 \(\lfloor \sqrt{x} \rfloor\),此時(while 的最後一次循環),判斷 mid 的平方與 x 的大小,有三種情況:

    1. mid == x / mid。則在循環內直接返回 mid 的值。
    2. mid > x / mid。這種情況下,因為 mid 左側的值都小於等於 \(\lfloor \sqrt{x} \rfloor\),而 mid 的值大於 \(x\),則 mid-1 即是答案。而按照分支條件,執行 right = mid - 1,可知 right 的值正是應返回的值。此時,循環結束,應返回 right。
    3. mid <= x / mid。這種情況下,mid、left 和 right 正是計算答案(右邊的值都大於 \(\lfloor \sqrt{x} \rfloor\))。按照分支條件,執行 left = mid + 1,循環結束。此時,mid 和 right 的值為計算結果。

    綜合上面三點可知,如果 while 循環結束,則 right 保存的值一定是計算結果。

    和之前的暴力法相比,使用二分查找的思想求解 sqrt(x),只需要循環遍歷 \(O(\lg{\frac{x}{2}})\) 次;空間複雜度為 \(O(1)\)。

    2. 牛頓—拉弗森迭代法

    牛頓—拉弗森迭代法(簡稱牛頓法)使用以直代曲的思想,是一種求解函數的方法,不僅僅適用於求解開方計算。當然使用牛頓法求解函數也存在很多坑,但對於求解開方而言,牛頓法是安全的。關於這一方法,你需要掌握一定的高等數學知識,想了解詳細的內容,可以參考鏈接:

    簡單的理解,可以參考圖片:

    圖片來源:

    給定任意一個非負整數 \(n\),我們想要找到一個 \(x = \lfloor \sqrt{n} \rfloor\),這相當於我們要計算函數 \(f(x) = x^2 – n\) 的根。我們首先需要先給出一個猜測值 \(x_0\),不妨令 \(x_0 = \frac{x}{2} + 1\)(證明見第一小節),然後在 \(f(x_0)\) 處作函數的切線,切線與 \(x\) 軸的交點,即為一次迭代后的值 \(x_1\)。若 \(x_1\) 不是要得到的結果,則繼續迭代,在 \(f(x_1)\) 處作函數的切線,切線與 \(x\) 軸的交點,即為第二次迭代后的值 \(x_2\)。以此類推,直到得到 \(x_n = \lfloor \sqrt{n} \rfloor\)。

    現在我們來推導迭代式。對於 \(x_i\),其函數值為 \(f(x_i)\),則對於點 \((x_i, f(x_i))\),可得其切線方程:

    \[ \begin{align} &y – f(x_i) = f(x_i)^\prime(x – x_i) \\[2ex] \implies &y – (x_i^2 – n) = 2x_i(x – x_i) \\[2ex] \implies &y + x_i^2 + n = 2x_ix \end{align} \]

    又因為 \(x_{i+1}\) 為切線與 \(x\) 軸的交點,所以令 \(y=0\),可得:

    \[ x_{i+1} = (x_i + n / x_i) / 2 \]

    現在,我們就可以根據迭代式編寫代碼了:

    int sqrt(int x)
    {
        // 避免除零錯誤,單獨處理 x = 0 的情況
        if (x == 0)
            return x;
        int t = x / 2 + 1;
        while (t > x / t)
            t = (t + x / t) / 2;
        return t;
    }

    為了確保算法是正確的,我們還需要一些額外的證明。首先,證明迭代式是單調遞減的:

    \[ x_{i+1} – x_i = \left\lfloor \frac{1}{2} (x_i + \frac{n}{x_i}) \right\rfloor – x_i = \left\lfloor \frac{1}{2} (\frac{n}{x_i} – x_i) \right\rfloor \]

    可知,在區間 \([\sqrt{x}, +\infty)\) 上,\(x_{i+1} – x_i < 0\)。

    然後,我們還要證明迭代式是可以收斂到 \(\lfloor \sqrt{n} \rfloor\) 的:

    \[ x_{i+1} = \left\lfloor \frac{1}{2} \left( x_i + \left\lfloor \frac{n}{x_i} \right\rfloor \right) \right\rfloor = \left \lfloor \frac{1}{2} (x_i + \frac{n}{x_i}) \right \rfloor \geq \left \lfloor \frac{1}{2} \times 2 \times \sqrt{x_i \cdot \frac{n}{x_i}} \right \rfloor = \lfloor \sqrt{n} \rfloor \]

    因此,當 while 循環結束時,我們可以得到正確的答案。

    關於牛頓法求 sqrt(x) 的時間複雜度,筆者目前也沒有搞清楚,有了解的童鞋歡迎交流~。不過通過查詢資料,以及實際測試,可知牛頓法的時間效率優於二分搜索法。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※南投搬家前需注意的眉眉角角,別等搬了再說!

  • esp8266 smartconfig-智能配網分析和使用及注意事項

    esp8266 smartconfig-智能配網分析和使用及注意事項

    基本知識:

      樂鑫⾃主研發的 ESP-TOUCH 協議采⽤的是 Smart Config(智能配置)技術,幫助⽤戶將 采⽤ ESP8266EX 和 ESP32 的設備(以下簡稱“設備”)連接⾄ Wi-Fi ⽹絡。⽤戶只需在⼿ 機上進⾏簡單操作即可實現智能配置。整個過程如下圖所示:

       

     由於設備在⼀開始並未連接⾄⽹絡,ESP-TOUCH App ⽆法直接向設備發送信息。通過 ESP-TOUCH 通信協議,具備 Wi-Fi ⽹絡接⼊能⼒的設備(例如智能⼿機)就可以向接 ⼊點 (AP) 發送⼀系列 UDP 包,其中每⼀包的⻓度(即 Length 字段)都按照 ESPTOUCH 通信協議進⾏編碼,SSID 和密碼就包含在 Length 字段中,隨後設備便可以獲 得並解析出所需的信息。數據包結構如圖 1-2 所示:

     

    源碼和app:

    源碼的路徑如下所示:

    源碼路徑:

    根據你的所需進行修改或直接安裝,筆者的截圖如下所示:

     

    操作步驟:

    準備⼀台⽀持 ESP-TOUCH 的設備,開啟配置功能;
    將⼿機連接⾄路由器;
    打開安裝在⼿機上的 ESP-TOUCH App;
    在 App 界⾯輸⼊路由器的 SSID 和密碼(若路由不加密則密碼為空),開始連接。

    注意事項:

    • 設備與⼿機建⽴鏈路所需的時間和兩者之間的距離有關,若距離很近,僅需數秒即可完成。

    • 在開啟設備端 ESP-TOUCH 智能配置功能之前,請確保路由器已經開啟。由於開啟此功能后,設備會先 掃描周圍的 AP 信息,如果路由器沒有開啟,則⽆法獲取周圍的 AP 列表。

    • ESP-TOUCH App 端發送序列有超時限制,即如果沒有在規定的時間配置上路由,App 會返回配置失敗 信息(參考 App 源碼)。設備端也會有超時限制,從設備鎖定通道開始計時到獲取 SSID 和密碼信息, 如果達到超時限制但並未獲取到 SSID 和密碼信息,設備會⾃動重新開始⼀次 Smart Config。⽤戶可以通 過 esptouch_set_timeout(uint8 time_s) 或 esp_smartconfig_set_timeout(uint8 time_s) 設置超時時 間。

    • Smart Config 過程中設備需要開啟 Sniffer 模式,所以在 ESP8266 上 Station 和 soft-AP 模式都是關閉 的,不要調⽤其他相關 API。ESP32 不受此限制,可以同時開啟 Sniffer 和 Station 模式。

    • 配置結束后,發送端獲取設備的 IP,設備端也會返回發送端的 IP,如果⽤戶需要⾃定義發送端和設備端 的信息交互,可以使⽤此 IP 信息進⾏局域⽹通信。 • 如果路由器設置了“AP 隔離”,會出現設備連接路由器成功,但 App 未收到連接成功的提示的現象。

    • ⽀持⼿機同時配置多個設備到同⼀路由器上,配置時 App 端選擇接收多個返回結果。

    • ESP8266 和 ESP32 不⽀持 5G 和 11AC 模式,因此不⽀持 Smart Config 連接 5G 或 11AC 模式的路由 器。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 湖北禁用廚餘垃圾餵豬 違規可罰5萬元

    摘錄自2020年1月7日星島日報報導

    中國非洲豬瘟疫情持續,湖北省政府立法,直接使用廚餘垃圾餵豬,將罰款最高5萬元人民幣,將於3月1日起實施。

    政府指,為加強廚餘垃圾管理,保障食品安全,促進廚餘垃圾資源化利用和無害化處理,所以實施《湖北省餐廚垃圾管理辦法》規定。根據規定,在廚餘垃圾投放、收集、運輸、處置過程中,不得實施下列行為:將廚餘垃圾與其他生活垃圾混合投放;將廚餘垃圾排入污水排水管道、雨水管道等市政公共設施以及河道、湖泊、水庫等公共水域;隨意傾倒、拋撒廚餘垃圾等。

    另外亦規定直接使用廚餘垃圾餵豬的,由縣級以上政府農業農村主管部門責令停止違法行為,可以處5000元以上2萬元以下罰款;情節嚴重的,處2萬元以上5萬元以下罰款。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

  • 南非八獅遭毒殺 爪及鼻子被割下疑圖製藥

    摘錄自2020年1月7日星島日報報導

    南非近日有八隻獅子被人非法獵殺,另有仍在母獅體內的胎兒亦因此胎死腹中。部分獅子的腳掌及鼻子亦被人割去,懷疑是用作製作藥物,警方目前仍在調查案件。

    事發在西北省斯瓦圖根斯(Swartruggens)附近一個農場,51歲的負責人布洛姆上周五(3日)在場內視察時,發現兩隻雄獅及六隻母獅被獵殺,獅子的爪以及鼻子亦被人割下。其中兩隻母獅各懷有三隻幼獅,但全都胎死腹中。另外,有兩隻幼獅剛在案發前出生,因喝下中毒母獅的奶水而死亡,事發後,該獅子家族僅剩一隻雌性幼獅「Yoda」,由於牠由布洛姆以奶樽餵哺而沒有喝下有毒的奶水,因此逃過一劫。

    布洛姆在現場找到四組腳印,警方目前正調查案件,暫無人被捕。在南非,由於獅子及老虎等動物的部分身體部位可製成藥材,因此盜獵事件不時發生。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    ※網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

    ※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

    ※想知道最厲害的台北網頁設計公司推薦、台中網頁設計公司推薦專業設計師”嚨底家”!!

  • 澳洲野火 煙雲飄1萬2000多公里觸及巴西

    摘錄自2020年1月8日中央社報導

    澳洲野火延燒,巴西國家太空署(National Institute for Space Research, INPE)旗下的遙測部門今天推文表示,野火煙霾今天觸及巴西。

    遙測部門(Department of Remote Sensing)指著衛星影像說,澳洲大火煙霾一路飄洋過海來到巴西最南端的南大河州(Rio Grande do Sul)。

    民間氣象公司MetSul也在推特發文說煙雲抵達南大河州首府快樂港(Porto Alegre),但強調:「從澳洲飄來的煙霾幾乎難以察覺,即使衛星圖顯示大快樂港地區上空被煙霧籠罩。」

    智利氣象局昨(6日)表示,智利和阿根廷能看見野火產生的煙霾。這意味著煙雲飄了1萬2000多公里到達南美洲。不過智利氣象局表示,飄來的煙霾並不會對南美居民的健康產生負面影響。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    ※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

    ※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

    ※評比南投搬家公司費用收費行情懶人包大公開

  • 闖民居尋水源造成危險 澳洲擬射殺1萬駱駝

    摘錄自2020年1月7日星島日報報導

    澳洲山火持續多個月,令當地持續乾旱。由於有駱駝闖入居民的家園以尋找水源,南澳州原住民地區決定,明日起射殺1萬隻野生駱駝,並已獲當局批准。

    根據外國媒體報道,環境部門會派出數架直升機射殺西北部約1萬隻野生駱駝,估計射殺行動維持五天,以控制其數量。報道又指,野生駱駝近期大量繁殖,數量難以控制,加上天氣乾旱,駱駝為了尋找水源,近年逐漸散布到近岸區域,亦不時闖入居民的家園,並破壞當地基建,威脅民眾安全。

    澳洲現時約有120萬隻野生駱駝,每年排放相當於1噸溫室氣體,若不加以管制,駱駝數量將每九年翻一倍。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※台北網頁設計公司這麼多,該如何挑選?? 網頁設計報價省錢懶人包"嚨底家"

    ※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    ※南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

  • 核災後九年 福島縣計畫轉型為再生能源樞紐

    環境資訊中心綜合外電;姜唯 編譯;林大利 審校

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※想知道網站建置、網站改版該如何進行嗎?將由專業工程師為您規劃客製化網頁設計及後台網頁設計

    ※不管是台北網頁設計公司、台中網頁設計公司,全省皆有專員為您服務

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※帶您來看台北網站建置,台北網頁設計,各種案例分享

  • 新疆哈密市爆牛O型口蹄疫 同批64牛遭一併撲殺

    摘錄自2020年1月7日星島日報報導

    農業農村部新聞辦公室今(7日)發佈,接獲到中國動物疫病預防控制中心報告,指維吾爾自治區哈密煙墩動植物聯合檢查站查獲,一批由河北省保定市曲陽縣調往新疆的牛,部份牛隻出現疑似口蹄疫症狀。

    經國家參考實驗室確診為O型口蹄疫疫情。該批牛共64隻,其中兩隻發病。在疫情發生後,當地按照有關預案和防治技術規範要求,切實做好疫情處置工作,發病的牛隻與同群牛已被撲殺及無害化處理,該批牛的調出地亦已展開全面排查和緊急監測工作。

    本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※網頁設計公司推薦更多不同的設計風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益

    ※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

    ※南投搬家前需注意的眉眉角角,別等搬了再說!