標籤: 網頁設計公司推薦

  • 特斯拉 5 年內推市占 100 萬台;大眾車款 Model 3 將搶市

    電動車大廠特斯拉 (Tesla) 最近 1 份報告指出,預計在 5 年內將特斯拉電動車市占量推上 100 萬台。   《華爾街日報》報導,特斯拉首席技術長 JB Straubel 15 日 在一場會議中提出這項市占率預測;此外,為迎合大眾市場,特斯拉即將推出價值 3.5 萬美元的 Model 3 型電動車,則預計成為銷售量成長最佳的動力來源。 Straubel 另表示,特斯拉 Model 3 型電動車預計在 2017 年問世。   Global Equities Research 的共同創辦人兼分析師 Trip Chowdhry 表示,只要特斯拉的超級電池工廠 Gigafactory 能如期完成,野心勃勃的百萬車輛計畫成功機率就相當高。  

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

  • 【從今天開始好好學數據結構01】數組

    【從今天開始好好學數據結構01】數組

    面試的時候,常常會問數組和鏈表的區別,很多人都回答說,“鏈表適合插入、刪除,時間複雜度O(1);數組適合查找,查找時間複雜度為O(1)”。實際上,這種表述是不準確的。數組是適合查找操作,但是查找的時間複雜度並不為O(1)。即便是排好序的數組,你用二分查找,時間複雜度也是O(logn)。所以,正確的表述應該是,數組支持隨機訪問,根據下標隨機訪問的時間複雜度為O(1)。

    每一種編程語言中,基本都會有數組這種數據類型。不過,它不僅僅是一種編程語言中的數據類型,還是一種最基礎的數據結構。儘管數組看起來非常基礎、簡單,但是我估計很多人都並沒有理解這個基礎數據結構的精髓。在大部分編程語言中,數組都是從0開始編號的,但你是否下意識地想過,為什麼數組要從0開始編號,而不是從1開始呢? 從1開始不是更符合人類的思維習慣嗎?帶着這個問題來學習接下來的內容,帶着問題去學習往往效果會更好!!!

    什麼是數組?我估計你心中已經有了答案。不過,我還是想用專業的話來給你做下解釋。數組(Array)是一種線性表數據結構。它用一組連續的內存空間,來存儲一組具有相同類型的數據。這個定義里有幾個關鍵詞,理解了這幾個關鍵詞,我想你就能徹底掌握數組的概念了。下面就從我的角度分別給你“點撥”一下。

    第一是線性表(Linear List)。顧名思義,線性表就是數據排成像一條線一樣的結構。每個線性表上的數據最多只有前和后兩個方向。其實除了數組,鏈表、隊列、棧等也是線性表結構。而與它相對立的概念是非線性表,比如二叉樹、堆、圖等。之所以叫非線性,是因為,在非線性表中,數據之間並不是簡單的前後關係。

    第二個是連續的內存空間和相同類型的數據。正是因為這兩個限制,它才有了一個堪稱“殺手鐧”的特性:“隨機訪問”。但有利就有弊,這兩個限制也讓數組的很多操作變得非常低效,比如要想在數組中刪除、插入一個數據,數組為了保持內存數據的連續性,會導致插入、刪除這兩個操作比較低效,相反的數組查詢則高效

    數組java代碼:

    package array;
    
    /**
     * 1) 數組的插入、刪除、按照下標隨機訪問操作;
     * 2)數組中的數據是int類型的;
     *
     * Author: Zheng
     * modify: xing, Gsealy
     */
    public class Array {
        //定義整型數據data保存數據
        public int data[];
        //定義數組長度
        private int n;
        //定義中實際個數
        private int count;
    
        //構造方法,定義數組大小
        public Array(int capacity){
            this.data = new int[capacity];
            this.n = capacity;
            this.count=0;//一開始一個數都沒有存所以為0
        }
    
        //根據索引,找到數據中的元素並返回
        public int find(int index){
            if (index<0 || index>=count) return -1;
            return data[index];
        }
    
        //插入元素:頭部插入,尾部插入
        public boolean insert(int index, int value){
            //數組中無元素 
    
            //if (index == count && count == 0) {
            //    data[index] = value;
            //    ++count;
            //    return true;
            //}
    
            // 數組空間已滿
            if (count == n) {
                System.out.println("沒有可插入的位置");
                return false;
            }
            // 如果count還沒滿,那麼就可以插入數據到數組中
            // 位置不合法
            if (index < 0||index > count ) {
                System.out.println("位置不合法");
                return false;
            }
            // 位置合法
            for( int i = count; i > index; --i){
                data[i] = data[i - 1];
            }
            data[index] = value;
            ++count;
            return true;
        }
        //根據索引,刪除數組中元素
        public boolean delete(int index){
            if (index<0 || index >=count) return false;
            //從刪除位置開始,將後面的元素向前移動一位
            for (int i=index+1; i<count; ++i){
                data[i-1] = data[i];
            }
            //刪除數組末尾元素  這段代碼不需要也可以
            /*int[] arr = new int[count-1];
            for (int i=0; i<count-1;i++){
                arr[i] = data[i];
            }
            this.data = arr;*/
    
            --count;
            return true;
        }
        public void printAll() {
            for (int i = 0; i < count; ++i) {
                System.out.print(data[i] + " ");
            }
            System.out.println();
        }
    
        public static void main(String[] args) {
            Array array = new Array(5);
            array.printAll();
            array.insert(0, 3);
            array.insert(0, 4);
            array.insert(1, 5);
            array.insert(3, 9);
            array.insert(3, 10);
            //array.insert(3, 11);
            array.printAll();
        }
    }

    GenericArray數組代碼

    public class GenericArray<T> {
        private T[] data;
        private int size;
    
        // 根據傳入容量,構造Array
        public GenericArray(int capacity) {
            data = (T[]) new Object[capacity];
            size = 0;
        }
    
        // 無參構造方法,默認數組容量為10
        public GenericArray() {
            this(10);
        }
    
        // 獲取數組容量
        public int getCapacity() {
            return data.length;
        }
    
        // 獲取當前元素個數
        public int count() {
            return size;
        }
    
        // 判斷數組是否為空
        public boolean isEmpty() {
            return size == 0;
        }
    
        // 修改 index 位置的元素
        public void set(int index, T e) {
            checkIndex(index);
            data[index] = e;
        }
    
        // 獲取對應 index 位置的元素
        public T get(int index) {
            checkIndex(index);
            return data[index];
        }
    
        // 查看數組是否包含元素e
        public boolean contains(T e) {
            for (int i = 0; i < size; i++) {
                if (data[i].equals(e)) {
                    return true;
                }
            }
            return false;
        }
    
        // 獲取對應元素的下標, 未找到,返回 -1
        public int find(T e) {
            for ( int i = 0; i < size; i++) {
                if (data[i].equals(e)) {
                    return i;
                }
            }
            return -1;
        }
    
    
        // 在 index 位置,插入元素e, 時間複雜度 O(m+n)
        public void add(int index, T e) {
            checkIndex(index);
            // 如果當前元素個數等於數組容量,則將數組擴容為原來的2倍
            if (size == data.length) {
                resize(2 * data.length);
            }
    
            for (int i = size - 1; i >= index; i--) {
                data[i + 1] = data[i];
            }
            data[index] = e;
            size++;
        }
    
        // 向數組頭插入元素
        public void addFirst(T e) {
            add(0, e);
        }
    
        // 向數組尾插入元素
        public void addLast(T e) {
            add(size, e);
        }
    
        // 刪除 index 位置的元素,並返回
        public T remove(int index) {
            checkIndexForRemove(index);
    
            T ret = data[index];
            for (int i = index + 1; i < size; i++) {
                data[i - 1] = data[i];
            }
            size --;
            data[size] = null;
    
            // 縮容
            if (size == data.length / 4 && data.length / 2 != 0) {
                resize(data.length / 2);
            }
    
            return ret;
        }
    
        // 刪除第一個元素
        public T removeFirst() {
            return remove(0);
        }
    
        // 刪除末尾元素
        public T removeLast() {
            return remove(size - 1);
        }
    
        // 從數組中刪除指定元素
        public void removeElement(T e) {
            int index = find(e);
            if (index != -1) {
                remove(index);
            }
        }
    
        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append(String.format("Array size = %d, capacity = %d \n", size, data.length));
            builder.append('[');
            for (int i = 0; i < size; i++) {
                builder.append(data[i]);
                if (i != size - 1) {
                    builder.append(", ");
                }
            }
            builder.append(']');
            return builder.toString();
        }
    
    
        // 擴容方法,時間複雜度 O(n)
        private void resize(int capacity) {
            T[] newData = (T[]) new Object[capacity];
    
            for (int i = 0; i < size; i++) {
                newData[i] = data[i];
            }
            data = newData;
        }
    
        private void checkIndex(int index) {
            if (index < 0 || index > size) {
                throw new IllegalArgumentException("Add failed! Require index >=0 and index <= size.");
            }
        }
    
        private void checkIndexForRemove(int index) {
            if(index < 0 || index >= size) {
                throw new IllegalArgumentException("remove failed! Require index >=0 and index < size.");
            }
        }
    }

    到這裏,就回溯最初的問題:

    從數組存儲的內存模型上來看,“下標”最確切的定義應該是“偏移(offset)”。前面也講到,如果用a來表示數組的首地址,a[0]就是偏移為0的位置,也就是首地址,a[k]就表示偏移k個type_size的位置,所以計算a[k]的內存地址只需要用這個公式:

    a[k]_address = base_address + k * type_size

    但是,如果數組從1開始計數,那我們計算數組元素a[k]的內存地址就會變為:

    a[k]_address = base_address + (k-1)*type_size

    對比兩個公式,我們不難發現,從1開始編號,每次隨機訪問數組元素都多了一次減法運算,對於CPU來說,就是多了一次減法指令。那你可以思考一下,類比一下,二維數組的內存尋址公式是怎樣的呢?有興趣的可以在評論區評論出來哦QAQ

    數組作為非常基礎的數據結構,通過下標隨機訪問數組元素又是其非常基礎的編程操作,效率的優化就要盡可能做到極致。所以為了減少一次減法操作,數組選擇了從0開始編號,而不是從1開始。
    不過我認為,上面解釋得再多其實都算不上壓倒性的證明,說數組起始編號非0開始不可。所以我覺得最主要的原因可能是歷史原因。

    關於數組,它可以說是最基礎、最簡單的數據結構了。數組用一塊連續的內存空間,來存儲相同類型的一組數據,最大的特點就是支持隨機訪問,但插入、刪除操作也因此變得比較低效,平均情況時間複雜度為O(n)。在平時的業務開發中,我們可以直接使用編程語言提供的容器類,但是,如果是特別底層的開發,直接使用數組可能會更合適。

    如果本文對你有一點點幫助,那麼請點個讚唄,謝謝~

    最後,若有不足或者不正之處,歡迎指正批評,感激不盡!如果有疑問歡迎留言,絕對第一時間回復!

    歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術,說好了來了就是盆友喔…

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

    【其他文章推薦】

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

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

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

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

  • 募資拆壩有成 烏克蘭濕地迅速恢復

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

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

    【其他文章推薦】

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

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

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

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

  • 印尼雅加達暴雨侵襲 洪災土石流釀21死

    摘錄自2020年1月2日公視報導

    印尼首都雅加達跨年夜遭暴雨侵襲,引發洪水和土石流,目前至少造成21人死亡。

    汽機車被洪水淹得滅頂,搜救人員出動橡皮艇挨家挨戶搜救,印尼首都雅加達跨年夜下起暴雨,引發七年來最嚴重水災,水深達到3公尺高,部分地鐵和火車停駛,一座機場也被迫臨時關閉,不少旅客滯留到早上,印尼總統佐科威表示已經動員12萬名搜救人員,全力救災。

    佐科威說:「許多公共設施被淹沒,包括哈里姆柏達那庫蘇馬機場、芝甘北公路。」此外,雅加達郊區發生土石流災情,一名16歲高中生遭雷擊死亡,當局警告在雨季四月結束前,可能將再發生洪災。

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

    【其他文章推薦】

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

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

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

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

  • 為搶市占 傳 LG Chem 明年起腰斬電池價格

    日廠有意增產搶市佔,車用電池龍頭 LG Chem 祭出激烈手段,據稱 2016 年起該公司電池售價將對半腰斬,藉此擊退對手。   消息人士透露,LG Chem 在中國生產的部分電動車電池,2016 年價格將減半,此舉是為了先發制人退敵,並因應中美兩國電動車需求大增。他說,LG Chem 將銷售更多電池,因此價格下殺不會拖累獲利。該公司表示,定價策略將保持彈性,價格視市況調整,是否降價仍須與客戶討論之後決定。   LG Chem 目前供應電動車電池給全球 30 多家車廠。該公司中國南京工廠將於明年啟用,增產不成問題,LG Chem 預估新廠年度產能為 10 萬顆電動車電池。該公司高層表示, LG Chem 長年研發材料、壓低成本,開始出現成效,明年起大型電池業務可望損益兩平,獲利提升讓他們決定降低售價。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

  • 巴西奧運電動車組出廠 中車長客 100 列全數完工

    為迎接 2016 年奧運,巴西和中國中車長客股份公司簽約,中國將提供 120 輛電動車組和 114 輛地鐵車輛,長客公司已於 19 日順利交付最後 100 列電動車組。   中新社報導,隨著最後一列電動車組(EMU)19 日在長春下線,由中國中車長客股份公司為巴西里約熱內盧製造的 100 列電動車組全部交付,每列 4 輛編組,最高時速可達 100 公里。這些列車屆時將肩負 2016 奧運交通運輸。  

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

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

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

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

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

  • 剖析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 設置為trueioLoop 會調用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)
    

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

    【其他文章推薦】

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

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

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

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

  • 實現 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(關於二分查找,這裏不多贅述,自行手動模擬即可),此時 midleftright 三者的值相等(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。這種情況下,midleftright 正是計算答案(右邊的值都大於 \(\lfloor \sqrt{x} \rfloor\))。按照分支條件,執行 left = mid + 1,循環結束。此時,midright 的值為計算結果。

    綜合上面三點可知,如果 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) 的時間複雜度,筆者目前也沒有搞清楚,有了解的童鞋歡迎交流~。不過通過查詢資料,以及實際測試,可知牛頓法的時間效率優於二分搜索法。

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

    【其他文章推薦】

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

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

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

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

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

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

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

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

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

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

    【其他文章推薦】

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

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

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

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

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

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

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

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

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

    【其他文章推薦】

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

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

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

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