標籤: 網頁設計公司推薦

  • 大規模搜索廣告的端到端一致性實時保障

    大規模搜索廣告的端到端一致性實時保障

    一、背景

    電商平台的搜索廣告數據處理鏈路通常較長,一般會經歷如下過程:

    • 廣告主在後台進行廣告投放;
    • 投放廣告品及關鍵詞數據寫入數據庫;
    • 數據庫中的數據通過全量構建(導入數據倉庫再進行離線批處理)或增量構建(藉助消息隊列和流計算引擎)的方式產出用於構建在線索引的“內容文件”;
    • BuildService基於“內容文件”,構建出在搜索服務檢索時使用的索引。

    下圖是ICBU的廣告系統的買賣家數據處理鏈路:

    右半部分(BP->DB)和offline部分即為廣告投放數據的更新過程。

    複雜的數據處理鏈路結合海量(通常是億級以上)的商品數據,對線上全量商品的投放狀態正確性測試提出巨大挑戰。從數據庫、到離線大規模數據聯表處理、到在線索引構建,鏈路中的任一節點出現異常或數據延遲,都有可能會對廣告主以及平台造成“資損”影響,例如:

    • 廣告主在後台操作取消A商品的廣告投放,但是因為數據鏈路處理延遲,搜索引擎中A的狀態仍處於“推廣中”,導致A能繼續在買家搜索廣告時得到曝光,相應地當“點擊”行為發生時,造成錯誤扣款。
    • 廣告主設定某個產品只限定對某個地域/國家的客戶投放廣告,但是因為搜索引擎的過濾邏輯處理不恰當,導致客戶的廣告品在所有地區都進行廣告投放,同樣會造成錯誤點擊扣款。

    傳統的測試手段,或聚焦於廣告主後台應用的模塊功能測試,或聚焦於搜索引擎的模塊功能測試,對於全鏈路功能的測試缺乏有效和全面的測試手段。而線上的業務監控,則側重於對業務效果指標的監控,如CTR(click through rate,點擊率)、CPC(cost per click,點擊成本)、RPM(revenue per impression,千次瀏覽收益)等。對涉及廣告主切身利益和平台總營收的廣告錯誤投放問題,缺乏有效的發現機制。

    我們期望對在線搜索廣告引擎所有實際曝光的商品,通過反查數據庫中曝光時刻前它的最後狀態,來校驗它在數據庫中的投放狀態與搜索引擎中的狀態的一致性,做到線上廣告錯誤投放問題的實時發現。同時,通過不同的觸發檢測方式,做到數據變更的各個環節的有效覆蓋。

    二、階段成果

    我們藉助日誌流同步服務(TTLog)、海量數據NoSQL存儲系統(Lindorm)、實時業務校驗平台(BCP)、消息隊列(MetaQ)、在線數據實時同步服務(精衛)以及海量日誌實時分析系統(Xflush)實現了ICBU搜索廣告錯誤投放問題的線上實時發現,且覆蓋線上的全部用戶真實曝光流量。同時,通過在數據變更節點增加主動校驗的方式,可以做到在特定場景下(該廣告品尚未被用戶檢索)的線上問題先於用戶發現。

    此外,藉助TTLog+實時計算引擎Blink+阿里雲日誌服務SLS+Xflush的技術體系,實現了線上引擎/算法效果的實時透出。

    下面是ICBU廣告實時質量大盤:

    從八月底開始投入線上使用,目前這套實時系統已經發現了多起線上問題,且幾乎都是直接影響資損和廣告主的利益。

    三、技術實現

    圖一:

    1. 引擎曝光日誌數據處理

    對於電商搜索廣告系統,當一個真實的用戶請求觸達(如圖一中1.1)時,會產生一次實時的廣告曝光,相對應地,搜索引擎的日誌里會寫入一條曝光記錄(如圖一中2)。我們通過日誌流同步服務TTLog對搜索引擎各個服務器節點上的日誌數據進行統一的搜集(如圖一中3),然後藉助數據對賬服務平台BCP對接TTLog中的“流式”數據(如圖一中4),對數據進行清洗、過濾、採樣,然後將待校驗的數據推送到消息隊列服務MetaQ(如圖一中5)。

    2. DB數據處理

    圖二:

    如圖二所示,通常,業務數據庫MySQL針對每個領域對象,只會存儲它當前時刻最新的數據。為了獲取廣告品在引擎中真實曝光的時刻前的最後數據,我們通過精衛監聽數據庫中的每次數據變更,將變更數據“快照”寫入Lindorm(底層是HBase存儲,支持海量數據的隨機讀寫)。

    3. 數據一致性校驗

    在廣告測試服務igps(我們自己的應用)中,我們通過監聽MetaQ的消息變更,拉取MetaQ中待校驗的數據(如圖一中6),解析獲得曝光時每個廣告品在搜索引擎中的狀態,同時獲得其曝光的時刻點。然後基於曝光時刻點,通過查詢Lindorm,獲得廣告品於曝光時刻點前最後在MySQL中的數據狀態(如圖一中7)。然後igps對該次廣告曝光,校驗引擎中的數據狀態和MySQL中的數據狀態的一致性。

    如果數據校驗不一致,則打印出錯誤日誌。最後,藉助海量日誌實時分析系統Xflush(如圖一中8),我們可以做到對錯誤數據的實時聚合統計、可視化展示以及監控報警。

    4. 數據變更節點的主動校驗

    因為線上的實時用戶搜索流量具有一定的隨機性,流量場景的覆蓋程度具有很大的不確定性,作為補充,我們在數據變更節點還增加了主動校驗。

    整個數據鏈路,數據變更有兩個重要節點:

    • MySQL中的數據變更;
    • 引擎索引的切換。

    對於MySQL中的數據變更:我們通過精衛監聽變更,針對單條數據的變更信息,構建出特定的引擎查詢請求串,發起查詢請求(如圖一中1.3)。

    對於引擎索引的切換(主要是全量切換):我們通過離線對歷史(如過去7天)的線上廣告流量進行聚合分析/改寫,得到測試用例請求集合。再監聽線上引擎索引的切換操作。當引擎索引進行全量切換時,我們主動發起對引擎服務的批量請求(如圖一中1.2)。

    上述兩種主動發起的請求,最後都會復用前面搭建的數據一致性校驗系統進行廣告投放狀態的校驗。

    上圖是對廣告投放狀態的實時校驗錯誤監控圖,從圖中我們清晰看到當前時刻,搜索廣告鏈路的數據質量。無論是中美業務DB同步延遲、DB到引擎數據增量處理鏈路的延遲、或者是發布變更導致的邏輯出錯,都會導致錯誤數據曲線的異常上漲。校驗的規則覆蓋了推廣計劃(campaign)、推廣組(adgroup)、客戶狀態(customer)、詞的狀態(keyword)、品的狀態(feed)。校驗的節點覆蓋了曝光和點擊兩個不同的環節。

    5. 引擎及算法的實時質量

    圖三:

    搜索引擎日誌pvlog中蘊含了非常多有價值的信息,利用好這些信息不僅可以做到線上問題的實時發現,還能幫助算法同學感知線上的實時效果提供抓手。如圖三所示,通過實時計算引擎Blink我們對TTLog中的pv信息進行解析和切分,然後將拆分的結果輸出到阿里雲日誌服務SLS中,再對接Xflush進行實時的聚合和可視化展示。

    如上圖所示,上半年我們曾出現過一次線上的資損故障,是搜索應用端構造的搜索廣告引擎SP請求串中缺失了一個參數,導致部分頭部客戶的廣告沒有在指定地域投放,故障從發生到超過10+客戶上報才發現,歷經了10幾個小時。我們通過對SP請求串的實時key值和重要value值進行實時監控,可以快速發現key值或value值缺失的場景。

    此外,不同召回類型、扣費類型、以及扣費價格的分佈,不僅可以監控線上異常狀態的出現,還可以給算法同學做實驗、調參、以及排查線上問題時提供參考。

    四、幾個核心問題

    1. why lindorm?

    最初的實現,我們是通過精衛監聽業務DB的變更寫入另一個新的DB(MySQL),但是性能是一個非常大的瓶頸。我們的數據庫分了5+個物理庫,1000+張分表,單表的平均數據量達到1000+w行,總數據達到千億行。

    后通過存儲的優化和按邏輯進行分表的方式,實現了查詢性能從平均1s到70ms的提升。

    2. why BCP + MetaQ + igps?

    最初我們是想直接使用BCP對數據進行校驗:通過igps封裝lindorm的查詢接口,然後提供hsf接口供在BCP里直接使用。

    但是還是因為性能問題:TTLog的一條message平均包含60+條pv,每個pv可能有5個或更多廣告,每個廣告要查6張表,單條message在BCP校驗需要調用約60x5x6=1800次hsf請求。當我們在BCP中對TTLog的數據進行10%的採樣時,後端服務igps的性能已經出現瓶頸,hsf線程池被打滿,同時7台服務器的cpu平均使用率達到70%以上。

    藉助MetaQ的引入,可以剔除hsf調用的網絡開銷,同時將消息的生產和消費解耦,當流量高峰到達時,igps可以保持自己的消費速率不變,更多的消息可以暫存在隊列里。通過這一優化,我們不僅扛住了10%的採樣,當線上採樣率開到100%時,我們的igps的服務器的平均cpu使用率仍只維持在20%上下,而且metaq中沒有出現消息堆積。

    不過這樣一來,bcp的作用從原來的“採樣、過濾、校驗、報警”,只剩下“採樣、過濾”。無法發揮其通過在線編碼可以快速適應業務變化的作用。

    3. why not all blink?

    其實“BCP + MetaQ + igps”的流程可以被“Blink + SLS”取代,那為什麼不都統一使用Blink呢。

    一方面,目前點擊的校驗由於其流量相對較小的因素,我們目前是直接在BCP里編寫的校驗代碼,不需要走發布流程,比較快捷。而且BCP擁有如“延遲校驗”、“限流控制”等個性化的功能。另一方面,從我們目前使用Blink的體驗來看,實時的處理引擎尚有一些不穩定的因素,尤其是會有不穩定的網絡抖動(可能是數據源和Blink workder跨機房導致)。

    4. SP請求的key值如何拆分?

    在做SP請求串的實時key值監控的時候,遇到了一個小難題:SP的請求串中參數key是動態的,並不是每個key都會在每個串中出現,而且不同的請求串key出現的順序是不一樣的。如何切分使其滿足Xflush的“列值分組”格式要求。

    實現方式是,對每個sp請求串,使用Blink的udtf(自定義表值函數)進行解析,得到每個串的所有key和其對應的value。然後輸出時,按照“validKey={key},validValue={value}”的格式對每個sp請求串拆分成多行輸出。然後通過Xflush可以按照validKey進行分組,並對行數進行統計。

    五、總結及後續規劃

    本文介紹了通過大數據的處理技術做到電商搜索廣告場景下數據端到端一致性問題的實時發現,並且通過“實時發現”結合“數據變更節點的主動校驗”,實現數據全流程的一致性校驗。

    後續的優化方向主要有兩方面:

    • 結合業務的使用場景,透出更豐富維度的實時數據。
    • 將該套技術體系“左移”到線下/預發測試階段,實現“功能、性能、效果”的一鍵式自動化測試,同時覆蓋從搜索應用到引擎的全鏈路。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • 【集合系列】- 深入淺出的分析TreeMap

    【集合系列】- 深入淺出的分析TreeMap

    一、摘要

    在集合系列的第一章,咱們了解到,Map的實現類有HashMap、LinkedHashMap、TreeMap、IdentityHashMap、WeakHashMap、Hashtable、Properties等等。

    本文主要從數據結構和算法層面,探討TreeMap的實現。

    二、簡介

    Java TreeMap實現了SortedMap接口,也就是說會按照key的大小順序對Map中的元素進行排序,key大小的評判可以通過其本身的自然順序(natural ordering),也可以通過構造時傳入的比較器(Comparator)。

    TreeMap底層通過紅黑樹(Red-Black tree)實現,所以要了解TreeMap就必須對紅黑樹有一定的了解,在《集合系列》文章中,如果你已經讀過紅黑樹的講解,其實本文要講解的TreeMap,跟其大同小異。

    紅黑樹又稱紅-黑二叉樹,它首先是一顆二叉樹,它具有二叉樹所有的特性。同時紅黑樹更是一顆自平衡的排序二叉樹。

    對於一棵有效的紅黑樹二叉樹,主要有以下規則:

    • 1、每個節點要麼是紅色,要麼是黑色,但根節點永遠是黑色的;
    • 2、每個紅色節點的兩個子節點一定都是黑色;
    • 3、紅色節點不能連續(也即是,紅色節點的孩子和父親都不能是紅色);
    • 4、從任一節點到其子樹中每個恭弘=叶 恭弘子節點的路徑都包含相同數量的黑色節點;
    • 5、所有的恭弘=叶 恭弘節點都是是黑色的(注意這裏說恭弘=叶 恭弘子節點其實是上圖中的 NIL 節點);

    這些約束強制了紅黑樹的關鍵性質:從根到恭弘=叶 恭弘子的最長的可能路徑不多於最短的可能路徑的兩倍長。結果是這棵樹大致上是平衡的。因為操作比如插入、刪除和查找某個值的最壞情況時間都要求與樹的高度成比例,這個在高度上的理論上限允許紅黑樹在最壞情況下都是高效的,而不同於普通的二叉查找樹。所以紅黑樹它是複雜而高效的,其檢索效率O(log n)。下圖為一顆典型的紅黑二叉樹。

    在樹的結構發生改變時(插入或者刪除操作),往往會破壞上述條件3或條件4,需要通過調整使得查找樹重新滿足紅黑樹的條件。

    調整方式主要有:左旋、右旋和顏色轉換!

    2.1、左旋

    左旋的過程是將x的右子樹繞x逆時針旋轉,使得x的右子樹成為x的父親,同時修改相關節點的引用。旋轉之後,二叉查找樹的屬性仍然滿足。

    2.2、右旋

    右旋的過程是將x的左子樹繞x順時針旋轉,使得x的左子樹成為x的父親,同時修改相關節點的引用。旋轉之後,二叉查找樹的屬性仍然滿足。

    2.3、顏色轉換

    顏色轉換的過程是將紅色節點轉換為黑色節點,或者將黑色節點轉換為紅色節點,以滿足紅黑樹二叉樹的規則!

    三、常用方法介紹

    3.1、get方法

    get方法根據指定的key值返回對應的value,該方法調用了getEntry(Object key)得到相應的entry,然後返回entry.value

    算法思想是根據key的自然順序(或者比較器順序)對二叉查找樹進行查找,直到找到滿足k.compareTo(p.key) == 0entry

    源碼如下:

    final Entry<K,V> getEntry(Object key) {
            //如果傳入比較器,通過getEntryUsingComparator方法獲取元素
            if (comparator != null)
                return getEntryUsingComparator(key);
            //不允許key值為null
            if (key == null)
                throw new NullPointerException();
            //使用元素的自然順序
                Comparable<? super K> k = (Comparable<? super K>) key;
            Entry<K,V> p = root;
            while (p != null) {
                int cmp = k.compareTo(p.key);
                if (cmp < 0)
                    //向左找
                    p = p.left;
                else if (cmp > 0)
                    //向右找
                    p = p.right;
                else
                    return p;
            }
            return null;
    }

    如果傳入比較器,通過getEntryUsingComparator方法獲取元素

    final Entry<K,V> getEntryUsingComparator(Object key) {
                K k = (K) key;
            Comparator<? super K> cpr = comparator;
            if (cpr != null) {
                Entry<K,V> p = root;
                while (p != null) {
                    //通過比較器順序,獲取元素
                    int cmp = cpr.compare(k, p.key);
                    if (cmp < 0)
                        p = p.left;
                    else if (cmp > 0)
                        p = p.right;
                    else
                        return p;
                }
            }
            return null;
    }

    測試用例:

    public static void main(String[] args) {
            Map initMap = new TreeMap();
            initMap.put("4", "d");
            initMap.put("3", "c");
            initMap.put("1", "a");
            initMap.put("2", "b");
            //默認自然排序,key為升序
            System.out.println("默認 排序結果:" + initMap.toString());
    
            //自定義排序,在TreeMap初始化階段傳入Comparator 內部對象
            Map comparatorMap = new TreeMap<String, String>(new Comparator<String>() {
    
                @Override
                public int compare(String o1, String o2){
                    //根據key比較大小,採用倒敘,以大到小排序
                    return o2.compareTo(o1);
                }
            });
            comparatorMap.put("4", "d");
            comparatorMap.put("3", "c");
            comparatorMap.put("1", "a");
            comparatorMap.put("2", "b");
    
            System.out.println("自定義 排序結果:" + comparatorMap.toString());
    }

    輸出結果:

    默認 排序結果:{1=a, 2=b, 3=c, 4=d}
    自定義 排序結果:{4=d, 3=c, 2=b, 1=a}

    3.2、put方法

    put方法是將指定的key, value對添加到map里。該方法首先會對map做一次查找,看是否包含該元組,如果已經包含則直接返回,查找過程類似於getEntry()方法;如果沒有找到則會在紅黑樹中插入新的entry,如果插入之後破壞了紅黑樹的約束,還需要進行調整(旋轉,改變某些節點的顏色)。

    源碼如下:

    public V put(K key, V value) {
            Entry<K,V> t = root;
            //如果紅黑樹根部為空,直接插入
            if (t == null) {
                compare(key, key); // type (and possibly null) check
    
                root = new Entry<>(key, value, null);
                size = 1;
                modCount++;
                return null;
            }
            int cmp;
            Entry<K,V> parent;
            // split comparator and comparable paths
            Comparator<? super K> cpr = comparator;
            //如果比較器,通過比較器順序,找到插入位置
            if (cpr != null) {
                do {
                    parent = t;
                    cmp = cpr.compare(key, t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            else {
                //通過自然順序,找到插入位置
                if (key == null)
                    throw new NullPointerException();
                    Comparable<? super K> k = (Comparable<? super K>) key;
                do {
                    parent = t;
                    cmp = k.compareTo(t.key);
                    if (cmp < 0)
                        t = t.left;
                    else if (cmp > 0)
                        t = t.right;
                    else
                        return t.setValue(value);
                } while (t != null);
            }
            //創建並插入新的entry
            Entry<K,V> e = new Entry<>(key, value, parent);
            if (cmp < 0)
                parent.left = e;
            else
                parent.right = e;
            //紅黑樹調整函數
            fixAfterInsertion(e);
            size++;
            modCount++;
            return null;
    }

    紅黑樹調整函數fixAfterInsertion(Entry<K,V> x)

    private void fixAfterInsertion(Entry<K,V> x) {
        x.color = RED;
        while (x != null && x != root && x.parent.color == RED) {
            //判斷x是否在樹的左邊,還是右邊
            if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
                Entry<K,V> y = rightOf(parentOf(parentOf(x)));
                //如果x的父親的父親的右子樹是紅色,違反了紅色節點不能連續
                if (colorOf(y) == RED) {
                    //進行顏色調整,以滿足紅色節點不能連續規則
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK); 
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //如果x的父親的右子樹等於自己,那麼需要進行左旋轉
                    if (x == rightOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateLeft(x);
                    }
                    setColor(parentOf(x), BLACK);  
                    setColor(parentOf(parentOf(x)), RED);
                    rotateRight(parentOf(parentOf(x)));
                }
            } else {
                //跟上面的流程正好相反
                //獲取x的父親的父親的左子樹節點
                Entry<K,V> y = leftOf(parentOf(parentOf(x)));
                //如果y是紅色節點,違反了紅色節點不能連續
                if (colorOf(y) == RED) {
                    //進行顏色轉換
                    setColor(parentOf(x), BLACK);
                    setColor(y, BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    x = parentOf(parentOf(x));
                } else {
                    //如果x的父親的左子樹等於自己,那麼需要進行右旋轉
                    if (x == leftOf(parentOf(x))) {
                        x = parentOf(x);
                        rotateRight(x);
                    }
                    setColor(parentOf(x), BLACK);
                    setColor(parentOf(parentOf(x)), RED);
                    rotateLeft(parentOf(parentOf(x)));
                }
            }
        }
        //根節點一定為黑色
        root.color = BLACK;
    }

    上述代碼的插入流程:

    • 1、首先在紅黑樹上找到合適的位置;
    • 2、然後創建新的entry並插入;
    • 3、通過函數fixAfterInsertion(),對某些節點進行旋轉、改變某些節點的顏色,進行調整;

    調整圖解:

    3.3、remove方法

    remove的作用是刪除key值對應的entry,該方法首先通過上文中提到的getEntry(Object key)方法找到 key 值對應的 entry,然後調用deleteEntry(Entry<K,V> entry)刪除對應的 entry。由於刪除操作會改變紅黑樹的結構,有可能破壞紅黑樹的約束,因此有可能要進行調整。

    源碼如下:

    public V remove(Object key) {
            Entry<K,V> p = getEntry(key);
            if (p == null)
                return null;
    
            V oldValue = p.value;
            deleteEntry(p);
            return oldValue;
    }

    刪除函數 deleteEntry()

    private void deleteEntry(Entry<K,V> p) {
        modCount++;
        size--;
        if (p.left != null && p.right != null) {// 刪除點p的左右子樹都非空。
            Entry<K,V> s = successor(p);// 後繼
            p.key = s.key;
            p.value = s.value;
            p = s;
        }
        Entry<K,V> replacement = (p.left != null ? p.left : p.right);
        if (replacement != null) {// 刪除點p只有一棵子樹非空。
            replacement.parent = p.parent;
            if (p.parent == null)
                root = replacement;
            else if (p == p.parent.left)
                p.parent.left  = replacement;
            else
                p.parent.right = replacement;
            p.left = p.right = p.parent = null;
            if (p.color == BLACK)
                fixAfterDeletion(replacement);// 調整
        } else if (p.parent == null) {
            root = null;
        } else { //刪除點p的左右子樹都為空
            if (p.color == BLACK)
                fixAfterDeletion(p);// 調整
            if (p.parent != null) {
                if (p == p.parent.left)
                    p.parent.left = null;
                else if (p == p.parent.right)
                    p.parent.right = null;
                p.parent = null;
            }
        }
    }

    刪除后調整函數fixAfterDeletion()的具體代碼如下:

    private void fixAfterDeletion(Entry<K,V> x) {
        while (x != root && colorOf(x) == BLACK) {
            //判斷當前刪除的元素,是在x父親的左邊還是右邊
            if (x == leftOf(parentOf(x))) {
                Entry<K,V> sib = rightOf(parentOf(x));
                //判斷x的父親的右子樹,是紅色還是黑色節點
                if (colorOf(sib) == RED) {
                    //進行顏色轉換
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateLeft(parentOf(x));
                    sib = rightOf(parentOf(x));
                }
                //x的父親的右子樹的左邊是黑色節點,右邊也是黑色節點
                if (colorOf(leftOf(sib))  == BLACK &&
                    colorOf(rightOf(sib)) == BLACK) {
                    //設置x的父親的右子樹為紅色節點,將x的父親賦值給x
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    //x的父親的右子樹的左邊是紅色節點,右邊也是黑色節點
                    if (colorOf(rightOf(sib)) == BLACK) {
                        //x的父親的右子樹的左邊進行顏色調整,右旋調整
                        setColor(leftOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateRight(sib);
                        sib = rightOf(parentOf(x));
                    }
                    //對x進行左旋,顏色調整
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(rightOf(sib), BLACK);
                    rotateLeft(parentOf(x));
                    x = root;
                }
            } else { // 跟前四種情況對稱
                Entry<K,V> sib = leftOf(parentOf(x));
                if (colorOf(sib) == RED) {
                    setColor(sib, BLACK);
                    setColor(parentOf(x), RED);
                    rotateRight(parentOf(x));
                    sib = leftOf(parentOf(x));
                }
                if (colorOf(rightOf(sib)) == BLACK &&
                    colorOf(leftOf(sib)) == BLACK) {
                    setColor(sib, RED);
                    x = parentOf(x);
                } else {
                    if (colorOf(leftOf(sib)) == BLACK) {
                        setColor(rightOf(sib), BLACK);
                        setColor(sib, RED);
                        rotateLeft(sib);
                        sib = leftOf(parentOf(x));
                    }
                    setColor(sib, colorOf(parentOf(x)));
                    setColor(parentOf(x), BLACK);
                    setColor(leftOf(sib), BLACK);
                    rotateRight(parentOf(x));
                    x = root;
                }
            }
        }
        setColor(x, BLACK);
    }

    上述代碼的刪除流程:

    • 1、首先在紅黑樹上找到合適的位置;
    • 2、然後刪除entry;
    • 3、通過函數fixAfterDeletion(),對某些節點進行旋轉、改變某些節點的顏色,進行調整;

    四、總結

    TreeMap 默認是按鍵值的升序排序,如果需要自定義排序,可以通過new Comparator構造參數,重寫compare方法,進行自定義比較。

    以上,主要是對 java 集合中的 TreeMap 做了寫講解,如果有理解不當之處,歡迎指正。

    五、參考

    1、JDK1.7&JDK1.8 源碼
    2、
    2、

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • 理解MySQL數據庫事務-隔離性

    理解MySQL數據庫事務-隔離性

    Transaction事務是指一個邏輯單元,執行一系列操作的SQL語句。

    事務中一組的SQL語句,要麼全部執行,要麼全部回退。在Oracle數據庫中有個名字,叫做transaction ID

    在關係型數據庫中,事務必須ACID的特性。

    • 原子性,事務中的操作,要不全部執行,要不都不執行
    • 一致性,事務完成前後,數據的必須保持一致。
    • 隔離性,多個用戶併發訪問數據庫時,每一個用戶開啟的事務,相互隔離,不被其他事務的操作所干擾。
    • 持久性,事務一旦commit,它對數據庫的改變是持久性的。

    目前重點討論隔離性。數據庫一共有四個隔離級別

    • 未提交讀(RU,Read Uncommitted)。它能讀到一個事物的中間狀態,不符合業務中安全性的保證,違背 了ACID特性,存在臟讀的問題,基本不會用到,可以忽略

    • 提交讀(RC,Read Committed)。顧名思義,事務提交之後,那麼我們可以看到。這是一種最普遍的適用的事務級別。我們生產環境常用的使用級別。

    • 可重複讀(RR,Repeatable Read)。是目前被使用得最多的一種級別。其特點是有GAP鎖,目前還是默認級別,這個級別下會經常發生死鎖,低併發等問題。

    • 可串行化,這種實現方式,其實已經是不是多版本了,而是單版本的狀態,因為它所有的實現都是通過鎖來實現的。

    因此目前數據庫主流常用的是RCRR隔離級別。

    隔離性的實現方式,我們通常用Read View表示一個事務的可見性。

    RC級別,事務可見性比較高,它可以看到已提交的事務的所有修改。因此在提交讀(RC,Read Committed)隔離級別下,每一次select語句,都會獲取一次Read View,得到數據庫最新的事務提交狀態。因此對於數據庫,併發性能也最好。

    RR級別,則不是。它為了避免幻讀和不可重複讀。保證在一個事務內前後數據讀取的一致。其可見性視圖Read View只有在自己當前事務提交之後,才會更新。

    那如何保證數據的一致性?其核心是通過redo logundo log來保證的。

    而在數據庫中,為了實現這種高併發訪問,就需要對數據庫進行多版本控制,通過事務的可見性來保證事務看到自己想看到的那個數據版本(或者是最新的Read View亦或者是老的Read View)。這種技術叫做MVCC

    多版本是如何實現的?通過undo日誌來保證。每一次數據庫的修改,undo日誌會存儲之前的修改記錄值。如果事務未提交,會回滾至老版本的數據。其MVCC的核心原理,以後詳談

    舉例論證:

    ##  開啟事務
    MariaDB [scott]> begin;                   
    Query OK, 0 rows affected (0.000 sec)
    
    ##查看當前的數據
    MariaDB [scott]>  select * from dept;
    +--------+------------+----------+
    | deptno | dname      | loc      |
    +--------+------------+----------+
    |     10 | ACCOUNTING | beijing  |
    |     20 | RESEARCH   | DALLAS   |
    |     30 | SALES      | CHICAGO  |
    |     40 | OPERATIONS | beijing  |
    |     50 | security   | beijing  |
    |     60 | security   | nanchang |
    +--------+------------+----------+
    6 rows in set (0.001 sec)
    
    ##更新數據
    MariaDB [scott]> update dept set loc ='beijing' where deptno = 20;
    Query OK, 1 row affected (0.001 sec)
    
    ## 其行記錄| 20 | RESEARCH | DALLAS |已經被放置在undo日誌中,目前最新的記錄被改為'beijing':
    MariaDB [scott]> select * from dept;
    +--------+------------+----------+
    | deptno | dname      | loc      |
    +--------+------------+----------+
    |     10 | ACCOUNTING | beijing  |
    |     20 | RESEARCH   | beijing  |
    |     30 | SALES      | CHICAGO  |
    |     40 | OPERATIONS | beijing  |
    |     50 | security   | beijing  |
    |     60 | security   | nanchang |
    +--------+------------+----------+
    
    ##事務不提交,回滾。數據回滾至老版本的數據。
    MariaDB [scott]> rollback;
    Query OK, 0 rows affected (0.004 sec)
    
    MariaDB [scott]> select * from dept;
    +--------+------------+----------+
    | deptno | dname      | loc      |
    +--------+------------+----------+
    |     10 | ACCOUNTING | beijing  |
    |     20 | RESEARCH   | DALLAS   |
    |     30 | SALES      | CHICAGO  |
    |     40 | OPERATIONS | beijing  |
    |     50 | security   | beijing  |
    |     60 | security   | nanchang |
    +--------+------------+----------+
    6 rows in set (0.000 sec)

    因為MVCC,讓數據庫有了很強的併發能力。隨着數據庫併發事務處理能力大大增強,從而提高了數據庫系統的事務吞吐量,可以支持更多的用戶併發訪問。但併發訪問,會出現帶來一系列問題。如下:

    數據庫併發帶來的問題 概述解釋
    臟讀(Dirty Reads) 當一個事務A正在訪問數據,並且對數據進行了修改,而這種修改還沒有提交到數據庫中,這時,另外一個事務B也訪問這同一個數據,如不控制,事務B會讀取這些”臟”數據,並可能做進一步的處理。這種現象被稱為”臟讀”(Dirty Reads)
    不可重複讀(Non-Repeatable Reads) 指在一個事務A內,多次讀同一數據。在這個事務還沒有結束時,另外一個事務B也訪問該同一數據。那麼,在事務A的兩次讀數據之間,由於第二個事務B的修改,那麼第一個事務兩次讀到的的數據可能是不一樣的 。出現了”不可重複讀”(Non-Repeatable Reads)的現象
    幻讀(Phantom Reads) 指在一個事務A內,按相同的查詢條件重新檢索以前檢索過的數據,同時發現有其他事務插入了數據,其插入的數據滿足事務A的查詢條件。因此查詢出了新的數據,這種現象就稱為”幻讀”(Phantom Reads)

    隔離級別和上述現象之間的聯繫。

    隔離級別有:未提交讀(RU,Read Uncommitted),提交讀(RC,Read Committed),可重複讀(RR,Repeatable Read),可串行化(Serializable)

    隔離級別 臟讀 不可重複讀 幻讀
    未提交讀(RU,Read Uncommitted) 可能 可能 可能
    提交讀(RC,Read Committed) 不可能 可能 可能
    可重複讀(RR,Repeatable Read) 不可能 不可能 可能
    (間隙鎖解決)
    可串行化(Serializable) 不可能 不可能 不可能

    實驗環節

    舉例在隔離級別RRRC下,說明“不可重複讀”問題。

    MySQL的默認級別是Repeatable Read,如下:

    MariaDB [(none)]> select @@global.tx_isolation;
    +-----------------------+
    | @@global.tx_isolation |
    +-----------------------+
    | REPEATABLE-READ       |
    +-----------------------+
    1 row in set (0.000 sec)

    這裏修改當前會話級別為Read Committed

    MariaDB [scott]> set session transaction isolation level read committed;
    Query OK, 0 rows affected (0.001 sec)
    
    MariaDB [scott]> select @@tx_isolation;
    +----------------+
    | @@tx_isolation |
    +----------------+
    | READ-COMMITTED |
    +----------------+
    1 row in set (0.000 sec)

    在隔離級別已提交讀(RC,Read Committed)下,出現了不可重複讀的現象。在事務A中可以讀取事務B中的數據。

    在隔離級別可重複讀(RR,Repeatable Read),不會出現不可重複讀現象,舉例如下:

    舉例說明“幻讀”的現象。

    行鎖可以防止不同事務版本的數據在修改(update)提交時造成數據衝突的問題。但是插入數據如何避免呢?

    在RC隔離級別下,其他事務的插入數據,會出現幻讀(Phantom Reads)的現象。

    而在RR隔離級別下,會通過Gap鎖,鎖住其他事務的insert操作,避免”幻讀”的發生。

    因此,在MySQL事務中,鎖的實現方式與隔離級別有關,如上述實驗所示。在RR隔離級別下,MySQL為了解決幻讀的問題,已犧牲并行度為代價,通過Gap鎖來防止數據的寫入。這種鎖,并行度差,衝突多。容易引發死鎖。

    目前流行的Row模式可以避免很多衝突和死鎖問題,因此建議數據庫使用ROW+RC(Read Committed)模式隔離級別,很大程度上提高數據庫的讀寫并行度,提高數據庫的性能。

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • ThreadLocal深度解析和應用示例

    ThreadLocal深度解析和應用示例

    開篇明意

      ThreadLocal是JDK包提供的線程本地變量,如果創建了ThreadLocal<T>變量,那麼訪問這個變量的每個線程都會有這個變量的一個副本,在實際多線程操作的時候,操作的是自己本地內存中的變量,從而規避了線程安全問題。

      ThreadLocal很容易讓人望文生義,想當然地認為是一個“本地線程”。其實,ThreadLocal並不是一個Thread,而是Thread的一個局部變量,也許把它命名ThreadLocalVariable更容易讓人理解一些。

      來看看官方的定義:這個類提供線程局部變量。這些變量與正常的變量不同,每個線程訪問一個(通過它的get或set方法)都有它自己的、獨立初始化的變量副本。ThreadLocal實例通常是類中的私有靜態字段,希望將狀態與線程關聯(例如,用戶ID或事務ID)。

    源碼解析

      1.核心方法之   set(T t)

     1     /**
     2      * Sets the current thread's copy of this thread-local variable
     3      * to the specified value.  Most subclasses will have no need to
     4      * override this method, relying solely on the {@link #initialValue}
     5      * method to set the values of thread-locals.
     6      *
     7      * @param value the value to be stored in the current thread's copy of
     8      *        this thread-local.
     9      */
    10     public void set(T value) {
    11         Thread t = Thread.currentThread();
    12         ThreadLocalMap map = getMap(t);
    13         if (map != null)
    14             map.set(this, value);
    15         else
    16             createMap(t, value);
    17     }

    解析:

      當調用ThreadLocal的set(T t)的時候,代碼首先會獲取當前線程的 ThreadLocalMap(ThreadLocal中的靜態內部類,同時也作為Thread的成員變量存在,後面會進一步了解ThreadLocalMap),如果ThreadLocalMap存在,將ThreadLocal作為map的key,要保存的值作為value來put進map中(如果map不存在就先創建map,然後再進行put);

      2.核心方法值 get()

    /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        public T get() {
           Thread t = Thread.currentThread();
           ThreadLocalMap map = getMap(t);        //此處和set方法一致,也是通過當前線程獲取對應的成員變量ThreadLocalMap,map中存放的是Entry(ThreadLocalMap的內部類(繼承了弱引用))
        
    if (map != null) {
          ThreadLocalMap.Entry e = map.getEntry(this);
          if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
          }
        }
        return setInitialValue();
    }

    解析:

      剛才把對象放到set到map中,現在根據key將其取出來,值得注意的是這裏的map裏面存的可不是鍵值對,而是繼承了WeakReference<ThreadLocal<?>> 的Entry對象,關於ThreadLocalMap.Entry類,後面會有更加詳盡的講述。

    核心方法之  remove()

        /**
         * Removes the current thread's value for this thread-local
         * variable.  If this thread-local variable is subsequently
         * {@linkplain #get read} by the current thread, its value will be
         * reinitialized by invoking its {@link #initialValue} method,
         * unless its value is {@linkplain #set set} by the current thread
         * in the interim.  This may result in multiple invocations of the
         * {@code initialValue} method in the current thread.
         *
         * @since 1.5
         */
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }

    解析:

      通過getMap方法獲取Thread中的成員變量ThreadLocalMap,在map中移除對應的ThreadLocal,由於ThreadLocal(key)是一種弱引用,弱引用中key為空,gc會回收變量value,看一下核心的m.remove(this);方法

            /**
             * Remove the entry for key.
             */
            private void remove(ThreadLocal<?> key) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1); //定義Entry在數組中的標號 for (Entry e = tab[i];              //通過循環的方式remove掉Thread中所有的Entry
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {   
                    if (e.get() == key) {
                        e.clear();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            } 

     

     

     

    靈魂提問

      問:threadlocal是做什麼用的,用在哪些場景當中?  

        結合官方對ThreadLocal類的定義,threadLocal主要滿足某些變量或者示例是線程隔離的,但是在相同線程的多個類或者方法中都能使用的到,並且當線程結束時該變量也應該銷毀。通俗點講:ThreadLocal保證每個線程有自己的數據副本,當線程結束后可  以獨立回收。由於ThreadLocal的特性,同一線程在某地方進行設置,在隨後的任意地方都可以獲取到。從而可以用來保存線程上下文信息。常用的比如每個請求怎麼把一串後續關聯起來,就可以用ThreadLocal進行set,在後續的任意需要記錄日誌的方法裏面進行get獲取到請求id,從而把整個請求串起來。     使用場景有很多,比如:

    • 基於用戶請求線程的數據隔離(每次請求都綁定userId,userId的值存在於ThreadLoca中)
    • 跟蹤一個請求,從接收請求,處理到返回的整個流程,有沒有好的辦法   思考:微服務中的鏈路追蹤是否利用了ThreadLocal特性
    • 數據庫的讀寫分離
    • 還有比如Spring的事務管理,用ThreadLocal存儲Connection,從而各個DAO可以獲取同一Connection,可以進行事務回滾,提交等操作。

        
    問:如果我啟動另外一個線程。那麼在主線程設置的Threadlocal值能被子線程拿到嗎?     原始的ThreadLocal是不具有繼承(或者說傳遞)特性的     
    問:那該如何解決ThreadLocal無法傳遞的問題呢?     用ThreadLocal的子類 InheritableThreadLocal,InheritableThreadLocal是具有傳遞性的

      /**
      * 重寫Threadlocal類中的getMap方法,在原Threadlocal中是返回
      * t.theadLocals,而在這麼卻是返回了inheritableThreadLocals,因為
      * Thread類中也有一個要保存父子傳遞的變量
      */ ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals; }
        /**  * 同理,在創建ThreadLocalMap的時候不是給t.threadlocal賦值  *而是給inheritableThreadLocals變量賦值  *  */
        void createMap(Thread t, T firstValue) {
            t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
        }

    解析:因為InheritableThreadLocal重寫了ThreadLocal中的getMap 和createMap方法,這兩個方法維護的是Thread中的另外一個成員變量  inheritableThreadLocals,線程在創建的時候回複製inheritableThreadLocals中的值 ;

    /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
      //Thread類中維護的成員變量,ThreadLocal會維護該變量
    ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */
    //Thread中維護的成員變量 ,
    InheritableThreadLocal 中維護該變量
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;


     

    //Thread init方法中的關鍵代碼,簡單來說是將父類中inheritableThreadLocals中的值拷貝到當前線程的inheritableThreadLocals中(淺拷貝,拷貝的是value的地址引用)
     if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

    總結

    • ThreadLocal類封裝了getMap()、Set()、Get()、Remove()4個核心方法。
    • 通過getMap()獲取每個子線程Thread持有自己的ThreadLocalMap實例, 因此它們是不存在併發競爭的。可以理解為每個線程有自己的變量副本。
    • ThreadLocalMap中Entry[]數組存儲數據,初始化長度16,後續每次都是1.5倍擴容。主線程中定義了幾個ThreadLocal變量,Entry[]才有幾個key。
    • Entry的key是對ThreadLocal的弱引用,當拋棄掉ThreadLocal對象時,垃圾收集器會忽略這個key的引用而清理掉ThreadLocal對象, 防止了內存泄漏。

        tips:上面四個總結來源於其他技術博客,個人認為總結的比較合理所以直接摘抄過來了

    拓展:

      ThreadLocal在線程池中使用容易發生的問題: 內存泄漏,先看下圖

      

      每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal實例. 這個Map的確使用了弱引用,不過弱引用只是針對key. 每個key都弱引用指向threadlocal. 當把threadlocal實例置為null以後,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收. 但是,我們的value卻不能回收,因為存在一條從current thread連接過來的強引用. 只有當前thread結束以後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value將全部被GC回收.

      所以得出一個結論就是只要這個線程對象被gc回收,就不會出現內存泄露,但在threadLocal設為null和線程結束這段時間不會被回收的,就發生了我們認為的內存泄露。其實這是一個對概念理解的不一致,也沒什麼好爭論的。最要命的是線程對象不被回收的情況,這就發生了真正意義上的內存泄露。比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。就可能出現內存泄露。  

      PS.Java為了最小化減少內存泄露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map里所有key為null的value。所以最怕的情況就是,threadLocal對象設null了,開始發生“內存泄露”,然後使用線程池,這個線程結束,線程放回線程池中不銷毀,這個線程一直不被使用,或者分配使用了又不再調用get,set方法,那麼這個期間就會發生真正的內存泄露。 

     

    1. JVM利用設置ThreadLocalMap的Key為弱引用,來避免內存泄露。
    2. JVM利用調用remove、get、set方法的時候,回收弱引用。
    3. 當ThreadLocal存儲很多Key為null的Entry的時候,而不再去調用remove、get、set方法,那麼將導致內存泄漏。
    4. 當使用static ThreadLocal的時候,延長ThreadLocal的生命周期,那也可能導致內存泄漏。因為,static變量在類未加載的時候,它就已經加載,當線程結束的時候,static變量不一定會回收。那麼,比起普通成員變量使用的時候才加載,static的生命周期加長將更容易導致內存泄漏危機。

     

      參考鏈接:

     

    在線程池中使用ThreadLocal

    通過上面的分析可以知道InheritableThreadLocal是通過Thread()的inint方法實現父子之間的傳遞的,但是線程池是統一創建線程並實現復用的,這樣就好導致下面的問題發生:

    •   線程不會銷毀,ThreadLocal也不會被銷毀,這樣會導致ThreadLoca會隨着Thread的復用而復用
    •   子線程無法通過InheritableThreadLocal實現傳遞性(因為沒有單獨的調用Thread的Init方法進行map的複製),子線程中get到的是null或者是其他線程復用的錯亂值(疑問點還沒搞清楚原因,後續補充::在異步線程中會出現null的情況,同步線程不會出現)     

        ps:線程池中的線程是什麼時候創建的?

     

      解決方案:

        下面兩個鏈接有詳細的說明,我就不重複寫了,後續我會將本文進一般優化並添加一些例子來幫助說明,歡迎收藏,關於本文有不同的意見歡迎評論指正……

        

        

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • 正則表達式 第五篇:C# 正則表達式

    正則表達式 第五篇:C# 正則表達式

    本文整理C#正則表達式的元字符,正則表達式是由字符構成的表達式,每個字符代表一個規則,表達式中的字符分為兩種類型:普通字符和元字符。普通字符是指字面含義不變的字符,按照完全匹配的方式匹配文本,而元字符具有特殊的含義,代表一類字符。

    把文本看作是字符流,每個字符放在一個位置上,例如,正則表達式 “Room\d\d\d”,前面四個字符Room是普通字符,後面的字符\是轉義字符,和後面的字符d組成一個元字符\d,表示該位置上有任意一個数字。

    用正則表達式的語言來描述是:正則表達式 “Room\d\d\d”共捕獲7個字符,表示“以Room開頭、以三個数字結尾”的一類字符串,我們把這一類字符串稱作一個模式(Pattern),也稱作是一個正則。

    一,轉義字符

    轉義字符是\,把普通字符轉義為具有特殊含義的元字符,常用的轉義字符有:

    • \t:水平製表符
    • \v:垂直製表符
    • \r:回車
    • \n:換行
    • \\:表示字符 \,也就說,把轉義字符 \ 轉義為普通的字符 \
    • \”:表示字符 “,在C#中,雙引號用於定義字符串,字符串包含的雙引號用 \” 來表示

    二,字符類

    在進行正則匹配時,把輸入文本看作是有順序的字符流,字符類元字符匹配的對象是字符,並會捕獲字符。所謂捕獲字符是指,一個元字符捕獲的字符,不會被其他元字符匹配,後續的元字符只能從剩下的文本中重新匹配。

    常用的字符類元字符:

    • [ char_group]:匹配字符組中的任意一個字符
    • [^char_group]:匹配除字符組之外的任意一個字符
    • [first-last]:匹配從first到last的字符範圍中的任意一個字符,字符範圍包括first和last。
    • .   :通配符,匹配除\n之外的任意一個字符
    • \w:匹配任意一個單詞(word)字符,單詞字符通常是指A-Z、a-z和0-9
    • \W:匹配任意一個非單詞字符,是指除A-Z、a-z和0-9之外的字符
    • \s:匹配任意一個空白字符
    • \S:匹配任意一個非空白字符
    • \d:匹配任意一個数字字符
    • \D:匹配任意一個非数字字符

    注意,轉義字符也屬於字符類元字符,在進行正則匹配時,也會捕獲字符。

    三,定位符

    定位符匹配(或捕獲)的對象是位置,它根據字符的位置來判斷模式匹配是否成功,定位符不會捕獲字符,是零寬的(寬度為0),常用的定位符有:

    • ^:默認情況下,匹配字符串的開始位置;在多行模式下,匹配每行的開始位置;
    • $:默認情況下,匹配字符串的結束位置,或 字符串結尾的\n之前的位置;在多行模式下,匹配每行結束之前的位置,或者每行結尾的\n之前的位置。
    • \A:匹配字符串的開始位置;
    • \Z:匹配字符串的結束位置,或 字符串結尾的\n之前的位置;
    • \z:匹配字符串的結束位置;
    • \G:匹配上一個匹配結束的位置;
    • \b:匹配一個單詞的開始或結束的位置;
    • \B:匹配一個單詞的中間位置;

     

    四,量詞、貪婪和懶惰

    量詞是指限定前面的一個正則出現的次數,量詞分為兩種模式:貪婪模式和懶惰模式,貪婪模式是指匹配盡可能多的字符,而懶惰模式是指匹配盡可能少的字符。默認情況下,量詞處於貪婪模式,在量詞的後面加上?來啟用懶惰模式。

    • *:出現0次或多次
    • +:出現1次或多次
    • ?:出現0次或1次
    • {n}:出現n次
    • {n,}:出現至少n次
    • {n,m}:出現n到m次

    注意,出現多次是指前面的元字符出現多次,例如,\d{2} 等價於 \d\d,只是出現兩個数字,並不要求兩個数字是相同的。要表示相同的兩個数字,必須使用分組來實現。

    五,分組和捕獲字符

    ()  括號不僅確定表達式的範圍,還創建分組,()內的表達式就是一個分組,引用分組表示兩個分組匹配的文本是完全相同的。定義一個分組的基本語法:

    (pattern)

    該類型的分組會捕獲字符,所謂捕獲字符是指:一個元字符捕獲的字符,不會被其他元字符匹配,後續的元字符只能從剩下的文本中重新匹配。

    1,分組編號和命名

    默認情況下,每個分組自動分配一個組號,規則是:從左向右,按分組左括號的出現順序進行編號,第一個分組的組號為1,第二個為2,以此類推。也可以為分組指定名稱,該分組稱作命名分組,命名分組也會被自動編號,編號從1開始,逐個加1,為分組指定名稱的語法是:

    (?< name > pattern)

    通常來說,分組分為命名分組和編號分組,引用分組的方式有:

    • 通過分組名稱來引用分組:\k<name>
    • 通過分組編號來引用分組:\number

    注意,分組只能後向引用,也就是說,從正則表達式文本的左邊開始,分組必須先定義,然後才能在定義之後面引用。

    在正則表達式里引用分組的語法為“\number”,比如“\1”代表與分組1 匹配的子串,“\2”代表與分組2 匹配的字串,以此類推。

    例如,對於 “<(.*?)>.*?</\1>” 可以匹配 <h2>valid</h2>,在引用分組時,分組對應的文本是完全相同的。

    2,分組構造器

    分組構造方法如下:

    • (pattern):捕獲匹配的子表達式,併為分組分配一個組號
    • (?< name > pattern):把匹配的子表達式捕獲到命名的分組中
    • (?:pattern):非捕獲的分組,並未分組分配一個組號
    • (?> pattern):貪婪分組

    3,貪婪分組

    貪婪分組也稱作非回溯分組,該分組禁用了回溯,正則表達式引擎將盡可能多地匹配輸入文本中的字符。如果無法進行進一步的匹配,則不會回溯嘗試進行其他模式匹配。

    (?> pattern )

    4,二選一

    | 的意思是或,匹配兩者中的任意一個,注意,|把左右兩邊的表達式分為兩部分。

    pattern1 | pattern2

    六,零寬斷言

    零寬是指寬度為0,匹配的是位置,所以匹配的子串不會出現在匹配結果中,而斷言是指判斷的結果,只有斷言為真,才算匹配成功。

    對於定位符,可以匹配一句話的開始、結束(^ $)或者匹配一個單詞的開始、結束(\b),這些元字符只匹配一個位置,指定這個位置滿足一定的條件,而不是匹配某些字符,因此,它們被成為 零寬斷言。所謂零寬,指的是它們不與任何字符相匹配,而匹配一個位置;所謂斷言,指的是一個判斷,正則表達式中只有當斷言為真時才會繼續進行匹配。零寬斷言可以精確的匹配一個位置,而不僅僅是簡單的指定句子或者單詞。

    正則表達式把文本看作從左向右的字符流,向右叫做後向(Look behind),向左叫做前向(Look ahead)。對於正則表達式,只有當匹配到指定的模式(Pattern)時,斷言為True,叫做肯定式,把不匹配模式為True,叫做否定式。

    按照匹配的方向和匹配的定性,把零寬斷言分為四種類型:

    • (?= pattern):前向、肯定斷言
    • (?! pattern):前向、否定斷言
    • (?<= pattern):後向、肯定斷言
    • (?<! pattern):後向、否定斷言

     1,前向肯定斷言

    前向肯定斷言定義一個模式必須存在於文本的末尾(或右側),但是該模式匹配的子串不會出現在匹配的結果中,前向斷言通常出現在正則表達式的右側,表示文本的右側必須滿足特定的模式:

     (?= subexpression )

    使用前向肯定斷言可以定一個模糊匹配,後綴必須包含特定的字符:

    \b\w+(?=\sis\b)

    對正則表達式進行分析:

    • \b:表示單詞的邊界
    •  \w+:表示單詞至少出現一次
    • (?=\sis\b):前向肯定斷言,\s 表示一個空白字符, is 是普通字符,完全匹配,\b 是單詞的邊界。

    從分析中,可以得出,匹配該正則表達式的文本中必須包含 is 單詞,is是一個單獨的單詞,不是某一個單詞的一個部分。舉個例子

    Sunday is a weekend day 匹配該正則,匹配的值是Sunday,而The island has beautiful birds 不匹配該正則。

    2,後向肯定斷言

    後向肯定斷言定義一個模式必須存在於文本的開始(或左側),但是該模式匹配的子串不會出現在匹配的結果中,後向斷言通常出現在正則表達式的左側,表示文本的左側必須滿足特定的模式:

    (?<= subexpression )

    使用後向肯定斷言可以定一個模糊匹配,前綴必須包含特定的字符:

    (?<=\b20)\d{2}\b

    對正則表達式進行分析:

    • (?<=\b20):後向斷言,\b表示單詞的開始,20是普通字符
    • \d{2}:表示兩個数字,数字不要求相同
    • \b:單詞的邊界

    該正則表達式匹配的文本具備的模式是:文本以20開頭、以兩個数字結尾。

    七,用正則從格式化的文本中扣值

    有如下的JSON格式的文本,從文本中扣出字段(CustomerId、CustomerName、CustomerIdSource和CustomerType)的值:

    {"CustomerDetails":"[{\"CustomerId\":\"57512f19\",\"CustomerName\":\"cust xyz\",\"CustomerIdSource\":\"AadTenantId\",\"CustomerType\":\"Enterprise\"}]"}

    注意,該文本轉換為C#中的字符時,需要對雙引號和轉義字符進行轉義。由於這四個字段提取規則相同,可以寫一個通用的模式來提取:

    public static string GetNestedItem(string txt, string key)
    {
        string pat = string.Format("(?<=\\\\\"{0}\\\\\":\\\\\").*?(?=\\\\\")", key);
        return Regex.Match(txt, pat, RegexOptions.IgnoreCase).Value;
    }

    正則表達式得解析:

    • (?<=\\\\\”{0}\\\\\”:\\\\\”):後向斷言,用於匹配字段名、雙引號和冒號
    • .*?:懶惰模式,匹配盡可能少的文本
    • (?=\\\\\”):前向斷言,用於匹配字段值得雙引號

     

     

    參考文檔:

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • [NLP] Adaptive Softmax

    [NLP] Adaptive Softmax

    1. Overview

    Adaptive softmax算法在鏈接1中的論文中提出,該算法目的是為了提高softmax函數的運算效率,適用於一些具有非常大詞彙量的神經網絡。

    在NLP的大部分任務中,都會用到softmax,但是對於詞彙量非常大的任務,每次進行完全的softmax會有非常大的計算量,很耗時(每次預測一個token都需要O(|V|)的時間複雜度)。

    所以paper中提出adaptive softmax來提升softmax的運算效率。

    1) 該算法的提出利用到了單詞分佈不均衡的特點(unbalanced word distribution)來形成clusters, 這樣在計算softmax時可以避免對詞彙量大小的線性依賴關係,降低計算時間;

    2) 另外通過結合現代架構和矩陣乘積操作的特點,通過使其更適合GPU單元的方式進一步加速計算。

    2. Introduction

    2.1 一般降低softmax計算複雜度的兩種方式

    1) 考慮原始分佈:近似原始概率分佈或近似原始概率分佈的子集

    2) 構造近似模型,但是產生準確的概率分佈。比如:hierarchical softmax.

    (上述兩個方法可以大致區分為:一是準確的模型產生近似概率分佈,二是用近似模型產生準確的概率分佈)

    2.2 本文貢獻點

    paper中主要採用的上述(2)的方式,主要借鑒的是hierarchical softmax和一些變型。與以往工作的不同在於,本paper結合了GPU的特點進行了計算加速。主要

    1. 定義了一種可以生成 近似層次模型 的策略,該策略同時考慮了矩陣乘積運算的計算時間。這個計算時間並非與矩陣的維數是簡單線性關係。

    2. 在近來的GPU上對該模型進行了實證分析。在所提出的優化算法中也包含了對實際計算時間模型的定義。

    3. 與一般的softmax相比,有2× 到 10× 的加速。這等價於在同等算力限制下提高了準確度。另外一點非常重要,該paper所提出的這種高效計算的方法與一些通過并行提高效率的方法相比,在給定一定量的training data下,對準確度並沒有損失。

    3. Adaptive Softmax 

    3.1 矩陣乘積運算的計算時間模型

    hidden states: (B × d); Word representation: (d × k); 這兩個矩陣的乘積運算時間: g(k, B)。其中B是batch size, d: hidden layer 的size, k vectors的數量。

    (1) 固定B,d, 探究k值大小與g(k)的關係:

    在K40、 M40兩種GPU model中進行實驗,發現k在大約為$k_0 \approx 50$的範圍之內,g(k)是常數級別的,在$k > k_0$后,呈線性依賴關係。其計算模型為:

    同樣的,關於B的計算時間的函數也呈現這樣的關係。這可以表示,矩陣相乘時,當其中一個維度很小時,矩陣乘法是低效的。

    如何理解呢?比如對於 $k_1 = 2^2$, $k_2 = 2^4$,它們均在同樣的常量時間內完成運算,那麼顯然對於$k1$量級有算力被浪費了。

    那麼這也說明,一些words的層次結構,其每個節點只具有少量的子節點(比如huffman 編碼的tree),是次優的。

    (2) 探究batch size B值大小與g(B)的關係:

    同樣的,當探究計算用時與batch size $B$,的關係時,發現在矩陣乘法運算中,其中一個維度很小時計算並不高效。因此會導致:

    (i) 在層次結構中,因為每個節點只有少數一些孩子節點 (比如Huffman 樹),其計算效率是次優的;

    (ii) 對於按照詞頻劃分clusters后,那些只包含稀有詞的cluster,被選擇的概率為p, 並且其batch size也縮減為 $p B$,也會出現以上所述的低效的矩陣乘積運算問題。

    (3) 本文的計算模型

    所以paper中綜合考慮了k與B,提出了下面的計算模型:

    對於Adaptive Softmax, 其核心思想是根據詞頻大小,將不同詞頻區間的詞分為不同的clusters,按照詞頻高的優先訪問的原則,來對每個cluster整體,進而對cluster中的每個詞進行訪問,進行softmax操作。

    那麼paper中首先以2個clusters為例,對該模型進行講解,之後推廣到多個clusters的一般情況。

    3.2 Two-Clusters Case

    根據Zipf定律,在Penn TreeBank中 20%的詞,可以覆蓋一般文檔中87%的出現過的詞。

    直觀地,可以將字典$V$中的詞分為$V_h$、$V_t$兩個部分。其中$V_h$表示高頻詞集合(head),$V_t$表示低頻詞集合(tail)。一般地,$|V_h| << |V_t|$,$P(V_h) >> P(V_t)$。

    (1) Clusters 的組織

    直觀地可以想到對於這兩個clusters有兩種組織方式:(i) 兩個clusters都是2層的樹結構,(ii) 將head部分用一個短列表保存,對tail部分用二層樹結構。何種方式更優可以就其計算效率和準確率進行比較:

    在準確率方面,(i) 較之 (ii) 一般有 5 ~ 10 %的下降。原因如下:

    對於屬於cluster $c$的每個詞$w$的概率計算為: 

    若採用(i):  $P(c | h) * P(w | c, h)$

    若採用(ii): 對於高頻詞head部分可以直接計算獲得 $P(w | h)$,其更為簡單直接。

    因此,除非(i)(ii)的計算時間有非常大的差別,否則選擇(ii)的組織方式。

    (2) 對計算時間的縮短

     

    圖2. Two clusters示意圖

    如圖2所示,$k_h = |V_h|, k_t = k – k_h, p_t = 1 – p_h$

    (i) 對於第一層:對於batch size 為B的輸入,在head中對$k_h$個高頻詞以及1個單位的二層cluster的向量(加陰影部分),共$k_h + 1$個向量,做softmax,

    這樣占文檔中$p_h$的詞可以基本確定下來,在head 的 short list 部分找到對應的單詞;

    而如果與陰影部分進行softmax值更大,表明待預測的詞,在第二層的低頻詞部分;

    第一層的計算開銷為:$g(k_h + 1, B)$

    (ii) 在short list 中確定了 $p_h × B$ 的內容后,還剩下 $p_t B$的輸入需要在tail中繼續進行softmax來確定所預測的詞。

    第二層中需要對$k_t$個向量做softmax;

    第二層的計算開銷為:$g(k_t, pB)$

    綜上,總的計算開銷為:$$C = g(k_h + 1, B) + g(k_t, p_t B)$$

    相比於最初的softmax,分母中需要對詞典中的每個詞的向量逐一進行計算;採用adaptive softmax可以使得該過程分為兩部分進行,每部分只需對該部分所包含的詞的向量進行計算即可,降低了計算用時。

    論文中的Figure 2显示了,對於$k_h$的合理劃分,最高可以實現相較於完全softmax的5x加速。

    (3) 不同cluster的分類器能力不同

    由於每個cluster基本是獨立計算的,它們並不要求有相同的能力(capacity)。

    一般可以為高頻詞的cluster予以更高的capacity,而可以適當降低詞頻詞的cluster的capacity。因為低頻詞在文檔中很少出現,因此對其capacity的適當降低並不會非常影響總體的性能。

    在本文中,採取了為不同的clusters共享隱層,通過加映射矩陣的方式降低分類器的輸入大小。一般的,tail部分的映射矩陣將其大小從$d$維縮小到$d_t = d / 4$維。

    3.3 一般性情況

    在3.2中以分為2個cluster為例,介紹了adaptive softmax如何組織並計算的,這裏將其拓展為一般情況,即可以分為多個clusters時的處理方式。

    假設將整個詞典分為一個head部分和J個詞頻詞cluster部分,那麼有:

    $$V = V_h \cup V_1 … V_j, V_i \cap V_j = \phi$$

    其中$V_h$在第一層,其餘均在第二層,如圖Figure 3所示。

    每個cluster中的詞向量的數目為$k_i = |V_i|$,單詞$w$屬於某個cluster的概率為:$p_i = \sum_{w \in V_i} p(w)$.

    那麼,

    計算head部分的時間開銷為:$C_h = g(J + k_h, B)$

    計算每個二層cluster的時間開銷為:$\forall_i, C_i = g(k_i, p_i B)$

    那麼總的時間開銷為:$C = g(J + k_h, B) + \sum_i g(k_i, p_i B) \tag8$

     

    回顧公式(7):

    $$g(k,B) = max(c + \lambda k_0 B_0, c + \lambda k B) \tag7$$

    這裡有兩部分,一個是常數部分$c + \lambda k_0 B_0$,一個是線性變換部分$c + \lambda k B$。

    通過3.1可知,在常數部分GPU的算力並未得到充分利用,因此應盡量避免落入該部分。那麼需要滿足:

    $kB \geq k_0B_0$,這樣在求max時,可以利用以上的第二部分。

    將(8)式代入(7)的第二部分得:

     

    那麼接下來的目標就是根據(10) 以最小化時間開銷C. 

    在(10)中,$J ,B$是固定的,那麼可以重點關注$\sum_i p_ik_i$與$k_h$。

    (1) $\sum_i p_ik_i$

    假設$p_{i + j} = p_i + p_j$, 那麼 $p_jk_j = (p_{i + j} – p_i) k_j$

    $p_ik_i + p_jk_j = p_i(k_i – k_j) + p_{i + j}k_j \tag{11}$  

    假設$k_i > k_j, p_{i + j}$, $k_j$均固定,那麼(11)中只有$p_i$可變,可以通過減小$p_i$的方式使(11)式減小。=> 也即$k_i > k_j$ 且 $p_i$盡量小,即包含越多詞的cluster,其概率越小。

    (2) $k_h$

    減小$k_h$可以使(10)式減小。 => 即為可以使高頻詞所在的cluster包含更少的詞。

    綜上,給定了cluster的個數J,與batch size B,可以通過給大的cluster分配以更小的概率降低時間開銷C.

    另外paper中講到可以用動態規劃的方法,在給定了J后,對$k_i$的大小進行劃分。

    cluster的劃分個數$J$。paper中實驗了採用不同的J所導致的不同計算時間。如圖Figure 4所示。雖然在$10 ~ 15$區間內效果最好,但$ > 5$之後效果沒有非常明顯的提升。所以文中建議採用2~5個clusters。

    實驗主要在text8, europarl, one billion word三個數據集上做的,通過比較ppl發現,adaptive softmax仍能保持低的ppl,並且相比於原模型有2x到10x的提速。

      

    參考鏈接:

    1. Efficient softmax approximation for GPUs: 

    Some of us get dipped in flat, some in satin, some in gloss. But every once in a while you find someone who’s iridescent, and when you do, nothing will ever compare. ~ Flipped

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • 【面經系列】一線互聯網大廠前端面試技巧深入淺出總結

    【面經系列】一線互聯網大廠前端面試技巧深入淺出總結

    一二面(基礎面)

    1. 一面基礎面

    1.1 面試準備

    1.1.1 個人簡歷

    • 基本信息:姓名-年齡-手機-郵箱-籍貫
    • 工作經歷:時間-公司-崗位-職責-技術棧-業績(哪些成就)
    • 學歷: 博士 > 碩士 > 本科 > 大專
    • 工作經歷:時間-公司-崗位-職責-技術棧-業績
    • 開源項目:GitHub和說明

    1.2.2 自我陳述

    1.2.2.1 把我面試的溝通方向(別把自己帶到坑裡面)

    答:我平時喜歡研究一些網站,並對一些技術的原理和好玩的點感興趣,我自己也喜歡思考,也喜歡嘗試探索有沒有更好的方式和實現。(有所收留,不要全部說出來,稍微留一點懸念留作面試官來提問)

    1.2.2.2 豁達、自信的適度發揮

    答:適當自信,向自己擅長的方向上面來引路;要讓面試官來欣賞我,而不是來鄙視他。

    1.2.2.3 自如談興趣

    (豁達自信,適當收住),巧妙演示實例,適時討論疑問(不知道的問題請求指導一下,如何去解決,不要說不知道,或者不了解)

    1.2.2.4 節奏要適宜

    切忌小聰明(盡量把問題的所有實現方法都寫出來,表現出來的是熟練)

    1.2 面試實戰

    [!NOTE]
    > 1. 方向要對,過程要細(性能優化,過程詳細)
    > 2. 膽子要大、心態要和(算法題認真思考,認真使勁想;敢於承擔責任,不要輕易放棄)

    2. CSS相關

    2.1 頁面布局

    2.1.1 如何實現垂直居中布局呢?

    1.已知寬高

    /*v1*/
    .container {
        position: absolute;
        left: 50%;
        top: 50%;
        marigin-left: -width / 2;
        marigin-top: -width / 2;
    }
    
    /*v2*/
    .container {
        position: absolute;
        top: calc(50% - 5em);
        left: calc(50% - 9em);
    }
    

    2.未知寬高

    /*v1*/
    .container {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
    }
    
    /*v2:flex+ auto*/
    .wrapper {
        dislay: flex;
    }
    .content {
        margin: auto;
    }
    
    /*v3. 父元素居中*/
    .wrapper {
        display: flex;
        /* 盒子橫軸的對齊方式 */
        justify-content: center;
        /* 盒子縱軸的對齊方式 */
        align-items: center;
    }
    
    /*v4.body內部居中*/
    .content {
         /* 1vh = 1% * 視口高度 */
          margin: 50vh auto;
          transform: translateY(-50%);
    }

    2.1.2 如何實現水平居中布局呢?

    1. 如果需要居中的元素為常規流中 inline / inline-block 元素,為父元素設置 text-align: center;
    2. 父元素上設置 text-align: center; 居中元素上margin 為 auto。
    3. 如果元素positon: absolute; 那麼
      • 0)設置父元素postion: relative
      • 1)為元素設置寬度,
      • 2)偏移量設置為 50%,
      • 3)偏移方向外邊距設置為元素寬度一半乘以-1

    2.1.3 如何實現三欄布局呢?

    1. left和right寫在center前面,並且分別左右浮動;
    2. 左右區域分別postion:absolute,固定到左右兩邊;中間的這個div因為是塊級元素,所以在水平方向上按照他的包容塊自動撐開。
    3. 父元素display: table;並且寬度為100%; 每一個子元素display: table-cell; 左右兩側添加寬度,中間不加寬度
    4. 包裹這個3個塊的父元素display: flex; 中間的元素flex: 1;
    5. 網格布局
    /* 網格布局 */
    .wrapper {
        display: grid;
        width: 100%;
        grid-template-columns: 300px 1fr 300px;
    }

    2.2 知道CSS動畫的實現嗎?

    [!NOTE]
    知道transition 過渡動畫和animation 關鍵幀動畫區別和具體實現。

    • 1.CSS動畫實現輪播圖
    • 2.CSS動畫實現旋轉的硬幣
    • 3.CSS動畫實現鐘擺效果

    2.3 CSS盒子模型

    2.3.1 說一下CSS的盒子模型?標準模型和IE模型的區別?CSS如何設置這兩種模型?

    • 標準盒子模型:width = content
    • IE盒子模型:width = content + pading + border

    • box-sizing : content-box
    • box-sizing : border-box

    2.4 CSS樣式獲取

    2.4.1 JS如何設置獲取盒子模型對應的寬度和高度?(面試重點)

    • dom.style.width/height : 只能取到內聯樣式的的屬性信息(拿不到外部引入的CSS樣式信息的)
    • dom.currentStyle.width/height : 會拿到瀏覽器渲染之後的屬性信息(IE瀏覽器)
    • window.getComputedStyle(dom).width/height : Chrome/Firefox 兼容, Firefox可以通過document.defaultView.getComputedStyle(dom)的方式來獲取
    • dom.getBoundingClientRect().width/height : 可以獲取距離viewport位置的寬度和高度

    2.5 BFC

    2.5.1 根據盒子模型解釋邊距額重疊問題?邊距重疊問題的解決方案?

    • 父子元素
    • 兄弟元素
    • 其他 ————————–計算方式:以參數的最大值來進行計算

    解決方案:對父級元素創建BFC

    2.5.2 BFC原理

    [!NOTE]
    BFC: 塊級格式化上下文,IFC(內聯格式化上下文)

    1. 在BFC的垂直邊距上面會發生重疊
    2. BFC的區域不會與浮動元素的BOX重疊
    3. BFC在頁面上是一個獨立的渲染區域,外部的元素不會影響到我,同時也不會影響到外部的元素
    4. 計算BFC的高度的時候,浮動元素也會參与運算

    2.5.3 如何創建BFC?

    1. float值不是none
    2. position值不是static或者relative
    3. display值為table, table-cell, inline-box1.
    4. overflow : auto/hidden

    2.5.4 BFC的使用場景?(重點理解)

    1. 解決邊距的重疊問題
    <section id="margin">
            <style>
                #margin {
                    background-color: #4eff35;
                    overflow: hidden;
                }
                #margin>p {
                    /*上 左右 下*/
                    margin: 5px auto 25px;
                    background-color: #ff255f;
                }
            </style>
            <p>1</p>
            <!--把一個元素放在一個容器裏面,為這個容器創建BFC即可解決邊距重疊問題-->
            <div style="overflow: hidden">
                <p>2</p>
            </div>
    
            <p>3</p>
    </section>
    1. BFC 不與float部分重疊的解決
    <section id="layout">
          <style>
              #layout {
                  background-color: #48adff;
              }
              #layout .left {
                  float: left;
                  height: 300px;
                  width: 200px;
                  background-color: #ff4344;
              }
              #layout .right {
                  height: 400px;
                  background-color: #ff255f;
                  /*給右邊的這個盒子容器創建一個BFC, 這個容器裏面的內容就會沿着垂直方向延伸*/
                  overflow: auto;
                  /*overflow: auto;*/
                  /*display: table;*/
                  /*float: left;*/
                  /*position: fixed;*/
              }
          </style>
          <div class="left">
              LEFT
          </div>
          <div class="right">
              RIGHT
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
              <p>111</p>
          </div>
      </section>
    1. BFC子元素即使是float元素也要參与運算
    <section id="float">
          <style>
              /*一個盒子內部的內容如果是浮動的話,那麼這個盒子的內容實際上是不參与父容器高度計算的*/
              #float {
                  background-color: red;
                  /*overflow: hidden;*/
                  float: left;
              }
              #float .float {
                  float: left;
                  font-size: 30px;
              }
          </style>
          <div class="float">
              我是浮動的元素
          </div>
    </section>

    3. 事件相關

    3.1 DOM事件

    3.1.1 DOM事件的級別有哪些?

    [!NOTE]
    DOM級別一共可以分為四個級別:DOM0級、DOM1級、DOM2級和DOM3級。而DOM事件分為3個級別:DOM0級事件處理,DOM2級事件處理和DOM3級事件處理。

    1. DOM0 : element.onclick = function(e) {}
      DOM1 :該標準中未涉及到事件綁定的相關東西
    2. DOM2 : element.addEventListener(‘click’, function(e){}, false), 一個DOM元素可以添加多個事件
    3. DOM3 : element.addEventListener(‘keyup’, function(e){}, false),在DOM2標準基礎上面增加了新的事件類型:鼠標事件,鍵盤事件,焦點事件

    3.1.2 DOM事件模型有哪些?

    1. 事件捕獲:從外向內, window -> document -> body -> button
    2. 事件冒泡:從內向外,button -> body -> document -> window

    3.1.3 DOM事件流?

    瀏覽器為當前的頁面與用戶進行交互的過程中,點擊鼠標後事件如何傳入和響應的呢?

      1. 捕獲階段:從外部容器開始向內
      1. 目標階段:事件通過捕獲到達目標階段
      1. 冒泡階段:從目標元素再上傳到window對象

    3.1.4 什麼事件可以代理?什麼事件不可以代理呢?

    什麼樣的事件可以用事件委託,什麼樣的事件不可以用呢?

    [!NOTE]

    1. 通常支持事件冒泡(Event Bubbling)的事件類型為鼠標事件和鍵盤事件,例如:mouseover, mouseout, click, keydown, keypress。
    2. 接口事件(指的是那些不一定與用戶操作有關的事件)則通常不支持事件冒泡(Event Bubbling),例如:load, change, submit, focus, blur。

    很明顯:focus 和 blur 都屬於不支持冒泡的接口事件。既然都不支持冒泡,那又如何實現事件代理呢?

    3.1.5 IE和DOM事件流的區別?

    IE採用冒泡型事件 Netscape使用捕獲型事件 DOM使用先捕獲后冒泡型事件

    1. 冒泡型事件模型: button -> div -> body (IE瀏覽器本身只支持Bubbling不支持Capturing)
    2. 捕獲型事件模型: body -> div-> button (Netscape事件流,網景瀏覽器公司)
    3. DOM事件模型: body -> div -> button -> button -> div -> body (先捕獲后冒泡,除了IE以外的其他瀏覽器都支持標準的DOM事件處理模型)

    [!NOTE]

    • 規範和瀏覽器實現的差別?
      • DOM2級事件規範的捕獲階段,事件從文檔節點document開始傳播,現代瀏覽器大多數都是從window對象開始傳播事件的;
      • DOM2級事件規範捕獲階段不涉及事件目標,現代瀏覽器大多數都在這個階段包含事件目標。

    3.1.6 事件對象event的屬性方法的差別?

            IE                    DOM
    cancelBubble = true    stopPropagation()    // 停止冒泡
    returnValue = false    preventDefault()     // 阻止元素默認事件
    srcEelement            target               // 事件目標

    3.1.7 描述DOM事件捕獲的具體流程?

    window -> document -> HTML標籤 -> body -> … -> 目標元素

    [!NOTE]
    關鍵點: 注意根節點是window這個對象的

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
      <div id="container">
          <style>
              #container {
                  width: 200px;
                  height: 200px;
                  background-color: #ff255f;
              }
          </style>
      </div>
      <script>
          // 事件捕獲機制
          window.addEventListener('click', function(){
              console.log('window capture');
          }, true)
          document.addEventListener('click', function () {
              console.log('document capture');
          }, true)
          document.documentElement.addEventListener('click', function () {
              console.log('HTML capture');
          }, true)
          document.body.addEventListener('click', function () {
              console.log('body capture');
          }, true)
          document.getElementById('container').addEventListener('click', function () {
              console.log('container capture');
          }, true)
    
          // 事件冒泡機制
          window.addEventListener('click', function(){
              console.log('window capture');
          })
          document.addEventListener('click', function () {
              console.log('document capture');
          })
          document.documentElement.addEventListener('click', function () {
              console.log('HTML capture');
          })
          document.body.addEventListener('click', function () {
              console.log('body capture');
          })
          document.getElementById('container').addEventListener('click', function () {
              console.log('container capture');
          })
    
          // 輸出結果
          window capture  --> document capture --> HTML capture --> body capture --> container capture --> container capture -->  body capture --> HTML capture --> document capture --> window capture
      </script>
    </body>
    </html>

    3.1.8 如何拿到HTML這個標籤節點元素呢?(加分項)

      var html = document.documentElement;

    3.1.9 描述Event對象的常見應用?

    1. e.preventDefault() : 阻止默認事件(如阻止a標籤的默認跳轉行為)
    2. e.stopPropagation() : 阻止事件冒泡的行為
    3. *** e.stopImmediatePropagation() : 事件響應的優先級的應用場景,如果一個元素綁定了多個事件,但是又不想讓其他的事件執行的時候使用該方法【也會阻止冒泡】
    4. e.currentTarget : 當前所綁定的事件對象
      document.documentElement.onclick = function(e) {
        console.log(e.currentTarget, e.target);       // <html><body>...</body></html>()給綁定事件的那個元素, 當前被點擊的那個元素
      }

    [!NOTE]
    e.target : 當前被點擊的元素,父元素使用事件代理的方式來實現,可以直接使用該屬性獲取被點擊的那個元素

    3.2 如何自定義事件?(重點))

    3.2.1 如何給一個按鈕綁定一個自己定義的事件呢?

      // v1. 使用Event對象來自定義事件
      // 開始創建一個自己定義的事件對象
      var eve = new Event('customEvent');
      // 使用dom2事件處理的方式來給這個元素綁定一個事件
      var dom = document.documentElement;
      dom.addEventListener('customEvent', function(e) {
        console.log('customEvent called!');
      });
      // 下面的這句話可以在適合的場景中來觸發一個自己定義的事件對象
      setTimeout(function(){
        // 在1s之後觸發這個事件
        dom.dispatchEvent(eve);
      }, 1000)
    
    
      // v2. 使用CustomEvent來實現自定義事件
      var dom = document.documentElement;
      // 使用CustomEvent的方式可以在事件觸發的時候傳遞一個參數,然後通過e.detail 的方式來獲取這個參數信息
      var myClick = new CustomEvent('myClick', {detail : {name : 'zhangsan', age : 24}});
      dom.addEventListener('myClick', function(e){
        console.log(e.detail, e.target)
      })
      dom.dispatchEvent(myClick);
    

    4. HTTP協議

    4.1 HTTP協議的主要特點?

    • 簡單快速
    • 靈活
    • 無連接
    • 無狀態

    4.2 HTTP報文的組成部分?

    • 請求報文
      請求行:請求方法 資源地址 HTTP版本
      請求頭: key : value
      空行 :
      請求體 : name=zhangsan&age=18
    • 響應報文 : HTTP版本 狀態碼
      狀態行
      響應頭
      空行
      響應體

    4.3 HTTP方法?

    • GET : 獲取資源
    • POST : 傳輸資源
    • PUT :更新資源
    • DELETE : 刪除資源
    • HEAD :獲取報文首部
    • OPTIONS : 允許客戶端查看服務器的性能。

    4.4 POST和GET的區別?

    1. GET請求在瀏覽器回退的時候是無害的,而POST會再次提交請求
    2. GET請求產生的URL地址可以被收藏,而POST不可以
    3. GET請求會被瀏覽器主動緩存,而POST不會,除非主動設置
    4. GET請求只能進行URL編碼,而POST支持多種編碼方式
    5. GET請求參數會被完整第保留在瀏覽器的歷史記錄裏面,而POST參數不會被保留
    6. GET請求愛URL中傳送的參數的長度是有限的(2KB),而POST沒有限制
    7. 對參數的數據類型,GET值接受ASCII字符,而POST沒有限制
    8. POST比GET更安全,GET參數直接暴露在URL上,所以不能用來傳遞敏感信息
      9. GET參數通過URL傳遞,POST參數直接放在了Request body中

    4.5 HTTP狀態碼?

    4.5.1 狀態碼的第一位

    • 1xx :指示信息-表示請求已接收,繼續處理(重點)
    • 2xx :成功-表示請求已被成功接收
    • 3xx :重定向-要完成請求必須進行更進一步的操作
    • 4xx :客戶端錯誤-請求有語法錯誤或請求無法實現
    • 5xx :服務器錯誤-服務器未能實現合法的請求

    4.5.2 狀態碼詳解

    • 200 OK : 客戶端請求成功
    • 206 Partial Content : 客戶端發送了一個帶有Range頭的GET請求(Video標籤或者audio標籤在請求數據的時候)
    • 301 Moved Permanently : 請求的頁面已經轉移到了新的URL
    • 302 Found : 所請求的頁面已經臨時轉移到了新的URL
    • 304 Not Modified :客戶端有緩衝的文檔併發出了一個條件下的請求,原來緩衝的文檔還可以繼續使用
    • 400 Bad Request : 客戶端請求有語法錯誤,不被服務器所理解
    • 401 Unauthorized : 請求未經授權,這個狀態碼必須和WWW-Authenticate報頭域一起使用
    • 403 Forbidden:對被請求頁面的訪問被禁止
    • 404 Not Found : 請求資源不存在
    • 500 Internal Server Error :服務器發生不可預期的錯誤,原來緩衝的文檔還可以繼續使用
    • 503 Service Unavailable : 請求未完成,服務器臨時過載或宕機,一段時間后可能恢復正常

    4.6 什麼是持久連接?

    [!NOTE]
    HTTP協議採用‘請求-應答’模式, HTTP1.1版本才支持的,使用Keep-alive字段可以建立一個長連接,從而不需要每次請求都去建立一個新的連接。

    4.7 什麼是管線化?

    4.7.1 基本概念

    • 在使用持久連接(Keep-alive)的情況下,某個連接上的消息的傳遞類似於:請求1 –> 響應1 –> 請求2 –> 響應2 –> 請求3 –> 響應3
    • 管線化的過程: 請求1 –> 請求2 –> 請求3 –> 響應1 –> 響應2 –> 響應3

    4.7.2 管線化的特點(特點)

    1. 管線化機制通過持久連接完成,僅在HTTP1.1版本之後支持
    2. 只有GET和HEAD請求可以進行管線化,POST有所限制的
    3. 初次創建連接的時候不應該啟動管線機制,因為對方(服務器)不一定支持HTTP1.1版本的協議
    4. 管線化不會影響到響應到來的順序,HTTP響應返回的順序並未改變
    5. HTTP1.1 要求服務器支持管線化,但並不要求服務器也對響應進行管線化處理,只是要求對於管線化的請求不失敗即可
    6. 由於上面提到的服務器端問題,開啟管線化很可能並不會帶來大幅度的性能提升,而且很多服務器和代理程序對管線化的支持並不好,因此現代的瀏覽器如Chrome和Firefox默認並沒有開啟管線化支持

    5. 原型鏈

    5.1 創建對象的幾種方法?

    // 1. 使用字面量的方式來創建
    var o1 = {name : 'zhangsan'};
    var o11 = new Object({name : 'zhangsan'});
    
    // 2. 使用普通構造函數的方式來創建
    var M = function(){
        this.name = 'zhangsan';
    }
    var o2 = new M();
    
    // 3. Object.create方法
    var p = {name : 'zhangsan'};
    var o3 = Object.create(p);

    5.2 原型、構造函數、實例、原型鏈?

    構造函數:使用new運算符來聲明一個實例(任何函數都是可以通過構造函數來使用的)

    原型鏈:通過原型鏈可以找到上一級別的原型對象

    原型對象:多個實例公用的數據和屬性或者方法

    5.3 instanceof的原理?

    [!NOTE]
    instanceof 檢測一個對象A是不是另一個對象B的實例的原理是:查看對象B的prototype指向的對象是否在對象A的[[prototype]]鏈上。如果在,則返回true,如果不在則返回false。不過有一個特殊的情況,當對象B的prototype為null將會報錯(類似於空指針異常)。

    // 2. 使用普通構造函數的方式來創建
    var M = function(){
      this.name = 'zhangsan';
    }
    var o2 = new M();
    undefined
    o2.__proto__ == M.prototype
    true
    o2.__proto__ == M.prototype
    true
    o2.__proto__.constructor === Object
    false
    o2.__proto__.constructor === M
    true

    5.4 new運算符的原理?

    1. 一個新對象被創建。它繼承於foo.prototype
    2. 構造函數foo被執行。執行的時候,相應的傳參會被傳入,同時上下文(this)會被指定為這個新實例,new foo等同於 new foo(),只能用在不傳遞任何參數的情況
    3. 如果構造函數返回了一個“對象”,那麼這個對象會取代整個new出來的結果。如果構造函數沒有返回對象,那麼new 出來的結果為步驟1創建的對象
    // new 一個對象的過程
    var _new = function (fn) {
      // 1. 創建一個對象,這個對象要繼承fn這個構造函數的原型對象
      var o = Object.create(fn.prototype);
      // 2. 執行構造函數
      var k = fn.call(o, arguments);
      // 3. 看下執行的這個函數的運行效果是不是函數
      if (typeof k === 'object'){
          return k;
      }
      else
      {
          return o;
      }
    }

    6. 面向對象

    6.1 類與繼承:如何實現繼承,繼承的幾種實現方式

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    <script>
      // 類的聲明
      function Animal1() {
          this.name = 'name';
      }
      // ES6 中的class的聲明
      class Animal2 {
          constructor(){
              this.name = 'name';
          }
      }
    
      console.log(new Animal1(), new Animal2());
      ///////////////////////////////////////////////////////////////////////////////////////////
    
    
      // 如何實現類的繼承呢???-----------本質:原型鏈
      // v1. 藉助構造函數實現繼承
      function Parent1() {
          this.name = 'parent1'
      }
      Parent1.prototype.sayHello = function () {
          console.log('hello');
      }
      function Child1() {
          // 執行父親的構造函數:
          // 1. 實現原理:將父級函數的this指向了這個子類的實例上面去了
          // 2. 缺點:父親的原型鏈上面的方法或者屬性不能被繼承;只能實現部分繼承
          Parent1.call(this);
          this.type = 'child1';
      }
      // 沒有參數的時候,可以直接new + 函數名稱
      console.log(res = new Child1);
    
    
    
    
      // v2. 藉助原型鏈實現繼承
      function Parent2() {
          this.name = 'parent2';
          this.data = [1, 2, 3];
      }
      Parent2.prototype.sayHello = function () {
          console.log('hello');
      }
      function Child2() {
          this.type = 'child2';
      }
      // prototype 就是為了讓這個對象的實例可以訪問到原型鏈上的內容
      Child2.prototype = new Parent2();
      // new Child2().__proto__ === Child2.prototype  // true
      // new Child2().__proto__.name                  // parent2
      // 原型鏈繼承的缺點:
      // 1. 原理:通過修改原型鏈來實現對象的繼承關係
      // 2. 缺點:修改第一個對象上面的屬性,會直接修改第二個對象屬性數據(引用類型)
      var c1 = new Child2();
      var c2 = new Child2();
      c1.data.push(100, 200, 300);
    
      // v3. 組合繼承
      function Parent3() {
          this.name = 'parent3';
          this.data = [1, 2, 3];
      }
      function Child3() {
          // 1. 借用構造函數繼承
          Parent3.call(this);
          this.type = 'child3';
      }
      // 2. 原型鏈繼承
      // child3的原型對象是Parent3的一個實例對象,但是這個實例對象中是沒有constructor這個屬性的,因此尋找屬性的時候回沿着這個實例對象的原型鏈繼續向上尋找new Parent3().prototype 這個原型對象的,
      // 最終在Parent3.prototype這個原型對象中找到了這個屬性,new一個對象找的實際上是{Parent3.prototype.constructor : Parent3}
      Child3.prototype = new Parent3();
      var c1 = new Child3();
      var c2 = new Child3();
      c1.data.push(100, 200, 300);
      // 組合繼承的特點:
      // 1. 原理:結合借用構造函數繼承和原型鏈繼承的優點,摒棄二者的缺點
      // 2. 缺點:父類構造函數在創建實例的時候總共執行了兩次(new Parent3(), new Child3())
    
    
      // v4. 組合繼承的優化1
      function Parent4() {
          this.name = 'parent4';
          this.data = [1, 2, 3];
      }
      function Child4() {
          // 1. 借用構造函數繼承
          Parent4.call(this);
          this.type = 'child4';
      }
      // 讓子類的構造函數的原型對象和父類構造函數的原型對象執向同一個對象(都是同一個對象)
      Child4.prototype = Parent4.prototype;
      // 測試
      var c1 = new Child4();
      var c2 = new Child4();
      console.log(c1 instanceof Child4, c1 instanceof Parent4);
      console.log(c1.constructor)         // Parent4? 如何實現:c1.constructor(c1.__proto__.constructor) === Child4 呢?
      // 缺點:
      // 1. 無法通過原型對象的constructor屬性來獲取對象的屬性對應的構造函數了(子類和父類公用的是一個contructor)
      // 2. obj instanceof Child4 === true; obj instanceof Parent4 === true
      // 3. obj.__proto__.constructor === Child4; obj.__proto__.constructor === Parent4  ???
    
      // v5. 組合繼承的優化2【完美寫法】
      function Parent5() {
          this.name = 'parent5';
          this.data = [1, 2, 3, 4, 5];
      }
      function Child5(){
          Parent5.call(this);
          this.type = 'child5';
      }
    
      // 通過創建中間對象的方式來把兩個對象區分開
      // var obj = new Object(); obj.__proto__ = Constructor.prototype;
      // 1. Object.create創建的對象obj, 這個obj的原型對象就是參數
      // 2. Child5的原型對象是Child5.prototype
      // 3. Child5.prototype = obj,obj這個對象相當於就是一个中間的橋樑關係
      Child5.prototype = Object.create(Parent5.prototype);
      // 當前的方式還是會按照原型鏈一級一級向上尋找的, 給Child5的原型對象上面綁定一個自己定義的constructor屬性
      Child5.prototype.constructor = Child5;
    
      // var s1 = new Child5()
    
      // 上面的代碼等價於
      var obj = Object.create(Parent5.prototype);     // obj.prototype = Parent5.prototype
      Child5.prototype = obj;
      Child5.prototype.constructor = Child5;
      // 1. 對象之間就是通過__proto__ 屬性向上尋找的
      // 2. 尋找規則: child5 ---> Child5.prototype ---> obj(Object.create(Parent5.prototype)) ---> Parent5.prototype
    
    
      // 技巧:不要讓面試官問太多題目:拖拉時間【擠牙膏】,把一個問題盡量吃透
      // 消化這一塊內容
    </script>
    </body>
    </html>

    [!WARNING]
    面試技巧

    1. 不要讓面試官問太多題目:拖拉時間【擠牙膏】,把一個問題盡量吃透
    2. 知識深度

    7. 通信

    7.1 什麼是同源策略個限制?

    [!NOTE]
    同源策略限制是從一個源加載的文檔或腳本如何與來自另一個源的資源進行交互。這是一個用於隔離潛在惡意文件的關鍵的安全機制。(一個源的文檔或腳本是沒有權利直接操作另外一個源的文檔或腳本的)

    1. Cookie, LocalStorage和IndexDB無法讀取
    2. DOM無法獲得;(document.body是無法獲取的)
    3. Ajax請求不能發送

    7.2 前後端如何進行通信呢?

    1. Ajax(有同源策略限制);Fetch API則是XMLHttpRequest的最新替代技術, 它是W3C的正式標準
    2. WebSocket:支持跨域請求數據,沒有同源策略的限制
    3. CORS:新的協議通信標準;CORS則將導致跨域訪問的請求分為三種:Simple Request,Preflighted Request以及Requests with Credential;cors相對於jsonp而言的好處就是支持所有的請求方式,不止是get請求,還支持post,put請求等等,而它的缺點就很明顯,無法兼容所有的瀏覽器,對於要兼容到老式瀏覽器而言,還是使用jsonp好點

    7.3 如何創建Ajax呢?

    1. XMLHttpRequest對象的工作流程
    2. 瀏覽器的兼容性處理【重點】
    3. 事件的觸發條件
    4. 事件的觸發順序
      function ajax(params){
        // 1. 創建對象,考慮兼容性【重點】
        var xhr = XMLHTTPRequest ? new XMLHTTPRequest() : new window.ActiveXObject('Microsoft.XMLHTTP');      // *** 兼容性問題必須考慮
        // 2. 打開連接
        var type = params.type || 'GET',
            url = params.url || '',
            data = params.data || {},
            success = params.success,
            error = params.error,
            dataArr = [];
        for (var k in data) {
          dataArr.push(k + '=' + data[k]);
        }
        //帶上Cookie
        xhr.withCredentials = true;
        if (type.toUpperCase() === 'GET') {
          // get
          url += '?' + dataArr.join('&');
          // 問號結尾的話,直接替換為空字符串
          xhr.open(type, url.replace(/\?$/g, ''), true);
          // GET 請求的話,是不需要再send方法中帶上參數的
          xhr.send();
        }
        else {
          // POST
          xhr.open(type, url, true);
          xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
          // POST 請求需要把數據放在send方法裏面, data = name=zhangsna&age=18&sex=male
          xhr.send(dataArr.join('&'));
        }
        // 開始監聽變化
        xhr.onreadystatechange = function(){
          // 這裏需要考慮強緩存和協商緩存的話直接處理,206是媒體資源的創建方式
          if (xhr.readyState === 4 && xhr.status === 200 || xhr.status === 304) {
              var res;
              if (success instanceof Function) {
                res = xhr.responseText;
                if (typeof res === 'string') {
                  res = JSON.parse(res);
                  // 開始執行成功的回調函數
                  success.call(xhr, res);
                }
              } else {
                if (error instanceof Function) {
                  // 失敗的話直接返回這個responseText中的內容信息
                  error.call(xhr, res);
                }
              }
          }
        }
      }

    7.4 跨域通信的幾種方式?

    7.4.1 JSONP

      function jsonp(url, onsuccess, onerror, charset){
        // 1. 全局註冊一個callback
        var callbackName = 'callback' + Math.random() * 100;
        window[callbackName] = function(){
          if (onsuccess && typeof onsuccess === 'Function') {
            onsuccess(arguments[0]);
          }
        }
        // 2. 動態創建一個script標籤
        var script = document.createElement('script');
        script.setAttribute('type', 'text/javascript');
        charset && script.setAttribute('charset', charset);
        script.setAttribute('src', url);
        script.async = true;
        // 3. 開始監聽處理的過程
        script.onload = script.onreadystatechange = function(){
          if (!script.readyState || /loaded|complete/.test(script.readyState)) {
            // 4. 成功之後移除這個事件
            script.onload = script.onreadystatechange = null;
            // 刪除這個script的DOM對象(head.removeChild(script), 這個DOM節點的父節點相當於是head標籤這個父節點)
            script.parentNode && script.parentNode.removeChild(script);
            // 刪除函數或變量
            window[callbackName] = null;
          }
        }
        script.onerror = function(){
          if (onerror && typeof onerror === 'Function') {
            onerror();
          }
        }
        // 5. 開始發送這個請求(把這個標籤放在頁面中的head標籤中即可)
        document.getElementsByTagName('head')[0].appendChild(script);
      }

    7.4.2 Hash

    hash 改變后頁面不會刷新的

    [!NOTE]
    使用場景:當前的頁面A通過iframe或者frame嵌入了跨域的頁面

      // 1. A頁面中的代碼如下
      var B = document.getElementsByTagName('iframe');
      B.src = B.src + '#' + JSON.stringfy(data);
      // 2. B中的偽代碼如下
      window.onhashchange = function(){
        var data = window.location.hash;    // 接受數據
        data = JSON.parse(data);
      }

    7.4.3 postMessage(HTML5中新增)

    [!NOTE]
    使用場景: 可以實現窗口A(A.com)向窗口B(B.com)發送信息

      // 1. 窗口B中的代碼如下
      var BWindow = window;
      BWindow.postMessage(JSON.stringfy(data), 'http://www.A.com');   
      // 2. 窗口A中代碼
      var AWindow = window;
      AWindow.addEventListener('message', function(e){
          console.log(e.origin);                  // http://www.B.com
          console.log(e.source);                  // BWindow
    
          e.source.postMessage('已成功收到消息');
    
          console.log(JSON.parse(e.data));        // data
      }, false)
      // 父窗口給子窗口發信息,需要用iframe的contentWindow屬性作為調用主體
      // 子窗口給父窗口發的信息需要使用window.top,多層iframe使用window.frameElement

    7.4.4 . WebSocket

    [!NOTE]
    不受同源策略影響,可以直接使用

      var ws = new window.WebSocket('ws://echo.websocket.org');
    
      // 打開連接
      ws.onopen = function(e){
        console.log('Connection open ……');
        ws.send('Hello WebSocket!');
      }
    
      // 接受消息
      ws.onmessage = function(e){
        console.log('Received Message : ', e.data);
      }
    
      // 關閉連接
      ws.onclose = function(e){
        console.log('Connection closed');
      }

    7.4.5 CORS

    支持跨域通信版本的Ajax,是一種新的標準(Origin頭)【ajax的一個變種,適用於任何】

      fetch('/get/name', {
        method : 'get'
      }).then(function(response){
        console.log(response);
      }).catch(function(err){
        // 出錯了;等價於then的第二個參數
      });
      // 原因:瀏覽器默認會攔截ajax請求,會根據頭中的origin消息進行判斷處理消息;Origin字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。JSONP只支持GET請求,CORS支持所有類型的HTTP請求。JSONP的優勢在於支持老式瀏覽器,以及可以向不支持CORS的網站請求數據。
    7.4.5.1 CORS請求的基本流程
    1. 對於簡單請求,瀏覽器直接發出CORS請求。具體來說,就是在頭信息之中,增加一個Origin字段。
    2. Origin字段用來說明,本次請求來自哪個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否同意這次請求。
    3. 如果Origin指定的源,不在許可範圍內,服務器會返回一個正常的HTTP回應。瀏覽器發現,這個回應的頭信息沒有包含Access-Control-Allow-Origin字段(詳見下文),就知道出錯了,從而拋出一個錯誤,被XMLHttpRequest的onerror回調函數捕獲。
    4. 如果Origin指定的域名在許可範圍內,服務器返回的響應,會多出幾個頭信息字段。
      Access-Control-Allow-Origin: http://api.bob.com   // 必需的字段
      Access-Control-Allow-Credentials: true            // 可選字段: 是否允許發送cookie
      Access-Control-Expose-Headers: FooBar
      Content-Type: text/html; charset=utf-8
    1. 簡單請求的CORS請求,會在正式通信之前,增加一次HTTP查詢請求,稱為”預檢”請求(preflight)。OPTIONS表示當前的這個請求是用來詢問的;服務器收到”預檢”請求以後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以後,確認允許跨源請求,就可以做出回應。
    7.4.5.2 JSONP和CORS的區別?
    1. JSONP只支持GET請求,CORS支持所有類型的HTTP請求
    2. JSONP的優勢在於支持老式瀏覽器,以及可以向不支持CORS的網站請求數據。

    8. 安全

    8.1 CSRF

    8.1.1 基本概念和縮寫

    CSRF: 跨站請求偽造,Cross site request forgery

    8.1.2 CSRF 攻擊原理

    8.1.3 可以成功攻擊的條件?

    1. 目標網站存在CSRF漏洞的請求接口(一般為get請求)
    2. 目標用戶之前已經成功登錄過這個網站(留下了Cookie)

    8.1.4 如何防禦呢?

    1. Token驗證:訪問服務器接口的時候,會自動帶上這個token
    2. Referer驗證:驗證網站的頁面來源(只有我當前網站下的頁面才可以請求,對於來自其他網站的請求一律攔截)
    3. 隱藏令牌: 隱藏信息會放在header中(類似於Token)

    8.2 XSS

    8.2.1 基本概念和縮寫

    XSS: cross-site scripting, 跨站腳本攻擊

    8.2.2 XSS防禦

    攻擊原理: 注入JS腳本

    防禦措施: 讓JS代碼無法解析執行

    8.3 CSRF和XSS的區別呢?

    1. CSRF:網站本身存在漏洞的接口,依賴這些登錄過目標網站的用戶來實現信息的竊取;
    2. XSS:向頁面中注入JS執行,JS函數體內執行目標任務;

    [!NOTE]

    1. 一定要說出中文名稱,實現原理,防範措施都說出來
    2. 不要拖泥帶水,言簡意賅

    9. 算法

    [!NOTE]
    算法攻略:多刷題才是硬道理!!!

    二三面(知識深度面)

    10. 渲染機制

    10.1 什麼是DOCTYPE及作用?

    1. DTD(Document Type Definition):文檔類型定義,是一系列的語法規則,用來定義XML或者(X)HTML的文件類型。瀏覽器會使用它來判斷文檔的類型,決定使用哪一種協議來解析,以及切換瀏覽器模式;
    2. DOCTYPE: 是用來聲明文檔類型和DTD規範的,一個主要的用途是文件的合法性驗證;如果文件代碼不合法,那麼瀏覽器解析的時候就會出現一些出錯
    3. 總結:Doctype就是通知瀏覽器當前的文檔是屬於那種類型的,包含哪些DTD。
      <!--HTML5的寫法-->
      <DOCTYPE html>
      <!-- HTML 4.01  Strict
        1. 這個DTD 包含所有的HTML元素和屬性
        2. 但是不包含展示性的和棄用的元素(比如font)
      -->
      <DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd" >
      <!-- HTML 4.0.1 Transitional
        1. 這個DTD 包含所有的HTML元素和屬性
        2. 也包含展示性的和棄用性的元素(比如font)
      -->
      <DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" " http://www.w3.org/TR/html4/loose.dtd" >

    [!NOTE]
    在W3C標準出來之前,不同的瀏覽器對頁面渲染有不同的標準,產生了一定的差異。這種渲染方式叫做混雜模式。在W3C標準出來之後,瀏覽器對頁面的渲染有了統一的標準,這種渲染方式叫做標準模式。<!DOCTYPE>不存在或者形式不正確會導致HTML或XHTML文檔以混雜模式呈現,就是把如何渲染html頁面的權利交給了瀏覽器,有多少種瀏覽器就有多少種展示方式。因此要提高瀏覽器兼容性就必須重視<!DOCTYPE>

    10.2 嚴格模式和混雜模式

    [!NOTE]
    嚴格模式和混雜模式都是瀏覽器的呈現模式,瀏覽器究竟使用混雜模式還是嚴格模式呈現頁面與網頁中的DTD(文件類型定義)有關,DTD裡面包含了文檔的規則。比如:loose.dtd

    • 嚴格模式:又稱標準模式,是指瀏覽器按照W3C標準來解析代碼,呈現頁面
    • 混雜模式:又稱為怪異模式或者兼容模式,是指瀏覽器按照自己的方式來解析代碼,使用一種比較寬鬆的向後兼容的方式來显示頁面。

    10.3 瀏覽器的渲染過程?

    10.3.1 開始進行DOM解析,渲染DOM Tree

    10.3.2 開始進行CSS解析,渲染CSSOM Tree

    10.3.3 DOM樹和CSS樹的結合,最後會轉換為Render Tree

    10.3.4 Layout的過程,計算每一個DOM元素的位置、寬度、高度等信息,最終渲染並显示頁面到瀏覽器

    10.4 何時會觸發Reflow?

    [!NOTE]
    定義:DOM結構中每個元素都有自己的盒子模型,這些都是需要根據各種樣式來計算並根據計算結果將元素放在它該出現的位置,這個過程就是reflow;

    1. 當你增加、刪除、修改DOM節點的時候,會導致Reflow或Repaint
    2. 當你移動DOM的位置,或者設置動畫的時候
    3. 當你修改CSS樣式的時候
    4. 當你Resize窗口的時候(移動端沒有這個問題,與瀏覽器有關),或者在滾動窗口的時候
    5. 當你修改網頁的默認的字體的時候

    10.5 何時回觸發Repaint?

    [!NOTE]
    定義:當各種盒子的位置、大小以及其他屬性,例如顏色、字體大小都確定下來以後,瀏覽器於是便按照元素各自的特性繪製了一遍,於是頁面的內容出現了,這個過程就是repaint

    1. DOM改動
    2. CSS改動

    10.6 如何最大程度上的減少瀏覽器的重繪Repaint過程(頻率)呢?

    10.6.1 避免在document上直接進行頻繁的DOM操作,如果確實需要可以採用off-document的方式進行

      1. 先將元素從document中刪除,完成修改之後然後再把元素放回原來的位置
      1. 將元素的display設置為none, 然後完成修改之後再把元素的display屬性修改為原來的值
      1. 如果需要創建多個DOM節點,可以使用DocumentFragment創建完畢之後一次性地加入document中去
      var frag = document.createDocumentFragment();
      frag.appendChild(dom);    /*每次創建的節點先放入DocumentFragment中*/

    10.6.2 集中修改樣式

    1. 盡可能少的修改元素style上的屬性
    2. 盡量通過修改className來修改樣式(一次性修改)
    3. 通過cssText屬性來設置樣式值
      document.getElementById("d1").style.cssText = "color:red; font-size:13px;";

    10.6.3 緩存Layout的屬性值

    [!NOTE]
    對於Layout屬性中非引用類型的值(数字型),如果需要多次訪問則可以在一次訪問時先存儲到局部變量中,之後都使用局部變量,這樣可以避免每次讀取屬性時造成瀏覽器的渲染。

      var width = el.offsetWidth;
      var scrollLeft = el.scrollLeft;

    10.6.4 設置元素的position為absolute或fixed

    [!NOTE]
    在元素的position為static和relative時,元素處於DOM樹結構當中,當對元素的某個操作需要重新渲染時,瀏覽器會渲染整個頁面。將元素的position設置為absolute和fixed可以使元素從DOM樹結構中脫離出來獨立的存在,而瀏覽器在需要渲染時只需要渲染該元素以及位於該元素下方的元素,從而在某種程度上縮短瀏覽器渲染時間。

    11. 布局Layout?

    Layout屬性包括

    1. offsetLeft、offsetTop、offsetHeight、offsetWidth: 相對於父對象的邊距信息,且返回值為数字;left獲取或設置相對於具有定位屬性(position定義為relative)的父對象的邊距信息,返回值為字符串10px
    2. scrollTop/Left/Width/Height:滾動條在各個方向上拉動的距離,返回值為数字
    3. clientTop/Left/Width/Height:瀏覽器的可視區域的大小
    4. getComputedStyle()、currentStyle(in IE):瀏覽器渲染DOM元素之後的寬度和高度等樣式信息

    12. JS運行機制

    12.1 如何理解JS的單線程?

    看代碼,寫結果?

      // 同步任務
      console.log(1);
      // 異步任務要掛起
      setTimeout(function(){
        console.log(2)
      }, 0);
      console.log(3)
      // out : 1 3 2
      console.log('A');
      setTimeout(function(){
        console.log('B')
      }, 0);
      while (true) {
    
      }
    
      // out : A

    12.2 什麼是任務隊列?

      for (var i = 0; i < 4; i++) {
        // setTimeout , setInterval 只有在時間到了的時候,才會把這個事件放在異步隊列中去
        setTimeout(function(){
          console.log(i);
        }, 1000);
      }
      // out : 4 4 4 4

    12.3 什麼是Event Loop?

    [!NOTE]
    JS是單線程的,瀏覽器引擎會先來執行同步任務,遇到異步任務之後,會把當前的這個異步任務放在time模塊中,等到主線程中的所有的同步任務全部執行完畢之後;然後當前的這個異步任務只有時間到了之後,才會把這個任務(回調函數)放在一個異步隊列中;噹噹前的任務棧中的任務全部執行完畢了之後,會先去執行微任務隊列中的任務(Promise),然後等到微任務隊列中的所有任務全部執行完畢之後,再去執行process.nextTick()這個函數,等到這個函數執行完畢之後,本次的事件輪訓結束;
    開啟新的執行棧,從宏任務隊列中依次取出異步任務,開始執行;每個宏任務執行都會重新開啟一個新的任務執行棧

    12.3.1 3個關鍵點

    1. 執行棧執行的是同步任務;
    2. 什麼時候去異步隊列中取這個任務;
    3. 什麼時候向這個任務隊列中放入新的異步任務

      12.3.2 異步任務的分類

    • setTimeout, setInterval;
    • DOM事件(點擊按鈕的時候也會先去執行同步任務);
    • Promise

    13. 知識點總結

    1. 理解JS的單線程的概念
    2. 理解任務隊列
    3. 理解Event Loop
    4. 理解哪些語句會翻入到異步任務隊列
    5. 理解與放入到異步任務隊列的時機

      13.1 頁面性能

      13.1.1 提升頁面性能的方法有哪些?

    6. 資源壓縮合併,減少HTTP請求;
    7. 非核心代碼的異步加載 —> 異步加載的方式有哪些? —> 異步加載的區別?
    8. 利用瀏覽器的緩存 —> 緩存的分類 —> 緩存的原理
    9. 使用CDN加速
    10. 預解析DNS:DNS Prefetch 是一種DNS 預解析技術,當你瀏覽網頁時,瀏覽器會在加載網頁時對網頁中的域名進行解析緩存,這樣在你單擊當前網頁中的連接時就無需進行DNS的解析,減少用戶等待時間,提高用戶體驗。(提前解析域名,而不是點擊鏈接的時候才去進行DNS域名解析,可以節省DNS解析需要耗費的20-120毫秒時間)

      <!-- https協議的網站,默認是關閉了DNS的預解析的,可以使用下面的語句開啟  -->
      <meta http-equiv="x-dns-prefetch-control" content="on">
      <!-- 開始配置需要進行DNS預解析的域名 -->
      <link rel="dns-prefetch" href="//www.zhix.net">                               <!--支持http和HTTPS-->
      <link rel="dns-prefetch" href="http://bdimg.share.baidu.com" />               <!--支持http的協議-->
      <link rel="dns-prefetch" href="http://nsclick.baidu.com" />
      <link rel="dns-prefetch" href="http://hm.baidu.com" />
      <link rel="dns-prefetch" href="http://eiv.baidu.com" />

    14. 異步加載的方式

    14.1 動態腳本的加載

      var script = document.createElement('script');
      document.getElementsByTagName('head')[0].appendChild(script);
    
      // 沒有 defer 或 async,瀏覽器會立即加載並執行指定的腳本,“立即”指的是在渲染該 script 標籤之下的文檔元素之前,也就是說不等待後續載入的文檔元素,讀到就加載並執行。
      <script src="script.js"></script>

    14.2 defer

    <!-- 有 defer,加載後續文檔元素的過程將和 script.js 的加載并行進行(異步),但是 script.js 的執行要在所有元素解析完成之後,DOMContentLoaded 事件觸發之前完成。 -->
    <script defer src="myscript.js"></script>

    14.3 async

      <!-- 有 async,加載和渲染後續文檔元素的過程將和 script.js 的加載與執行并行進行(異步)。 -->
      <script async src="script.js"></script>

    14.4 異步加載的區別?

    [!NOTE]

    1. defer是在HTML解析完成之後(DOMContentLoaded事件執行之後)才會執行,如果是多個,會按照加載的順序依次執行(按照順序執行)
    2. async是在加載完之後立即執行,如果是多個,執行順序和加載順序無關(與順序無關)

    15. 說一下瀏覽器的緩存機制吧?

    15.1 緩存的分類

    [!NOTE]
    緩存目的就是為了提升頁面的性能

    15.1.1 強緩存

    直接從本地讀取,不發送請求

        Response Headers
        cache-control: max-age=315360000(相對時間,優先級比expires高)
        expires: Sat, 10 Mar 2029 04:01:39 GMT(絕對時間)

    15.1.2 協商緩存

    問一下服務器,這個文件有沒有過期,然後再使用這個文件

        Response Headers
        last-modified: Tue, 12 Mar 2019 06:22:34 GMT(絕對時間)
        etag: "52-583dfb6f4de80"

    向服務器請求資源的時候,帶上if-Modified-Since或者if-None-Match這個請求頭,去詢問服務器:

        Request Headers
        if-Modified-Since: Tue, 12 Mar 2019 06:22:34 GMT
        if-None-Match: "52-583dfb6f4de80"

    16. 錯誤監控/如何保證前端產品的上線質量?

    16.1 前端錯誤的分類?

    1. 即時運行錯誤:代碼錯誤
    2. 資源加載錯誤:圖片/css/js文件加載失敗

    16.2 錯誤的捕獲方式?

    16.2.1 即時運行錯誤的捕獲方式

      // 方法一:使用try catch捕獲
      try {
        // ...
      } catch (e) {
        // error
      } finally {
        // handle error
      }
    
      // 方法二:使用window.onerror 捕獲錯誤
      // 無法捕獲到資源加載錯誤
      window.onerror = function(msg, url, line, col, error){
        // ...
      }  
      window.addEventListener('error', function(msg, url, line, col, error){
        // ...
      })

    16.2.2 資源加載錯誤(不會向上冒泡)

      // 方法一: 直接在script, img這些DOM標籤上面直接加上onerror事件
      Object.onerror = function(e){
          // ...
      }
    
      // 方法二:window.performace.getEntries(間接獲取資源加載錯誤的數量)
      var loadedResources = window.performance.getEntries();           // 1. 獲取瀏覽器中已經加載的所有資源(包括各個階段的詳細加載時間)
      var loaderImgs = loadedResources.filter(item => {
          return /\.jpg|png|gif|svg/.test(item.name)
      });
      var imgs = document.getElementsByTagName('img');                // 2. 獲取頁面中所有的img集合
      var len = imgs.length - loaderImgs.length;                      // 3. 加載失敗的圖片數量
      console.log('圖片加載失敗數量:', len, '條');
    
    
      // 方法三: 使用事件捕獲的方式來實現Error事件捕獲
      // 使用事件捕獲的方式來實現資源加載錯誤的事件的捕獲:window ---> document --> html --- > body ---> div ---...
      window.addEventListener('error', function (msg) {
          console.log(msg);
      }, true);

    16.2.3 補充的方法

          // 使用事件捕獲的方式來實現
         window.addEventListener('error', function (msg) {
             console.log('資源加載異常成功捕獲:', msg);
         }, true);
         // 使用事件冒泡的方式是只能捕獲到運行的時候的一些異常
         window.addEventListener('error', function (e) {
             console.log('運行異常成功捕獲1:', e.message, e.filename, e.lineno, e.colno, e.error);
         }, false);
    
         // 這種方式是可以按照參數的方式來接受相關的參數信息
         window.onerror = function (msg, url, line, col, error) {
             console.log('運行異常成功捕獲2:', msg, url, line, col, error);
         }

    16.2.4 問題的延伸:跨域的js運行錯誤可以捕獲嗎,錯誤提示是什麼?應該怎麼處理呢?

    16.2.4.1 錯誤信息

    errorinfo :
    Script0 error
    0 row
    0 col
    16.2.4.2 處理方法
    1. 第一步:在script標籤上增加crossorigin屬性
      <!-- script 表情添加crossorigin屬性 -->
      <!-- 除了 script,所有能引入跨域資源的標籤包括 link 和 img 之類,都有一樣的屬性 -->
      <script crossorigin  src="http://www.lmj.com/demo/crossoriginAttribute/error.js"></script>
    1. 第二步:設置js資源響應頭’Access-Control-Allow-Origin: * ‘,服務器端需要開啟
      // 服務器可以直接設置一個響應頭信息
      res.setResponseHeader('Access-Control-Allow-Origin', 'www.lmj.com');

    16.3 上報錯誤的基本原理?

    1. 採用Ajax通信的方式來上報
    2. 利用Image對象進行上報(cnzz)[重點理解掌握]
      // 下面的兩種方式都是可以實現錯誤信息的上報功能的
      (new Image).src = 'http://www.baidu.com?name=zhangsna&age=18&sex=male'
      (new Image()).src = 'https://www.baidu.com?name=zhangsan'

    17. 如何使用JS獲取客戶端的硬件信息呢?

      // IE 瀏覽器提供的獲取電腦硬件的API
      var locator = new ActiveXObject ("WbemScripting.SWbemLocator");
      var service = locator.ConnectServer(".");
      var properties = service.ExecQuery("SELECT * FROM Win32_Processor");

    18. 使用window.performace 來實現用戶體驗的數據記錄呢?

    [!NOTE]
    可以參考性能優化章節-performance性能監控一文內容。

    三四面(業務項目面)

    [!NOTE]

    • 知識面要廣
    • 理解要深刻
    • 內心要誠實:沒了解過,問面試官有哪些資料可以學習
    • 態度要謙虛
    • 回答要靈活:把握一個度,不要和面試官爭執對錯
      • 要學會讚美:被問住了可以回答,適當讚美(沒面試官理解的那麼深,虛心請教)

    19.介紹一下你做過的項目?

    19.1 項目介紹模板(業務能力體現)

    1. 我做過什麼業務?
    2. 負責的業務有什麼業績?
    3. 使用了什麼技術方案?
    4. 突破了什麼技術難點?
    5. 遇到了什麼問題?
    6. 最大的收穫是什麼?

    19.2 團隊協作能力

    19.3 事務推動能力

    19.4 帶人能力

    終面(HR面)

    20. 技術終面或HR面試要點

    [!NOTE]
    主要考察點:樂觀积極、主動溝通、邏輯順暢、上進有責任心、有主張,做事果斷、職業競爭力、職業規劃

    20.1 職業競爭力

    1. 業務能力:可以做到行業第一

    2. 思考能力:對同一件事可以從不同角度去思考,找到最優解

    3. 學習能力:不斷學習新的業務,沉澱、總結

    4. 無上限的付出:對於無法解決的問題可以熬夜、加班

    20.2 職業規劃

    1. 目標是什麼:在業務上成為專家,在技術上成為行業大牛

    2. 近階段的目標:不斷的學習積累各方面地經驗,以學習為主

    3. 長期目標:做幾件有價值的事情,如開源作品、技術框架等

    4. 方式方法:先完成業務上的主要問題,做到極致,然後逐步向目標靠攏

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • Spring框架學習總結(上)

    Spring框架學習總結(上)

    目錄

    @

    1、Spring的概述

    在學習SSM框架中,我建議初學者最好先學Spring框架,其次mybatis接着springMVC,先學mybatis當然也是可以的,今天我們就以絕對優雅的姿態闖進Spring世界,系好安全帶,準備好了嗎,出發了哦!!!咳咳….平時開發接觸最多的估計就是IOC容器,它可以裝載bean(所謂的bean也就是我們java中的類,當然也包括servicedao裏面),有了這個機制,我們就不用在每次使用這個類的時候為它初始化,很少看到鍵字new。另外spring的aop,事務管理等等都是我們經常用到的,可見Spring的尤為重要的作用Spring的核心是控制反轉(IoC)和面向切面(AOP)

    1.1什麼是Spring

    肯定有熊dei會問SE/EE開發的一站式框架所謂的一站式是什麼意思,(哼,人類,我早就猜到你會問了)
    所謂一站式框架指的是有EE開發的每一層解決方案。

    WEB層 :SpringMVC

    Service層 :Spring的Bean管理,Spring聲明式事務

    DAO層 :Spring的Jdbc模板,Spring的ORM模塊

    1.2為什麼學習Spring

    俗話說,人狠話不多(兄嘚看圖)

    1.3Spring的版本

    Spring3.x、Spring4.x和Spring5.x

    1.4Spring的體繫結構

    正所謂,人狠話不多(兄嘚看圖)

    2、Spring的入門(IOC)

    2.1什麼IOC

    一說起IOC我就想起了武哥對IOC的理解的幾個例子,可謂通俗易懂,非常適合剛入門Spring的兄嘚!有興趣的可以去了解了解武哥,武哥博客:https://blog.csdn.net/eson_15

    IOC(Inverse of Control):控制反轉,也可以稱為依賴倒置。
    控制反轉:將對象的創建權反轉給(交給)Spring。

    所謂依賴,從程序的角度看,就是比如A要調用B的方法,那麼A就依賴於B,反正A要用到B,則A依

    賴於B。所謂倒置,你必須理解如果不倒置,會怎麼著,因為A必須要有B,才可以調用B,如果不倒

    置,意思就是A主動獲取B的實例:B b = new B(),這就是最簡單的獲取B實例的方法(當然還有各種

    設計模式可以幫助你去獲得B的實例,比如工廠、Locator等等),然後你就可以調用b對象了。所

    以,不倒置,意味着A要主動獲取B,才能使用B;到了這裏,就應該明白了倒置的意思了。倒置就是

    A要調用B的話,A並不需要主動獲取B,而是由其它人自動將B送上門來。

    2.2通俗理解IOC

    形象的舉例就是:
    通常情況下,假如你有一天在家裡口渴了,要喝水,那麼你可以到你小區的小賣部去,告訴他們,你需要一瓶水,然後小賣部給你一瓶水!這本來沒有太大問題,關鍵是如果小賣部很遠,那麼你必須知道:從你家如何到小賣部;小賣部里是否有你需要的水;你還要考慮是否開着車去;等等等等,也許有太多的問題要考慮了。也就是說,為了一瓶水,你還可能需要依賴於車等等這些交通工具或別的工具,問題是不是變得複雜了?那麼如何解決這個問題呢?
    解決這個問題的方法很簡單:小賣部提供送貨上門服務,凡是小賣部的會員,你只要告知小賣部你需要什麼,小賣部將主動把貨物給你送上門來!這樣一來,你只需要做兩件事情,你就可以活得更加輕鬆自在:
    第一:向小賣部註冊為會員。
    第二:告訴小賣部你需要什麼。

    這和Spring的做法很類似!Spring就是小賣部,你就是A對象,水就是B對象
    第一:在Spring中聲明一個類:A
    第二:告訴Spring,A需要B

    假設A是UserAction類,而B是UserService類

    <bean id="userService" class="org.leadfar.service.UserService"/>
    <bean id="documentService" class="org.leadfar.service.DocumentService"/>
    <bean id="orgService" class="org.leadfar.service.OrgService"/>
     
    <bean id="userAction" class="org.leadfar.web.UserAction">
         <property name="userService" ref="userService"/>
    </bean>

    在Spring這個商店(工廠)中,有很多對象/服務:userService,documentService,orgService,也有很多會員:userAction等等,聲明userAction需要userService即可,Spring將通過你給它提供的通道主動把userService送上門來,因此UserAction的代碼示例類似如下所示:

    package org.leadfar.web;
    public class UserAction{
         private UserService userService;
         public String login(){
              userService.valifyUser(xxx);
         }
         public void setUserService(UserService userService){
              this.userService = userService;
         }
    }
    

    在這段代碼裏面,你無需自己創建UserService對象(Spring作為背後無形的手,把UserService對象通過你定義的setUserService()方法把它主動送給了你,這就叫依賴注入!),當然咯,我們也可以使用註解來注入。Spring依賴注入的實現技術是:動態代理

    2.3下載Spring的開發包以及解壓說明

    官網下載:http://spring.io/
    什麼?不會下載?what???
    好吧,已打包好了QAQ:https://pan.baidu.com/s/18wyE-5SRWcCu12iPOX56pg
    什麼?沒有網盤?what???
    有事請燒香謝謝…

    解壓之後,文件說明:
    docs :Spring的開發規範和API
    libs :Spring的開發的jar和源碼
    schema :Spring的配置文件的約束

    2.4創建web項目,引入jar包

    2.5創建普通接口和實現類

    創建普通接口,定義一個eat方法

    package com.gx.sping;
    
    public interface IUserDao {
    
        public void eat();
    }
    

    創建普通實現類

    package com.gx.sping;
    
    public class UserDaoimpl implements IUserDao {
      @Override
        public void eat() {
            // TODO Auto-generated method stub
            System.out.println(用戶eat了");
        }
    }
    

    2.6Spring的IOC底層實現原理

    創建普通接口和類出現的問題:
    如果底層的實現切換了,需要修改源代碼,能不能不修改程序源代碼對程序進行擴展?
    重點來了,要想不改變源碼,Spring的IOC就能實現!如下圖:Spring的IOC底層實現

    2.7將實現類交給Spring管理

    1、在classpath下(也就是src)創建一個XML文件

    2、文件名最好統一叫applicationContext.xml

    3、其xml文件的內容頭為schema約束

    4、約束文件位置在spring的解壓路徑下lspring-framework-4.2.4.RELEASE\docs\spring-framework-reference\html\xsd-configuration.htm

    5、不要求xml文件的內容頭能夠背出來,但要了解的是你要知道它是怎麼來的

    6、xml文件的內容頭添加后,將實現類交給Spring管理

    applicationContext.xml配置文件如下

    <?xml version="1.0" encoding="UTF-8"?>
    <beans 
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans 
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
     <!-- 實現類UserDaoimpl交給Spring管理 -->
         <bean id="IuserDao" class="com.gx.Ioc.UserDaoimpl" ></bean>
    </beans>

    2.8編寫測試類

    package com.gx.Ioc;
    
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class SpingDemo1 {
        @Test
        public void demo11() {
            // 面向接口傳統方式
            UserDaoimpl userdao = new UserDaoimpl();
            userdao.eat();
        }
           //Spring的bean管理方式
        @Test
        public void demo22() {
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            IUserDao userdao = (IUserDao) applicationContext.getBean("IuserDao");
            userdao.eat();
    
        }
    
    }

    兄嘚,如果測試不成功最好看看二者是否對應!!!

    兄dei,到這裏,Spring的入門(IOC)算是入門了,是不是覺得很有成就感啊?

    拉倒吧! 我都不好意思說了.(兄dei,我錯了,是我飄了,呀呀呀,兄dei別打臉鴨QAQ)

    但是我依舊是阻止不了你驕傲的心.

    那就頂我,讓我感受感受你的驕傲!哈哈哈QAQ

    2.9 IOC和DI

    IOC不是什麼技術,而是一種設計思想,IOC能指導我們如何設計出松耦合、更優良的程序。傳統應用程序都是由我們在類內部主動創建依賴對象,從而導致類與類之間高耦合,難於測試;有了IoC容器后,把創建和查找依賴對象的控制權交給了Spring容器,由容器進行注入組合對象,所以對象與對象之間是鬆散耦合,這樣利於功能復用,更重要的是使得程序的整個體繫結構變得非常靈活。
    IOC:控制反轉,將對象的創建權反轉給了Spring。
    DI:依賴注入,前提必須有IOC的環境,Spring管理這個類的時候將類的依賴的屬性注入(設置)進來。比如說下面講到的Spring的屬性注入其實就是典型的DI

    所謂繼承:is a

    Class A{
    
    }
    Class B extends A{
    
    }

    所謂依賴:

    Class A{
    
    }
    Class B{
        public void xxx(A a){
    
    }
    }
    

    所謂聚合:has a

    3、Spring的工廠類

    3.1Spring工廠類的結構

    3.2老版本的工廠類:BeanFactory

    BeanFactory:調用getBean的時候,才會生成類的實例。

    3.3新版本的工廠類:ApplicationContext

    ApplicationContext:加載配置文件的時候,就會將Spring管理的類都實例化
    ApplicationContext有兩個實現類
    1、ClassPathXmlApplicationContext :加載類路徑下的配置文件
    2、FileSystemXmlApplicationContext :加載文件系統下的配置文件

    4、Spring的配置

    4.1XML的提示配置(Schema的配置)

    在XML文件中要使用各種標籤來給spring進行配置,博主我這佩奇腦袋怎麼可能記住spring中所有的標籤呢,不怕不怕,博主我會配置XML的提示配置QAQ,會了這一招就算兄dei你是喬治腦袋也不用擔心(再說了我看兄dei各各英俊瀟洒,玉樹臨風,聰明絕頂…咳咳,暴露了暴露了)

    4.2Bean的相關的配置(< bean >標籤的id和name的配置)

    id :使用了約束中的唯一約束。裏面不能出現特殊字符的。上面提及到了要與getbean參數值對應

    name :沒有使用約束中的唯一約束(理論上可以出現重複的,但是實際開發不能出現的)。裏面可以出現特殊字符。

    4.3Bean的生命周期的配置(了解)

    init-method :Bean被初始化的時候執行的方法
    destroy-method :Bean被銷毀的時候執行的方法(Bean是單例創建,工廠關閉)

    4.4Bean的作用範圍的配置(重點)

    scope屬性Bean的作用範圍

    scope屬性值如下(主要用的是前二者)
    singletonscope屬性的默認值,Spring會採用單例模式創建這個對象。
    prototype多例模式。(Struts2和Spring整合一定會用到)
    request :應用在web項目中,Spring創建這個類以後,將這個類存入到request範圍中。
    session :應用在web項目中,Spring創建這個類以後,將這個類存入到session範圍中。
    globalsession :應用在web項目中,必須在porlet環境下使用。但是如果沒有這種環境,相對於session。

    5、Spring的屬性注入

    首先,創建幾個普通類

    com.gx.spring.demo.Car
    public class Car {
        private String name;
        private Double price;
        
        public Car(String name, Double price) {
            super();
            this.name = name;
            this.price = price;
        }
        @Override
        public String toString() {
            return "Car [name=" + name + ", price=" + price + "]";
        }
    }
    com.gx.spring.demo.Car2
    /**
     * 用作set方法的屬性注入類
     */
    public class Car2 {
        private String name;
        private Double price;
        public void setName(String name) {
            this.name = name;
        }
        public void setPrice(Double price) {
            this.price = price;
        }
        @Override
        public String toString() {
            return "Car2 [name=" + name + ", price=" + price + "]";
        }
        
    }
    
    com.gx.spring.demo.Person 
    /**
     * 用作set方法的對象屬性注入類
     */
    public class Person {
        private String name;
        private Car2 car2;
        public void setName(String name) {
            this.name = name;
        }
        public void setCar2(Car2 car2) {
            this.car2 = car2;
        }
        @Override
        public String toString() {
            return "Employee [name=" + name + ", car2=" + car2 + "]";
        }
    }
    

    5.1構造方法的方式的屬性注入

    構造方法的屬性注入
    constructor-arg 標籤用於配置構造方法的屬性注入
    name :參數的名稱
    value:設置普通數據
    ref:引用數據,一般是另一個bean id值

    當然了,構造方法的方式的屬性注入也支持對象屬性的注入,標籤中對應屬性也是ref
    如果只有一個有參數的構造方法並且參數類型與注入的bean類型匹配,那就會注入到該構造方法中

    applicationContext.xml中配置:

    <!-- 構造方法的方式 -->
        <bean id="car" class="com.gx.spring.demo.Car">
            <constructor-arg name="name" value="瑪莎拉蒂"/>
            <constructor-arg name="price" value="800000"/>
        </bean>

    測試方法:

        /**
         * 構造方法方式的普通屬性注入方法
         */
        public void demo1(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Car car = (Car) applicationContext.getBean("car");
            System.out.println(car);
        }

    5.2Set方法的方式的屬性注入【開發常用】

    Set方法的普通屬性注入
    property 標籤用於配置Set方法的屬性注入
    name :參數的名稱
    value:設置普通數據
    ref:引用數據,一般是另一個bean id值

    applicationContext.xml中配置:

    <!-- set方法的方式 -->
    <bean id="car2" class="com.gx.spring.demo.Car2">
            <property name="name" value="法拉利黃金跑車"/>
            <property name="price" value="10000000"/>
    </bean> 

    測試方法:

    @Test
        /**
         * set方法方式的屬性注入
         */
        public void demo2(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Car2 car2 = (Car2) applicationContext.getBean("car2");
            System.out.println(car2);
        }

    Set方法設置對象類型的屬性
    applicationContext.xml中配置:

    <!-- set方法注入對象類型的屬性 -->
    <bean id="Person" class="com.gx.spring.demo.Person">
                <!-- value:設置普通類型的值,ref:設置其他的類的id或name-->
            <property name="name" value="濤哥"/>
            <property name="car2" ref="car2"/>
        </bean> 

    測試方法:

    @Test
        /**
         * set方法注入對象類型
         */
        public void demo3(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
            Person person= (Person) applicationContext.getBean("Person");
            System.out.println(person);
        }

    5.3註解的方式屬性注入【開發常用】

    @Component (作用在類上通用:組件)
    @Component(“userService”)相當於< bean id=”userService” class=”…”>

    衍生:
    @Controller Web層
    @Service 業務層
    @Repository 持久層
    這三個註解是為了讓標註類本身的用途清晰

    屬性注入的註解 ( 可以沒有set方法
    普通類型屬性:@Value

    對象類型屬性:@Resource對應bean中的id)= @Autowired(類型)+ @Qualifier(名稱)

    5.3.1註解的理解

    額,初學框架,註解二字可能對於大部分熊dei來說,太過於陌生,註解其實就是在一個類、方法、屬性上,使用@註解名稱,就比如是我們最熟悉的接實現口中的方法默認會有一個 @Override (熊dei,這樣理解能接受?)

    5.3.2註解的jar包導入

    Spring3.x註解的jar包
    在Spring3.x的版本中,使用註解開發,只需要 spring核心基礎四包外 + log4j包 + 1個依賴包 即可

    Spring4.x註解的jar包
    然而在Spring4.x版本之後則需在 再添加一個要引入 spring-aop 的 jar 包,因為,spring4.x版本中一些註解的依賴方法封裝在spring-aop 的 jar 包中

    5.3.3引入註解的context約束

    所謂約束就是就是就是約束啦(搽汗),其中bean約束是最基本的約束!(下圖也可以看出)

    引入約束:(引入 context 的約束):

    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
    http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/context 
    http://www.springframework.org/schema/context/spring-context.xsd">
    </beans>
    5.3.4編寫相關的類
    public interface UserDao {
    public void sayHello();
    }
    public class UserDaoImpl implements UserDao {
    @Override
    public void sayHello() {
    System.out.println("Hello Spring...");
    } }
    5.3.5配置註解掃描

    Spring的註解開發:組件掃描(不使用類上註解的時候可以不用組件掃描
    使用註解方式,需要開啟組件掃描< context:component-scan base-package=直接包名或者包名.類名/>,當然開發中一般都是base-package=包名,畢竟這樣可以掃描整個包,方便開發
    Spring 的註解開發:組件掃描(類上註解: 可以直接使用屬性注入的註解)

    <!-- Spring 的註解開發:組件掃描(類上註解: 可以直接使用屬性注入的註解) -->
    <context:component-scan base-package="com.gx.spring.demo1"/>
    5.3.6 在相關的類上添加註解

    1、使用類上註解方式@Component(value=“userDao”),相當於< bean id=”userDao class=”com.gx.類名”>< /bean>
    當然value屬性名可以省去直接@Component(”userDao”),當然@Component(“value值任意寫建議取的要有意義”)
    2、註解方式可以沒有set方法

    @Component(value="userDao")  //相當於配置了<bean id="userDao" class="com.gx.UserDaoImpl "></bean>
    public class UserDaoImpl implements UserDao {
    @Override
    public void sayHello() {
    System.out.println("Hello Spring Annotation...");
    } }
    5.3.7 編寫測試類
    @Test
    public void demo3() {
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = (UserDao) applicationContext.getBean("userDao");
    userDao.sayHello();
    }

    5.4P名稱空間的屬性注入(Spring2.5以後)

    通過引入p名稱空間完成屬性的注入:
    寫法:
    普通屬性 p:屬性名=”值”
    對象屬性 p:屬性名-ref=”值”

    P名稱空間的約束引入

    使用p名稱空間

    5.5 SpEL的屬性注入(Spring3.0以後)

    SpEL:Spring Expression Language,Spring的表達式語言。
    語法: #{SpEL}

    5.6集合類型屬性注入(了解)

    集合類型屬性配置:
    集合的注入都是在< property>標籤中添加子標籤
    數組:< array >
    List:< list >
    Set:< set >
    Map:< map > ,map存放k/v 鍵值對,使用 描述
    Properties:< props> < prop key=””>< /prop>
    普通數據:< value >
    引用數據:< ref >

        <!-- Spring的集合屬性的注入============================ -->
        <!-- 注入數組類型 -->
        <bean id="collectionBean" class="com.gx.spring.demo.CollectionBean">
            <!-- 數組類型 -->
            <property name="arrs">
                <list>
                    <value>天才</value>
                    <value>王二</value>
                    <value>冠希</value>
                </list>
            </property>
            
            <!-- 注入list集合 -->
            <property name="list">
                <list>
                    <value>李兵</value>
                    <value>趙如何</value>
                    <value>鄧鳳</value>
                </list>
            </property>
            
            <!-- 注入set集合 -->
            <property name="set">
                <set>
                    <value>aaa</value>
                    <value>bbb</value>
                    <value>ccc</value>
                </set>
            </property>
            
            <!-- 注入Map集合 -->
            <property name="map">
                <map>
                    <entry key="aaa" value="111"/>
                    <entry key="bbb" value="222"/>
                    <entry key="ccc" value="333"/>
                </map>
            </property>
        </bean>

    6、Spring的分模塊開發的配置

    分模塊配置:
    在加載配置文件的時候,加載多個,沒錯,這就是傳說中的騷操作,堪稱開掛級別的操作(當然,這是可以的不是開掛)

    在一個配置文件中引入多個配置文件,簡直優秀!!!

    到這裏,恭喜恭喜,各位熊dei以優雅的儀式感闖進Spring世界,對Spring的IOC以及DI有了一定了解了,是不是也很期待Spring的Aop吶,畢竟Spring的核心是控制反轉(IOC)和面向切面(AOP)。

    (哎哎,別打..別打..別打臉….)

    如果本文對你有一點點幫助,就請點個讚唄,手留余香,謝謝!

    最後,歡迎各位關注我的公眾號,一起探討技術,嚮往技術,追求技術…

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • 阿里雲ECS服務器部署HADOOP集群(一):Hadoop完全分佈式集群環境搭建,阿里雲ECS服務器部署HADOOP集群(二):HBase完全分佈式集群搭建(使用外置ZooKeeper),阿里雲ECS服務器部署HADOOP集群(三):ZooKeeper 完全分佈式集群搭建,阿里雲ECS服務器部署HADOOP集群(四):Hive本地模式的安裝,阿里雲ECS服務器部署HADOOP集群(六):Flume 安裝,阿里雲ECS服務器部署HADOOP集群(七):Sqoop 安裝

    阿里雲ECS服務器部署HADOOP集群(一):Hadoop完全分佈式集群環境搭建,阿里雲ECS服務器部署HADOOP集群(二):HBase完全分佈式集群搭建(使用外置ZooKeeper),阿里雲ECS服務器部署HADOOP集群(三):ZooKeeper 完全分佈式集群搭建,阿里雲ECS服務器部署HADOOP集群(四):Hive本地模式的安裝,阿里雲ECS服務器部署HADOOP集群(六):Flume 安裝,阿里雲ECS服務器部署HADOOP集群(七):Sqoop 安裝

    準備:

    兩台配置CentOS 7.3的阿里雲ECS服務器;

    hadoop-2.7.3.tar.gz安裝包;

    jdk-8u77-linux-x64.tar.gz安裝包;

    hostname及IP的配置:

    更改主機名:

    由於系統為CentOS 7,可以直接使用‘hostnamectl set-hostname 主機名’來修改,修改完畢后重新shell登錄或者重啟服務器即可。

    1 hostnamectl set-hostname master
    2 exit
    3 ssh root@master
    1 hostnamectl set-hostname slave1
    2 exit 3 ssh root@slave1

    設置本地域名:

    設置本地域名這一步非常關鍵,ip的本地域名信息配置不好,即有可能造成Hadoop啟動出現問題,又有可能造成在使用Hadoop的MapReduce進行計算時報錯。在ECS上搭建Hadoop集群環境需參考以下兩篇文章:

    總結一下那就是,在“/etc/hosts”文件中進行域名配置時要遵從2個原則:

    •  新加域名在前面: 將新添加的Master、Slave服務器ip域名(例如“test7972”),放置在ECS服務器原有本地域名(例如“iZuf67wb***************”)的前面。但是注意ECS服務器原有本地      域名(例如“iZuf67wb***************”)不能被刪除,因為操作系統別的地方還會使用到。
    •  IP本機內網,其它外網: 在本機上的操作,都要設置成內網ip;其它機器上的操作,要設置成外網ip。

    master

    slave1

    此處摘自 

    配置好后需要在各個節點上執行如下命令,測試是否相互 ping 得通,如果 ping 不通,後面就無法順利配置成功:

    1 ping master -c 3
    2 ping slave1 -c 3

    例如我在 master 節點上 ping slave1 ,ping 通的話會显示 time,显示的結果如下圖所示:

    各節點角色分配

    master: NameNode  ResourceManager

    slave1: DataNode NodeManager

    免密碼登錄配置

    分別在 master 和 slave1 上做如下操作

    1 ssh-keygen -t rsa
    2 ssh-copy-id master 3 ssh-copy-id slave1

    驗證

    ssh master date;ssh slave1 date

    配置JDK

    解壓JDK安裝包到/usr/local/下

    tar -zxvf jdk-8u77-linux-x64.tar.gz -C /usr/local/

    將解壓目錄改為 jdk1.8

    mv jdk1.8.0_77/ jdk1.8/

    設置JAVA_HOME到系統環境變量

    vim /etc/profile

    在最後加入以下兩行代碼

    export JAVA_HOME=/usr/local/jdk1.8
    export PATH=$PATH:$JAVA_HOME/bin

    重新加載環境

    source /etc/profile

    這樣 master 的jdk就配置好了,可以用命令 java -version 測試下。

    java -version

    下面只需將 master 上配置好的文件分發到 slave1 上即可。

    將/usr/local/jdk1.8分發到 slave1 的/usr/local/下(建議壓縮后再分發)

    scp -r /usr/local/jdk1.8/ slave1:/usr/local/

    將/etc/profile分發到 slave1 的/etc/下

    scp /etc/profile slave1:/etc/

      然後重新加載 slave1 環境便完成了 slave1 的jdk配置

    source /etc/profile

    hadoop集群配置

    1 cd ~
    2 tar -zxvf hadoop-2.7.3.tar.gz -C /usr/local # 解壓到/usr/local中
    3 cd /usr/local/
    4 mv ./hadoop-2.7.3/ ./hadoop            # 將文件夾名改為hadoop

    輸入如下命令來檢查 Hadoop 是否可用,成功則會显示 Hadoop 版本信息:

    1 cd /usr/local/hadoop
    2 ./bin/hadoop version

    添加 HADOOP_HOME 到系統環境變量

    vim /etc/profile

    在後面添加如下兩行

    1 export HADOOP_HOME=/usr/local/hadoop
    2 export PATH=$PATH:$HADOOP_HOME/bin:$HADOOP_HOME/sbin

    重新加載環境,並輸出變量 HADOOP_HOME 驗證

    進入/user/local/hadoop/etc/hadoop/可以看到如下配置文件

    集群/分佈式模式需要修改 /usr/local/hadoop/etc/hadoop 中的6個配置文件,更多設置項可點擊查看官方說明,這裏僅設置了我完成課堂作業所必須的設置項:hadoop-env.sh, slaves,  ,  ,  ,   。

    1.首先來配置 hadoop-env.sh ,只需要設置一下JAVA_HOME即可

    注:之前在配置jdk中配置的是基於系統的JAVA_HOME變量,這裏需要配置基於Hadoop集群的JAVA_HOME變量。

    hadoop-env.sh 是Hadoop的環境變量配置腳本。

    所以應做以下修改 vim hadoop-env.sh

    export JAVA_HOME=/usr/local/jdk1.8

    2.配置 slave , 指定 slave 節點

    sudo vi slaves

    刪去原有的 localhost , 添加將作為 slave 節點的 slave1

    3.配置 core-site.xml 

     1 <configuration>
     2 
     3     <property>
     4         <name>fs.defaultFS</name>
     5         <value>hdfs://master:9000</value>
     6         <description>The name of the default file system.</description>
     7     </property> 
     8 # 設置訪問hdfs的默認名,9000是默認端口
     9 
    10     <property>
    11         <name>hadoop.tmp.dir</name>
    12         <value>/usr/local/hadoop/tmp</value>
    13         <description>Abase for other temporary directories.</description>
    14     </property>
    15 # 在hdfs格式化的時候會自動創建相應的目錄 'tmp/' 16 17 <property> 18 <name>fs.trash.interval</name> 19 <value>4320</value> 20 <description>Number of minutes after which the checkpoint gets deleted.</description> 21 </property> 22 # 設置回收站里的文件保留時間(單位:秒) 23 24 </configuration>

    4.配置 hdfs-site.xml 

     1 <configuration>
     2 
     3     <property>
     4         <name>dfs.namenode.name.dir</name>
     5         <value>/usr/local/hadoop/tmp/dfs/name</value>
     6     </property>
     7 
     8     <property>
     9         <name>dfs.datanode.data.dir</name>
    10         <value>/usr/local/hadoop/tmp/dfs/data</value>
    11     </property>
    12 
    13     <property>
    14         <name>dfs.replication</name>
    15         <value>1</value>
    16     </property>
    17 # 副本,因為有一個 slave 節點這裏設置為1(一般偽分佈模式設1個,三個或三個以上節點設3個)
    18 
    19     <property>
    20         <name>dfs.permissions.enabled</name>
    21         <value>false</value>
    22         <description>If "true", enable permission checking in HDFS. If "false", permission checking is turned off, but all other behavior is unchanged. Switching from one parameter value to the other does not change the mode, owner or group of files or directories.</description>
    23     </property>
    24 
    25 </configuration>    

    5.配置 mapred-site.xml (這個文件沒有直接提供,而是提供了模版文件,需將模版文件轉換為配置文件) 

    1 sudo mv mapred-site.xml.template mapred-site.xml
    2 sudo vi mapred-site.xml
     1 <configuration>
     2 
     3     <property>
     4         <name>mapreduce.framework.name</name>
     5         <value>yarn</value>
     6         <description>The runtime framework for executing MapReduce jobs.Can be one of local, classic or yarn.</description>
     7     </property>
     8     <property>
     9         <name>mapreduce.jobtracker.http.address</name>
    10         <value>master:50030</value>
    11     </property>
    12     <property>
    13         <name>mapreduce.jobhisotry.address</name>
    14         <value>master:10020</value>
    15     </property>
    16     <property>
    17         <name>mapreduce.jobhistory.webapp.address</name>
    18         <value>master:19888</value>
    19     </property>
    20     <property>
    21         <name>mapreduce.jobhistory.done-dir</name>
    22         <value>/jobhistory/done</value>
    23     </property>
    24     <property>
    25         <name>mapreduce.jobhistory.intermediate-done-dir</name>
    26         <value>/jobhisotry/done_intermediate</value>
    27     </property>
    28     <property>
    29         <name>mapreduce.job.ubertask.enable</name>
    30         <value>true</value>
    31         <description>Whether to enable the small-jobs "ubertask" optimization,which runs "sufficiently small" jobs sequentially within a single JVM."Small" is defined by the following maxmaps, maxreduces, and maxbytes settings. Note that configurations for application masters also affect the "Small" definition - yarn.app.mapreduce.am.resource.mb must be larger than both mapreduce.map.memory.mb and mapreduce.reduce.memory.mb, and yarn.app.mapreduce.am.resource.cpu-vcores must be larger than both mapreduce.map.cpu.vcores and mapreduce.reduce.cpu.vcores to enable ubertask. Users may override this value.</description>
    32     </property>
    33 
    34 </configuration>

    6.配置 yarn-site.xml

     1 <configuration>
     2 
     3     <property>
     4         <name>yarn.resourcemanager.hostname</name>
     5         <value>master</value>
     6     </property>
     7     <property>
     8         <name>yarn.nodemanager.aux-services</name>
     9         <value>mapreduce_shuffle</value>
    10         <description>A comma separated list of services where service name should only contain a-zA-Z0-9_ and can not start with numbers</description>
    11     </property>
    12     <property>
    13         <name>yarn.resourcemanager.address</name>
    14         <value>master:18040</value>
    15     </property>
    16     <property>
    17         <name>yarn.resourcemanager.scheduler.address</name>
    18         <value>master:18030</value>
    19     </property>
    20     <property>
    21         <name>yarn.resourcemanager.resource-tracker.address</name>
    22         <value>master:18025</value>
    23     </property>
    24     <property>
    25         <name>yarn.resourcemanager.admin.address</name>
    26         <value>master:18141</value>
    27     </property>
    28     <property>
    29         <name>yarn.resourcemanager.webapp.address</name>
    30         <value>master:18088</value>
    31     </property>
    32     <property>
    33         <name>yarn.log-aggregation-enable</name>
    34         <value>true</value>
    35     </property>
    36     <property>
    37         <name>yarn.log-aggregation.retain-seconds</name>
    38         <value>86400</value>
    39     </property>
    40     <property>
    41         <name>yarn.log-aggregation.retain-check-interval-seconds</name>
    42         <value>86400</value>
    43     </property>
    44     <property>
    45         <name>yarn.nodemanager.remote-app-log-dir</name>
    46         <value>/tmp/logs</value>
    47     </property>
    48     <property>
    49         <name>yarn.nodemanager.remote-app-log-dir-suffix</name>
    50         <value>logs</value>
    51     </property>
    52 
    53 </configuration>

     到這裏 master 就已經配置好了,下面將該服務器的配置分發到 slave1 上去(建議壓縮后再分發),在此使用壓縮後分發的方法

    在 master 節點上執行

    1 cd /usr/local
    2 tar -zcvf ~/hadoop.master.tar.gz ./hadoop 3 cd ~ 4 scp ./hadoop.master.tar.gz slave1:/root/ 5 scp /etc/profile slave1:/etc/

    在 slave1 節點上執行

    tar -zxvf ~/hadoop.master.tar.gz -C /usr/local

    在 slave1 上重新加載環境並檢查驗證

    source /etc/profile
    echo $HADOOP_HOME

    HDFS NameNode 格式化(只要在 master 上執行即可)

    $HADOOP_HOME/bin/hdfs namenode -format

    看到下面的輸出,表明hdfs格式化成功

    INFO common.Storage: Storage directory /usr/local/hadoop/tmp/dfs/name has been successfully formatted.

    啟動前檢查防火牆狀態

    systemctl status firewalld

    我這裡是已經關閉的,若未關閉,可以參考下圖(來自)

    阿里雲服務器還需要在服務器安全組裡配置防火牆,需將配置文件里的相關端口全部添加,否則會出現 web 頁面打不開,以及 DataNode 啟動但 Live datenode 為 0 等問題

    啟動 Hadoop 集群

    $HADOOP_HOME/sbin/start-all.sh

     

    • 第一次啟動 hadoop 時會出現 ssh 提示,提示是否要連入 0.0.0.0 節點,輸入 yes 即可
    • 若出現 hadoop 啟動時 datanode 沒有啟動,可以參考來解決

    啟動 job history server

    在 master 上執行

    $HADOOP_HOME/sbin/mr-jobhistory-daemon.sh start historyserver

    成功后在兩個節點上驗證

    在 master 上 執行 

    jps

    可以看到 ResourceManager、SecondaryNameNode、NameNode、JobHistoryServer 四個進程全部啟動

    在 slave1 上執行

    jps

    可以看到 NodeManager、DataNode 兩個進程全部啟動

    缺少任一進程都表示出錯。另外還需要在 Master 節點上通過命令 hdfs dfsadmin -report 查看 DataNode 是否正常啟動,如果 Live datanodes 不為 0 ,則說明集群啟動成功。例如我這邊一共有 1 個 Datanodes:

    全部配置完成之後查看 web 頁面

    hdfs: http://master:50070/

    hdfs Datanode 節點信息

    hdfs 的情況

    hdfs 的文件情況

     yarn:http://master:18088/

     

    阿里雲ECS服務器部署HADOOP集群系列:

     

     

     

     

     

     

     

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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

  • PL真有意思(一):引言

    前言

    斷斷續續學編譯原理到之前發過寫一個編譯器和正則表達式引擎系列文章也有一段時間了,然後最近看完PLP這本書,這本書應該算是入門書,但是對我這種半吊子收穫很大。所以為了彌補最近學操作系統和接外包摸的魚,就想寫寫看完這本書的收穫。(為拙劣的標題道歉

    程序設計語言的譜系

    現在的新語言都是一撮一撮的出來,但是基本都可以用他們的計算模型來分成兩類,一類是更關心計算機做什麼的說明式,一類是更關心計算機怎麼做的命令式

    一般認為像函數式邏輯式語言都算是說明式,而馮諾依曼式和面向對象的都被認為是命令式

    函數式

    函數式是基於函數的遞歸定義的計算模型,一般從本質上來看,函數式把程序認為是一種從輸入到輸出的函數,使用更簡單的函數通過逐步細化的過程來定義

    邏輯式

    邏輯式里經典的應該就是Prolog了,邏輯式一般將計算看作是一種找出滿足某些特定關係的值的嘗試過程

    馮諾依曼式

    馮諾依曼式最重要的就是通過副作用也就是修改寄存器里的值來對後續計算產生影響,像C和Fortran都屬於馮諾依曼式

    幾個例子

    如果C語言來實現求最大公約數,可以發現C語言偏向通過迭代和反覆修改變量的值來實現

    int gcd(int a, intb) {
        while (a != b) {
          if (a > b) {
            a = a - b;
          } else {
            b = b - a;
          }
        }
    }

    從lisp來看,則更加關注輸入和輸出的數學關係,要算出最大公約數,需要對函數的不斷的擴充和精簡

    (define gcd
      (lambda (a b)
        (cond ((= a b) a)
              ((> a b) (gcd (- a b) b))
              (else (gcd (- b a) a)))))

    對於像C或者Java入門的人,看到Prolog可能頭都大了,因為Prolog和命令式的思考邏輯完全不同,邏輯式更傾向給出一組公理和檢測規則,期望系統能給出相應合理的值,我也僅限於能看懂這些小程序

    gcd(A,B,G) :- A = B, G = A.
    gcd(A,B,G) :- A > B, C is A - B, gcd(C,B,G)
    gcd(A,B,G) :- B > A, C is B-A
    gcd(C,A,G)

    編譯和解釋

    下面再看兩個概念,編譯和解釋。

    編譯一般是指從一個語言到另一個語言的翻譯,無論是高級語言到彙編還是高級語言到高級語言都算是編譯。而解釋就是直接執行代碼,但是現代的解釋器,一般還有虛擬機一層,即翻譯到虛擬機指令再由虛擬機進行執行

    自舉

    很多語言的編譯器都是由自己編譯而成的,所以問題就是,最開始的編譯器是怎麼編譯的?

    假設現在要為Java編寫一個編譯器,我們可以先用C語言編寫一個Java小子集的編譯器,然後再手動將C語言翻譯到這個Java子集,就可以由這個子集編譯運行自己,然後就可以不斷擴充這個編譯器

    編譯概覽

    其實這個在之前那個寫編譯器的系列是說得最詳細的,這個系列是想要寫寫筆記對實踐和語言設計結合的說。

    • 詞法分析

    有關詞法分析其實就是將字符流化成單詞流,記錄每個單詞的信息,然後通常還會有其它更多的信息讓編譯器更好的生成錯誤信息

    • 語法分析

    語法分析通常會用到一個概念:上下文無關文法,就是用來定義語法形式的,比如while語句就可以這樣表示

    while-statment := WHILE ( expr ) statment

    一般語法分析過程最後的輸出都是樹狀結構

    • 語義分析和中間代碼生成

    語法分析只保證源代碼語法格式的正確,但是卻不能保證其它的正確性,比如對於靜態類型的語言,可能就會在編譯時直接檢測出類型錯誤

    而在語義分析過程一般還需要藉助一個叫做符號表的數據結構,這個符號表將每個標識符都映射到關於它的已知信息

    中間代碼的生成通常是會將樹形結構翻譯成更接近彙編的中間線性格式,但是中間格式不是必須的,比如之前那個系列里還寫了C的解釋器,雖然很殘缺,它是沒有中間代碼的,連虛擬機都沒有,是直接進行遍歷語法樹進行執行解釋的

    • 代碼優化

    有些代碼改進是機器無關的,即可以在中間格式上就進行優化,但是有的代碼優化是平台相關的,所以就需要在最後對目標語言優化

    • 目標代碼生成

    代碼生成階段就是根據之前生成的線性結構和符號表信息對目標代碼的生成

    小結

    這一篇主要說了幾個範式的語言和編譯過程的概括,對摸魚進行懺悔

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

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

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

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