標籤: 台北網頁設計公司

  • 工信部:第287批申報目錄 純電動汽車型達424款

    工信部:第287批申報目錄 純電動汽車型達424款

    8月10日,工信部對申報《道路機動車輛生產企業及產品公告》(第287批)的車輛新產品進行公示。申報第287批公告的純電動車輛及底盤數量多達424款,插電式混合動力車輛及底盤數量為10款,純電動廂式運輸車車型數量多達129款。  
     
    申報純電動客車車型數量約162款,詳情如下:   安凱9款,星凱龍4款,安達爾1款,北汽福田1款,比亞迪1款,長春北車電動汽車公司3款,長沙梅花汽車2款,成都客車2款,丹東黃海2款,東風汽車5款,東莞中汽宏遠2款,佛山飛馳2款,杭州長江客車2款,少林客車2款,南車時代5款,江蘇九龍1款,常隆客車1款,江蘇陸地方舟16款,江西江鈴4款,江西宜春客車廠1款,金華青年汽車12款,蘇州金龍8款,牡丹汽車2款,南京金龍8款,南京公共交通車輛廠1款,山西原野汽車6款,陝西躍迪1款,申龍客車1款,申沃客車1款,上海萬象1款,深圳五洲龍5款,川汽野馬1款,廈門金龍3款,廈門金旅10款,煙臺舒馳1款,揚子江汽車6款,揚州亞星3款,一汽客車1款,浙江南車電車8款,宇通6款,一汽集團1款,濟南豪沃1款,中通客車2款,中植一客5款,重慶恒通1款,珠海廣通1款。  
    插電式混合動力客車及底盤共10款。具體到企業來看,安凱申報的插電式混合動力客車車型3款,昆明客車1款,揚州亞星2款,中通客車1款,重慶恒通2款。  
    純電動轎車及乘用車的申報車型超過18款。其中,北汽3款,長城1款,東風悅達起亞1款,東風小康2款,合肥長安汽車1款,湖南江南汽車4款,奇瑞3款,榮成華泰1款,浙江吉利1款,力帆1款,比亞迪1款,江蘇卡威1款。   通過梳理第287批申報車型,筆者認為:純電動客車依然會是未來中國新能源汽車市場的主力軍,純電動物流車市場亟待爆發,純電動轎車和乘用車車型數量增多使得消費者的選擇權變大,此領域的競爭格局將會有所改變。第287批公告的公示時間為2016年8月10日至2016年8月16日。   文章來源:蓋世汽車

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

    【其他文章推薦】

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

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

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

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

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

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

    一、背景

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

    • 廣告主在後台進行廣告投放;
    • 投放廣告品及關鍵詞數據寫入數據庫;
    • 數據庫中的數據通過全量構建(導入數據倉庫再進行離線批處理)或增量構建(藉助消息隊列和流計算引擎)的方式產出用於構建在線索引的“內容文件”;
    • 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進行分組,並對行數進行統計。

    五、總結及後續規劃

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

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

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

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

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

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

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

  • 安卓JNI精細化講解,讓你徹底了解JNI(一):環境搭建與HelloWord

    安卓JNI精細化講解,讓你徹底了解JNI(一):環境搭建與HelloWord

    目錄

    1、基礎概念

    1.1、JNI

    JNI(Java Native Interface)Java本地接口,使得Java與C/C++具有交互能力

    1.2、NDK

    NDK(Native Development Kit) 本地開發工具包,允許使用原生語言(C和C++)來實現應用程序的部分功能

    Android NDK開發的主要作用:

    1、特定場景下,提升應用性能;
    2、代碼保護,增加反編譯難度;
    3、生成庫文件,庫可重複使用,也便於平台、項目間移植;

    1.3、CMake與ndk-build

    當我們基於NDK開發出native功能后,通常需要編譯成庫文件,給Android項目使用。
    目前,有兩種主流的編譯方式:__CMake__與ndk-build

    __CMake__與__ndk-build__是兩種不同的編譯工具(與Android代碼和C/C++代碼無關)

    CMake

    CMake是Androidstudio2.2之後引入的跨平台編譯工具(特點:簡單易用,2.2之後是默認的NDK編譯工具)
    
    如何配置:
       1、創建CMakeLists.txt文件,配置CMake必要參數;
       2、使用gradle配置CMakeLists.txt以及native相關參數;
    
    如何編譯庫文件:
       1、Android Studio執行Build即可;

    ndk-build

    ndk-build是NDK中包含的腳本工具(可在NDK目錄下找到該工具,為了方便使用,通常配置NDK的環境變量)
    
    如何配置:
       1、創建Android.mk文件,配置ndk-build必要參數;
       2、可選創建application.mk文件,配置ndk-build參數 (該文件的配置項可使用gradle的配置替代);
       3、使用gradle配置Android.mk以及native相關參數;
    
    2、如何編譯庫文件(兩種方式):
       1、Android Studio執行Build即可(執行了:Android.mk + gradle配置);
       2、也可在Terminal、Mac終端、cmd終端中通過ndk-build命令直接構建庫文件(執行了:Android.mk)

    2、環境搭建

    JNI安裝
    JNI 是JDK里的內容,電腦上正確安裝並配置JDK即可 (JDK1.1之後就正式支持了);

    NDK安裝
    可從官網自行下載、解壓到本地,也可基於AndroidStudio下載解壓到默認目錄;

    編譯工具安裝
    cmake 可基於AndroidStudio下載安裝;
    ndk-build 是NDK里的腳本工具,NDK安裝好即可使用ndk-build;

    當前演示,使用的Android Studio版本如下(當前最新版):

    啟動Android Studio –> 打開SDK Manager –> SDK Tools,如下圖所示:

    我們選擇NDK、CMake、LLDB(調試Native時才會使用),選擇Apply進行安裝,等安裝成功后,NDK開發所依賴的環境也就都齊全了。

    3、Native C++ 項目(HelloWord案例)

    3.1、項目創建(java / kotlin)

    新建項目,選擇 Native C++,如下圖:

    新創建的項目,默認已包含完整的native 示例代碼、cmake配置 ,如下圖:

    這樣,我們就可以自己定義Java native方法,並在cpp目錄中寫native實現了,很方便。

    但是,當我們寫完native的實現代碼,希望運行APP,查看JNI的交互效果,此時,就需要使用編譯工具了,咱們還是先看一下Android Studio默認的Native編譯方式吧:CMake

    3.2、CMake的應用

    在CMake編譯之前,咱們應該先做哪些準備工作?

    1、NDK環境是否配置正確?
    -- 如果未配置正確是無法進行C/C++開發的,更不用說CMake編譯了
    
    2、C/C++功能是否實現? 
    -- 此次演示主要使用系統默認創建的native-lib.cpp文件,關於具體如何實現:後續文章再詳細講解
    
    3、CMakeLists.txt是否創建並正確配置? 
    -- 該文件是CMake工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果
    
    4、gradle是否正確配置?
    -- gradle配置也是CMake工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果

    除此之外,咱們還應該學習CMake的哪些重要知識?

    1、CMake工具編譯生成的庫文件默認在什麼位置?apk中庫文件又是在什麼位置?
    2、CMake工具如何指定編譯生成的庫文件位置?
    3、CMake工具如何指定生成不同CPU平台對應的庫文件?

    帶着這些問題,咱們開始CMake之旅吧:

    3.2.1、NDK環境檢查

    編譯前,建議先檢查下工程的NDK配置情況(不然容易報一些亂七八糟的錯誤):
    File –> Project Structure –> SDK Location,如下圖(我本地的Android Studio默認沒有給配置NDK路徑,那麼,需要自己手動指定一下):

    3.2.2、C/C++功能實現

    因為本節主講CMake編譯工具,代碼就不單獨寫了,咱們直接使用工程默認生成的native-liv.cpp,簡單調整一下native實現方法的代碼吧(修改返迴文本信息):

    因Native C++工程默認已配置好了CMakeLists.txt和gradle,所以咱們可直接運行工程看效果,如下圖:

    JNI交互效果我們已經看到了,說明CMake編譯成功了。那麼,這究竟是怎麼做到的呢?咱們接着分析一下吧:

    3.2.3、CMake生成的庫文件與apk中的庫文件

    安卓工程編譯時,會執行CMake編譯,在 工程/app/build/…/cmake/ 中會產生對應的so文件,如下圖:

    繼續編譯安卓工程,會根據build中的內容,生成我們的*.apk安裝包文件。我們找到、反編譯apk安裝包文件,查找so庫文件。原來在apk安裝包中,so庫都被存放在lib目錄中,如下圖:

    3.2.4、CMake是如何編譯生成so庫的呢?

    在前面介紹CMake定義時,提到了CMake是基於CMakeLists.txt文件和gradle配置實現編譯Native類的。那麼,咱們先來看一下CMakeLists.txt文件吧:

    #cmake最低版本要求
    cmake_minimum_required(VERSION 3.4.1)
    
    #添加庫
    add_library(
            # 庫名
            native-lib
    
            # 類型:
            # SHARED 是指動態庫,對應的是.so文件
            # STATIC 是指靜態庫,對應的是.a文件
            # 其他類型:略
            SHARED
    
            # native類路徑
            native-lib.cpp)
    
    # 查找依賴庫
    find_library( 
            # 依賴庫別名
            log-lib
    
            # 希望加到本地的NDK庫名稱,log指NDK的日誌庫
            log)
    
    
    # 鏈接庫,建立關係( 此處就是指把log-lib 鏈接給 native-lib使用 )
    target_link_libraries( 
            # 目標庫名稱(native-lib 是咱們要生成的so庫)
            native-lib
    
            # 要鏈接的庫(log-lib 是上面查找的log庫)
            ${log-lib})

    實際上,CMakeList.txt可配置的內容遠不止這些,如:so輸出目錄,生成規則等等,有需要的同學可查下官網。

    接着,咱們再看一下app的gradle又是如何配置CMake的呢?

    apply plugin: 'com.android.application'
    
    android {
        compileSdkVersion 29
        buildToolsVersion "29.0.1"
        defaultConfig {
            applicationId "com.qxc.testnativec"
            minSdkVersion 21
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            //定義cmake默認配置屬性
            externalNativeBuild {
                cmake {
                    cppFlags ""
                }
            }
        }
        
        //定義cmake對應的CMakeList.txt路徑(重要)
        externalNativeBuild {
            cmake {
                path "src/main/cpp/CMakeLists.txt"
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'androidx.appcompat:appcompat:1.1.0'
        implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'androidx.test.ext:junit:1.1.1'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    }

    實際上,gradle可配置的cmake內容也遠不止這些,如:abi、cppFlags、arguments等,有需要的同學可查下官網。

    3.2.5、如何指定庫文件的輸出目錄?

    如果希望將so庫生成到特定目錄,並讓項目直接使用該目錄下的so,應該如何配置呢?
    比較簡單:需要在CMakeList.txt中配置庫的輸出路徑信息,即:

    CMAKE_LIBRARY_OUTPUT_DIRECTORY

    # cmake最低版本要求
    cmake_minimum_required(VERSION 3.4.1)
    
    # 配置庫生成路徑
    # CMAKE_CURRENT_SOURCE_DIR是指 cmake庫的源路徑,通常是build/.../cmake/
    # /../jniLibs/是指與CMakeList.txt所在目錄的同級目錄:jniLibs (如果沒有會新建)
    # ANDROID_ABI 生成庫文件時,採用gradle配置的ABI策略(即:生成哪些平台對應的庫文件)
    set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})
    
    # 添加庫
    add_library( # 庫名
            native-lib
    
            # 類型:
            # SHARED 是指動態庫,對應的是.so文件
            # STATIC 是指靜態庫,對應的是.a文件
            # 其他類型:略
            SHARED
    
            # native類路徑
            native-lib.cpp)
    
    # 查找依賴庫
    find_library(
            # 依賴庫別名
            log-lib
    
            # 希望加到本地的NDK庫名稱,log指NDK的日誌庫
            log)
    
    
    # 鏈接庫,建立關係( 此處就是指把log-lib 鏈接給native-lib使用 )
    target_link_libraries(
            # 目標庫名稱(native-lib就是咱們要生成的so庫)
            native-lib
    
            # 要鏈接的庫(上面查找的log庫)
            ${log-lib})

    還需要在gradle中配置 jniLibs.srcDirs 屬性(即:指定了lib庫目錄):

    sourceSets {
            main {
                jniLibs.srcDirs = ['jniLibs']//指定lib庫目錄
            }
        }

    接着,重新build就會在cpp相同目錄級別位置生成jniLibs目錄,so庫也在其中了:

    注意事項:
    1、配置了CMAKE_CURRENT_SOURCE_DIR,並非表示編譯時直接將so生成在該目錄中,實際編譯時,so文件仍然是
    先生成在build/.../cmake/中,然後再拷貝到目標目錄中的
    
    2、如果只配置了CMAKE_CURRENT_SOURCE_DIR,並未在gradle中配置 jniLibs.srcDirs,也會有問題,如下:
    More than one file was found with OS independent path 'lib/arm64-v8a/libnative-lib.so'
    
    此問題是指:在編譯生成apk時,發現了多個so目錄,android studio不知道使用哪一個了,所以需要咱們
    告訴android studio當前工程使用的是jniLibs目錄,而非build/.../cmake/目錄
    3.2.5、如何生成指定CPU平台對應的庫文件呢?

    我們可以在cmake中設置abiFilters,也可在ndk中設置abiFilters,效果是一樣的:

    defaultConfig {
            applicationId "com.qxc.testnativec"
            minSdkVersion 21
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            externalNativeBuild {
                cmake {
                    cppFlags ""
                    abiFilters "arm64-v8a"
                }
            }
        }
    defaultConfig {
            applicationId "com.qxc.testnativec"
            minSdkVersion 21
            targetSdkVersion 29
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
            externalNativeBuild {
                cmake {
                    cppFlags ""
                }
            }
            ndk {
                abiFilters "arm64-v8a"
            }
        }

    按照新的配置,我們重新運行工程,如下圖:

    再反編譯看下工程,果然只有arm64-v8a的so庫了,不過庫文件在apk中仍然是放在lib目錄,而非jniLibs(其實也很好理解,jniLibs只是我們本地的目錄,便於我們管理庫文件,真正生成apk時,仍然會按照lib目錄放置庫文件),如下圖:

    至此,CMake的主要技術點都講完了,接下來咱們看下NDK-Build吧~

    3.3、ndk-build的應用

    在ndk-build編譯之前,咱們又應該先做哪些準備工作?

    1、ndk-build環境變量是否正確配置?
    -- 如果未配置,是無法在cmd、Mac終端、Terminal中使用ndk-build命令的(會報錯:找不到命令)
    
    2、NDK環境是否配置正確?
    -- 如果未配置正確是無法進行C/C++開發的,更不用說ndk-build編譯了
    
    3、C/C++功能是否實現?
    -- 此次演示主要使用系統默認創建的native-lib.cpp文件,關於具體如何實現:後續文章再詳細講解
    -- 注意:為了與CMake區分,咱們新建一個“jni”目錄存放C/C++文件、配置文件吧
    
    4、Android.mk是否創建並正確配置? 
    -- 該文件是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果
    
    5、gradle是否正確配置?(可選項,如果通過cmd、Mac終端、Terminal執行ndk-build,可忽略)
    -- gradle配置也是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果
    
    6、Application.mk是否創建並正確配置?(可選項,一般配ABI、版本,這些項均可在gradle中配置)
    -- 該文件也是ndk-build工具編譯的基礎,未配置或配置項錯誤,均會影響編譯結果

    除此之外,咱們還應該學習ndk-build的哪些重要知識?

    1、ndk-build工具如何指定編譯生成的庫文件位置?
    2、ndk-build工具如何指定生成不同CPU平台對應的庫文件?

    帶着這些問題,咱們繼續ndk-build之旅吧:

    3.3.1、環境變量配置

    介紹NDK-Build定義時,提到了其實它是NDK的腳本工具。那麼,咱們還是先進NDK目錄找一下吧,ndk-build工具的位置如下圖:

    如果我們希望任意情況下都能便捷的使用這種腳本工具,通常做法是配置其環境變量,否則我們在cmd、Mac終端、Terminal中執行 ndk-build 命令時,會報錯:“未找到命令”

    配置NDK的環境變量,也很簡單,以Mac電腦舉例(如果是Windows電腦,網上也有很多關於配置環境變量的文章,如果有需要可自行查下):

    1、打開命令終端,輸入命令: open -e .bash_profile,打開bash_profile配置文件
    
    2、寫入如下內容(NDK_HOME指向 ndk-build 所在路徑):
    export NDK_HOME=/Users/xc/SDK/android-sdk-macosx/ndk/20.1.5948944
    export PATH=$PATH:$NDK_HOME
    
    3、生效.bash_profile配置
    source .bash_profile

    當我們在cmd、Mac終端、Terminal中執行 ndk-build 命令時,如果出現下圖所示內容,則代表配置成功了:

    3.3.2、C/C++功能實現

    咱們使用比較常用的一種ndk-build方式吧:ndk-build + Android.mk + gradle配置

    項目中新建jni目錄,拷貝一份CMake的代碼實現吧:

    1、新建jni目錄
    2、拷貝cpp/native-lib.cpp 至 jni目錄下
    3、重命名為haha.cpp (與CMake區分)
    4、調整一下native實現方法的文本(與CMake運行效果區分)
    5、新建Android.mk文件

    接着,編寫Android.mk文件內容:

    #表示Android.mk所在目錄
    LOCAL_PATH := $(call my-dir)
    
    #CLEAR_VARS變量指向特殊 GNU Makefile,用於清除部分LOCAL_變量
    include $(CLEAR_VARS)
    
    #模塊名稱
    LOCAL_MODULE    := haha
    #構建系統用於生成模塊的源文件列表
    LOCAL_SRC_FILES := haha.cpp
    
    #BUILD_SHARED_LIBRARY 表示.so動態庫
    #BUILD_STATIC_LIBRARY 表示.a靜態庫
    include $(BUILD_SHARED_LIBRARY)

    配置gradle:

    apply plugin: 'com.android.application'
    android {
        compileSdkVersion 28
        defaultConfig {
            applicationId "com.aaa.testnative"
            minSdkVersion 16
            targetSdkVersion 28
            versionCode 1
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    
            //定義ndkBuild默認配置屬性
            externalNativeBuild {
                ndkBuild {
                    cppFlags ""
                }
            }
        }
       
        //定義ndkBuild對應的Android.mk路徑(重要)
        externalNativeBuild {
            ndkBuild{
                path "src/main/jni/Android.mk"
            }
        }
    }
    
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:28.0.0'
        implementation 'com.android.support.constraint:constraint-layout:1.1.3'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.2'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    }
    

    現在,native代碼、ndk-build配置都完成了,咱們運行看一下效果吧,如下圖:

    3.3.4、如何指定庫文件的輸出目錄?

    通常,可在Android.mk文件中配置NDK_APP_DST_DIR
    指定源目錄與輸出目錄(與CMake類似)

    #表示Android.mk所在目錄
    LOCAL_PATH := $(call my-dir)
    
    #設置庫文件的輸入目錄
    #輸出目錄 ../jniLibs/
    #源目錄 $(TARGET_ARCH_ABI)
    NDK_APP_DST_DIR=../jniLibs/$(TARGET_ARCH_ABI)
    
    #CLEAR_VARS變量指向特殊 GNU Makefile,用於清除部分LOCAL_變量
    include $(CLEAR_VARS)
    
    #模塊名稱
    LOCAL_MODULE    := haha
    #構建系統用於生成模塊的源文件列表
    LOCAL_SRC_FILES := haha.cpp
    
    #BUILD_SHARED_LIBRARY 表示.so動態庫
    #BUILD_STATIC_LIBRARY 表示.a靜態庫
    include $(BUILD_SHARED_LIBRARY)
    
    3.3.5、如何生成指定CPU平台對應的庫文件呢?

    可在gradle中配置abiFilters(與Cmake類似)

    externalNativeBuild {
                ndkBuild {
                    cppFlags ""
                    abiFilters "arm64-v8a"
                }
            }
    externalNativeBuild {
                ndkBuild {
                    cppFlags ""
                }
            }
      ndk {
                abiFilters "arm64-v8a"
            }
    3.3.6、如何在Terminal中直接通過ndk-build命令構建庫文件呢?

    除了執行AndroidStudio的build命令,基於gradle配置 + Android.mk編譯生成庫文件,我們還可以在cmd、Mac 終端、Terminal中直接通過ndk-build命令構建庫文件,此處以Terminal為例進行演示吧:

    先進入包含Android.mk文件的jni目錄(Android Studio中可直接選中jni目錄並拖拽到Terminal中,會自動跳轉到該目錄),再執行ndk-build命令,如下圖:

    同樣,編譯也成功了,如下圖:

    因是直接在Terminal中執行了ndk-build命令,所以只會根據Android.mk進行編譯(不包含gradle配置內容,也就不會執行abiFilters過濾),生成了所有默認CPU平台的so庫文件。

    ndk-build命令其實也可以配上一些參數使用,此處就不再詳解了。日常開發時,還是建議選擇CMake作為Native編譯工具,因為是安卓主推的,而且更簡單一些。

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

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

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

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

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

  • 理解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)模式隔離級別,很大程度上提高數據庫的讀寫并行度,提高數據庫的性能。

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

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

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

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

  • 應屆畢業生工作7個月小結

    應屆畢業生工作7個月小結

    前言: 不知不覺已經工作了快 7 個月了,去年這個時候還躋身在考研的大軍中,不禁有些感慨… 結合這 7 個月發生的一些事情,簡單做一下總結吧…

    為獲得更好的閱讀體驗,請訪問原文地址:

    一、那時候剛入職

    不同於其他同學忙於畢設的 4 月,提早安排趁寒假已經完成畢設的我,已經開始撲在了「找工作」這件事上,有了去年「秋招」打下的基礎,複習起來快了很多,沒過多久就開始投簡歷面試了,面試也總體比較順利,剛面沒幾家就迅速和一家自己看好的初創公司簽下了。

    公司使用的技術棧區別於自己熟悉的 Java/ MySQL 這一套,而是主要使用的 Rails/ MongoDB,所以剛入職的一段時間,基本上都是在自己熟悉技術棧,也趁着閑暇的時間,把自己入門時候的一些學習心得寫成了文章發表:

    • MongoDB【快速入門】:
    • Java轉Ruby【快速入門】:

    對於職場小白來說,所謂「職場」還是顯得有些陌生,剛來的時候,雖然跟周圍的同事都稀鬆平常地打了一圈兒招呼,坐下之後,隨着他們又埋頭噼里啪啦敲打鍵盤工作的深入,又頓覺周圍一片陌生,還挺奇妙的,在第一周完的周報裏面我寫道:

    剛來公司有些迷茫,只是看着CheckList對照着熟悉一些技術,也不了解自己應該要熟悉到哪種程度,就希望自己能再主動些,不管是技術問題還是其他問題多請教,然後儘快跟其他成員熟悉起來。

    剛開始上手的時候也有好多問題不懂,我都習慣性的選擇自己研究一陣兒,因為自己有寫博客的一些經歷,被問過好多一搜索 or 自己一嘗試就能解決的問題,所以比較克制,但是後來「入職 1v1」溝通的時候被說到有問題別自己死磕,半個小時沒解決盡量就找一下旁邊的同事。摁?我一下子就把我的「主動性」發揮了出來。

    不過好記性也不如爛筆頭,找了一些工具記錄把這些「問題的答案」都記錄了下來,方便之後再查找,當時對於 Git 都不是很熟悉,也記錄了很多常用的命令在裏面,還有一些問題的反饋,甚至知道了月會要自我介紹,也打了一遍草稿記錄在了這裏:(那段時間真的問了好多問題,周報里也手動感謝了坐我旁邊的兩位大佬..)

    入職兩周的時候,雖然已經開始上手做一些簡單的埋點工作,但自己對於 Ruby 還是不是特別了解和熟悉,趁着某一個雙休,抓着一本《Effetive-Ruby》啃了两天,也把自己的學習輸出了一下:

    • 《Effective-Ruby》讀書筆記:

    二、逐漸能夠上手

    就這樣一邊熟悉,一邊開始接一些小需求,我記得我寫下的第一個 BUG,就報出了 6K 條記錄.. 慌慌張張在修復之後我不禁感嘆:「不要太相信用戶的任何數據」。(包括 equal 反寫也是之後在錯誤之中學習到的..)

    剛上手沒有一段時間,就接到了一個新項目的需求,跟着一位大佬開發一個新功能,大佬負責搭建基礎代碼和設計,我負責完成其餘的功能代碼,沒敢一絲懈怠,下班回家之後也對照着別人寫的代碼敲敲敲,時間和完成度上倒是沒有一絲耽擱,只是現在回過頭一想,當時沒有什麼單元測試的概念和意識,就自己在本地 Post-Man 測試完就完,所幸比較簡單 + 自己測試得比較仔細,到現在也沒有出現過什麼問題。

    工作對我這樣的小白另一個好處就是:「見識和增加技術的廣度」。公司所使用技術棧不論是廣度還是深度,都是自己在大學本科的學習中不可企及的程度,Jekins?Docker?K8S?跳板機?一下子冒出來好多新鮮陌生的名詞,懷着好奇心也嘗試了解了一些:

    • 了解【Docker】從這裏開始:
    • 「消息隊列」看過來!:
    • Kafka【入門】就這一篇!:

    也隨着公司的逐漸壯大,各模塊的耦合也越發嚴重,各條業務線之間的協作溝通成本越來越大,逐漸開始提出「微服務」這樣的概念,具體怎麼樣理解就不作討論了,總之就是期望通過梳理/ 重構/ 拆服務的方式來解決「協作」問題,所以期間也開始了解學習一些這方面的東西:

    • 什麼是微服務?:
    • 《重構:改善既有代碼的設計》讀書筆記:

    甚至期間還做了一些「微服務」的調研,我們選用什麼樣的姿勢和技術棧更加合適,所以也輸出了一些關於「Spring Cloud」的東西,但是最終駁回的原因是待我們整個容器化之後 k8s 平台自帶了這麼一套東西,業務同學只需要關心業務代碼就行了,也就沒有繼續深入了:

    • 你想了解的「Spring Cloud」都在這裏:

    然後我們在拆解的過程中,也借鑒到一些「DDD」的思想,也嘗試進行了一波學習:

    • 【吐血推薦】領域驅動設計學習輸出:

    總之,這一段時間我一邊通過各種小需求,接觸和了解了公司的系統的大半,一邊學習和了解着各種不同的技術,增加了技術上的廣度。

    三、開始負責一些項目

    為了加速服務化的推進工作和驗證「DDD」的一些東西,部門老大把一個邊界足夠清晰,也足夠小的一個模塊單獨交給我,期望我快速上線,不過最終交付已經逾期快大半個月了.. 雖然從最終的結果來看,順利交付完成了拆解任務並從 MongoDB 數據庫轉變成了 MySQL.. 但期間也踩過好些坑,當然也學習到一些東西..

    例如我真實地意識到「完美」這個詞的理想化。就拿設計 API 來說吧.. 自己就基於 RESTful 風格設計了好幾版.. 左想右想都覺得差一些,有一些接口覺得怎麼設計都不優雅.. 後來糾結一陣子也就放棄了.. 再例如寫代碼這件事情吧,好的代碼整潔的代碼是一次一次迭代和重構中出來的,如果一開始就想着寫出「完美」的代碼,那麼最終的結果可能就是寫不出來代碼。

    另外一個小插曲是,在做數據遷移的時候,我差點把線上服務器搞掛了.. 我在測試環境驗證了一把之後,就直接在線上進行操作了,因為當時對於數據庫的操作管控還沒有那麼嚴格,加上自己對於線上環境的複雜程度認識不足,我就起了 50 個線程,去分批量地讀取 MongoDB 的數據遷移到 MySQL,造成了線上庫的性能報警,就趕緊停了.. 緊接着就被一群大佬抓進了一個會議室做事件的復盤..

    說實話,我緊張壞了,第一次經歷這樣的算是「事故」的情況吧,差一點線上就被我搞掛啦,一時間不知所措… 讓人感到溫暖的是部門老大隨即丟來的消息:

    那天還有一些相關的同事都陪我寫復盤郵件到了晚上 10:30,現在想來都十分感謝他們。後來回到家我還打電話給我媽,我說我在工作中犯錯了,我做了xxxx這些動作,你覺得我做的怎麼樣呢,老媽的回復也讓人安心,只是現在想來,一些後續的動作可以做得更好的…

    因為「埋點」這件事涉及到系統的方方面面,我也藉此了解了很多不同的模塊,也是拜這一點所賜吧,後來我被派到各種各樣的支援任務中,同樣也因為對不同模塊都還不算陌生,都還算完成得不錯吧…

    時間一晃,在公司就四個月過去了,也在這個過程中從各個大佬那兒都學到了一些東西,在 8 月底發的周報裏面我寫下了以下的總結:

    之後也跟着大佬碰了一些公司的核心模塊,期間也沒有停止在工作中不斷地做學習輸出:

    • Git 原理入門解析:
    • Java計時新姿勢√:
    • Java8流操作-基本使用&性能測試:
    • 《代碼整潔之道》讀書筆記:
    • React 入門學習:
    • 談一談依賴倒置原則:

    四、回顧做的不好的部分

    • 對代碼還沒有保持足夠的敬畏之心。

    特別是一開始上手的時候,有時候甚至是在線上環境搞測試,後來越來越注重 codereview 和單元測試好了很多。

    • 溝通還不夠深入/ 到位

    有一次是臨時接到一個需求,因為「通用語言」沒有達成一致,導致最終交付的結果不符合產品的期望,最終我們所有相關人員在一起開了一個會,統一了「通用語言」,造成了額外的工作和負擔,拿到需求就應該確認好相關事宜的,越底層越細節越好,這方面的能力我仍然欠缺,但我已經持續在注意當中。

    另一次也是因為這一點,我需要幫助 A 系統擁有某一項功能,之前 A 系統已經介入了 B 系統完成了部分功能,我因為沒有進一步地確認 B 系統的現狀,就去接入了有完整功能的 C 系統,但其實 B 系統已經在上一周和開發 C 系統和 A 系統的同學對接好了,並完成了相關功能的接入,少了這一部分的溝通,就造成了不少額外的工作量.. 所以「溝通」還是非常重要的,也只能說持續進步吧…

    • 缺少一些主動性

    當我頭上掛着一些事情的時候,還是能夠保持着效率的,只是當我做完了,就時常缺乏一些主動地思考了,通常都是被動地去詢問同小組的同事有什麼事情是需要幫忙的.. 雖然也积極地參与到自己感興趣的那些技術評審之類的事情之中,但似乎效果都不佳.. 也沒有什麼實際好的輸出..

    • 接了一些私活兒黑活兒(沒有充分考慮團隊之間的配合)

    因為「埋點」會接觸各個平台的童鞋,並且時常變化和有一些新的需求,有時候直接繞過了一些環節,直接找上我了,我心想直接自己弄弄改改就可以了,也就沒多想… 但是現在想來,這樣跨團隊的事情,不能越過「頂頭上司」私自進行,一方面經常我的 BOSS 不知道我接了活兒,另一方面這樣的私自對接就會造成一些信息的流失,對於團隊之內還是團隊之間都會造成影響…

    五、回顧做得好的部分

    • 養成了閱讀的習慣

    公司買書是免費的,也有自己的圖書館,同事也不乏喜歡閱讀學習的,所以跟着跟着就養成了閱讀的習慣,期間也學習到了一些方法論的東西,貼一下入職以來讀過的那些書吧:(技術類的就沒有囊括了)

    其實每天閱讀的時間也不長,想我大學總共捧起的那麼些課外書,不禁有些唏噓…

    • 早睡早起 + 晨間日記

    早睡早起,從步入職場以來,就發現這樣的習慣會帶來一些額外的價值,例如一些閱讀我會放在早上,後來還加入了「晨間日記」,用來「回顧前一天的事情」和提前部署「今天的任務」,這不禁讓我多了一份清醒,也讓現在不怎麼鍛煉的我每一天精力更加好一些:(目前正在從印象筆記往 Notion 逐步遷移的過程中)

    • 學習撰寫 Commit Message && 遵守一些 Git 規範

    起初使用 Git 十分不規範,後來向大佬那兒學習到了如何標準地提交 Commit,包括 Commit Message 應該怎麼寫,我覺得這是一個很好的習慣,每一個 Commit 都有上下文,並且還帶上了 JIRA 號,任務也很好跟蹤,雖然公司並沒有大範圍地盛行起來,但我覺得這樣好習慣應該堅持下來:

    • 任務進度及時反饋給相關人員

    自己比較注意這一點,因為不這樣做會讓別人感受不怎麼好.. 光是自己心裏清楚是不行的.. 要保持信息的通暢才行,及時反饋是很重要的一步..

    • 自己先 review 一遍代碼

    犯過一些白痴錯誤之後,就有些擔心,逐步養成了自己先 review 一遍代碼的習慣..

    六、小結 && 展望

    總的來說,看着自己這樣一步一步成長過來,沒有很懈怠,自己就算比較滿意了,在工作中學習了很多東西,不管是技術上的硬技能,還是溝通中的軟技能,也認識到了很多厲害的大佬和有趣的小夥伴們..

    感恩在路上相遇,有幸共同行走過一段已然算是幸運,突然翻看起自己的朋友圈有一句話說得好:「成長從來都不是告別過去,成長是更加堅定的看向未來!」

    期待一路同行的大家,都能夠 Be Better!

    按照慣例黏一個尾巴:

    歡迎轉載,轉載請註明出處!
    獨立域名博客:wmyskxz.com
    簡書ID:
    github:
    歡迎關注公眾微信號:wmyskxz
    分享自己的學習 & 學習資料 & 生活
    想要交流的朋友也可以加qq群:3382693

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

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

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

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

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

  • 正則表達式 第五篇: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}\\\\\”:\\\\\”):後向斷言,用於匹配字段名、雙引號和冒號
    • .*?:懶惰模式,匹配盡可能少的文本
    • (?=\\\\\”):前向斷言,用於匹配字段值得雙引號

     

     

    參考文檔:

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

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

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

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

  • 【搞定 Java 併發面試】面試最常問的 Java 併發基礎常見面試題總結!

    【搞定 Java 併發面試】面試最常問的 Java 併發基礎常見面試題總結!

    本文為 SnailClimb 的原創,目前已經收錄自我開源的 中(61.5 k Star!【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。歡迎 Star!)。

    另外推薦一篇原創:

    Java 併發基礎常見面試題總結

    1. 什麼是線程和進程?

    1.1. 何為進程?

    進程是程序的一次執行過程,是系統運行程序的基本單位,因此進程是動態的。系統運行一個程序即是一個進程從創建,運行到消亡的過程。

    在 Java 中,當我們啟動 main 函數時其實就是啟動了一個 JVM 的進程,而 main 函數所在的線程就是這個進程中的一個線程,也稱主線程。

    如下圖所示,在 windows 中通過查看任務管理器的方式,我們就可以清楚看到 window 當前運行的進程(.exe 文件的運行)。

    1.2. 何為線程?

    線程與進程相似,但線程是一個比進程更小的執行單位。一個進程在其執行的過程中可以產生多個線程。與進程不同的是同類的多個線程共享進程的方法區資源,但每個線程有自己的程序計數器虛擬機棧本地方法棧,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。

    Java 程序天生就是多線程程序,我們可以通過 JMX 來看一下一個普通的 Java 程序有哪些線程,代碼如下。

    public class MultiThread {
        public static void main(String[] args) {
            // 獲取 Java 線程管理 MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
            // 不需要獲取同步的 monitor 和 synchronizer 信息,僅獲取線程和線程堆棧信息
            ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
            // 遍歷線程信息,僅打印線程 ID 和線程名稱信息
            for (ThreadInfo threadInfo : threadInfos) {
                System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
            }
        }
    }

    上述程序輸出如下(輸出內容可能不同,不用太糾結下面每個線程的作用,只用知道 main 線程執行 main 方法即可):

    [5] Attach Listener //添加事件
    [4] Signal Dispatcher // 分發處理給 JVM 信號的線程
    [3] Finalizer //調用對象 finalize 方法的線程
    [2] Reference Handler //清除 reference 線程
    [1] main //main 線程,程序入口

    從上面的輸出內容可以看出:一個 Java 程序的運行是 main 線程和多個其他線程同時運行

    2. 請簡要描述線程與進程的關係,區別及優缺點?

    從 JVM 角度說進程和線程之間的關係

    2.1. 圖解進程和線程的關係

    下圖是 Java 內存區域,通過下圖我們從 JVM 的角度來說一下線程和進程之間的關係。如果你對 Java 內存區域 (運行時數據區) 這部分知識不太了解的話可以閱讀一下這篇文章:

    從上圖可以看出:一個進程中可以有多個線程,多個線程共享進程的方法區 (JDK1.8 之後的元空間)資源,但是每個線程有自己的程序計數器虛擬機棧本地方法棧

    總結: 線程 是 進程 劃分成的更小的運行單位。線程和進程最大的不同在於基本上各進程是獨立的,而各線程則不一定,因為同一進程中的線程極有可能會相互影響。線程執行開銷小,但不利於資源的管理和保護;而進程正相反

    下面是該知識點的擴展內容!

    下面來思考這樣一個問題:為什麼程序計數器虛擬機棧本地方法棧是線程私有的呢?為什麼堆和方法區是線程共享的呢?

    2.2. 程序計數器為什麼是私有的?

    程序計數器主要有下面兩個作用:

    1. 字節碼解釋器通過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如:順序執行、選擇、循環、異常處理。
    2. 在多線程的情況下,程序計數器用於記錄當前線程執行的位置,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒了。

    需要注意的是,如果執行的是 native 方法,那麼程序計數器記錄的是 undefined 地址,只有執行的是 Java 代碼時程序計數器記錄的才是下一條指令的地址。

    所以,程序計數器私有主要是為了線程切換后能恢復到正確的執行位置

    2.3. 虛擬機棧和本地方法棧為什麼是私有的?

    • 虛擬機棧: 每個 Java 方法在執行的同時會創建一個棧幀用於存儲局部變量表、操作數棧、常量池引用等信息。從方法調用直至執行完成的過程,就對應着一個棧幀在 Java 虛擬機棧中入棧和出棧的過程。
    • 本地方法棧: 和虛擬機棧所發揮的作用非常相似,區別是: 虛擬機棧為虛擬機執行 Java 方法 (也就是字節碼)服務,而本地方法棧則為虛擬機使用到的 Native 方法服務。 在 HotSpot 虛擬機中和 Java 虛擬機棧合二為一。

    所以,為了保證線程中的局部變量不被別的線程訪問到,虛擬機棧和本地方法棧是線程私有的。

    2.4. 一句話簡單了解堆和方法區

    堆和方法區是所有線程共享的資源,其中堆是進程中最大的一塊內存,主要用於存放新創建的對象 (所有對象都在這裏分配內存),方法區主要用於存放已被加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。

    3. 說說併發與并行的區別?

    • 併發: 同一時間段,多個任務都在執行 (單位時間內不一定同時執行);
    • 并行: 單位時間內,多個任務同時執行。

    4. 為什麼要使用多線程呢?

    先從總體上來說:

    • 從計算機底層來說: 線程可以比作是輕量級的進程,是程序執行的最小單位,線程間的切換和調度的成本遠遠小於進程。另外,多核 CPU 時代意味着多個線程可以同時運行,這減少了線程上下文切換的開銷。
    • 從當代互聯網發展趨勢來說: 現在的系統動不動就要求百萬級甚至千萬級的併發量,而多線程併發編程正是開發高併發系統的基礎,利用好多線程機制可以大大提高系統整體的併發能力以及性能。

    再深入到計算機底層來探討:

    • 單核時代: 在單核時代多線程主要是為了提高 CPU 和 IO 設備的綜合利用率。舉個例子:當只有一個線程的時候會導致 CPU 計算時,IO 設備空閑;進行 IO 操作時,CPU 空閑。我們可以簡單地說這兩者的利用率目前都是 50%左右。但是當有兩個線程的時候就不一樣了,當一個線程執行 CPU 計算時,另外一個線程可以進行 IO 操作,這樣兩個的利用率就可以在理想情況下達到 100%了。
    • 多核時代: 多核時代多線程主要是為了提高 CPU 利用率。舉個例子:假如我們要計算一個複雜的任務,我們只用一個線程的話,CPU 只會一個 CPU 核心被利用到,而創建多個線程就可以讓多個 CPU 核心被利用到,這樣就提高了 CPU 的利用率。

    5. 使用多線程可能帶來什麼問題?

    併發編程的目的就是為了能提高程序的執行效率提高程序運行速度,但是併發編程並不總是能提高程序運行速度的,而且併發編程可能會遇到很多問題,比如:內存泄漏、上下文切換、死鎖還有受限於硬件和軟件的資源閑置問題。

    6. 說說線程的生命周期和狀態?

    Java 線程在運行的生命周期中的指定時刻只可能處於下面 6 種不同狀態的其中一個狀態(圖源《Java 併發編程藝術》4.1.4 節)。

    線程在生命周期中並不是固定處於某一個狀態而是隨着代碼的執行在不同狀態之間切換。Java 線程狀態變遷如下圖所示(圖源《Java 併發編程藝術》4.1.4 節):

    由上圖可以看出:線程創建之後它將處於 NEW(新建) 狀態,調用 start() 方法后開始運行,線程這時候處於 READY(可運行) 狀態。可運行狀態的線程獲得了 CPU 時間片(timeslice)后就處於 RUNNING(運行) 狀態。

    操作系統隱藏 Java 虛擬機(JVM)中的 RUNNABLE 和 RUNNING 狀態,它只能看到 RUNNABLE 狀態(圖源::),所以 Java 系統一般將這兩個狀態統稱為 RUNNABLE(運行中) 狀態 。

    當線程執行 wait()方法之後,線程進入 WAITING(等待) 狀態。進入等待狀態的線程需要依靠其他線程的通知才能夠返回到運行狀態,而 TIME_WAITING(超時等待) 狀態相當於在等待狀態的基礎上增加了超時限制,比如通過 sleep(long millis)方法或 wait(long millis)方法可以將 Java 線程置於 TIMED WAITING 狀態。當超時時間到達后 Java 線程將會返回到 RUNNABLE 狀態。當線程調用同步方法時,在沒有獲取到鎖的情況下,線程將會進入到 BLOCKED(阻塞) 狀態。線程在執行 Runnable 的run()方法之後將會進入到 TERMINATED(終止) 狀態。

    7. 什麼是上下文切換?

    多線程編程中一般線程的個數都大於 CPU 核心的個數,而一個 CPU 核心在任意時刻只能被一個線程使用,為了讓這些線程都能得到有效執行,CPU 採取的策略是為每個線程分配時間片並輪轉的形式。當一個線程的時間片用完的時候就會重新處於就緒狀態讓給其他線程使用,這個過程就屬於一次上下文切換。

    概括來說就是:當前任務在執行完 CPU 時間片切換到另一個任務之前會先保存自己的狀態,以便下次再切換回這個任務時,可以再加載這個任務的狀態。任務從保存到再加載的過程就是一次上下文切換

    上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統來說意味着消耗大量的 CPU 時間,事實上,可能是操作系統中時間消耗最大的操作。

    Linux 相比與其他操作系統(包括其他類 Unix 系統)有很多的優點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。

    8. 什麼是線程死鎖?如何避免死鎖?

    8.1. 認識線程死鎖

    多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由於線程被無限期地阻塞,因此程序不可能正常終止。

    如下圖所示,線程 A 持有資源 2,線程 B 持有資源 1,他們同時都想申請對方的資源,所以這兩個線程就會互相等待而進入死鎖狀態。

    下面通過一個例子來說明線程死鎖,代碼模擬了上圖的死鎖的情況 (代碼來源於《併發編程之美》):

    public class DeadLockDemo {
        private static Object resource1 = new Object();//資源 1
        private static Object resource2 = new Object();//資源 2
    
        public static void main(String[] args) {
            new Thread(() -> {
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get resource2");
                    synchronized (resource2) {
                        System.out.println(Thread.currentThread() + "get resource2");
                    }
                }
            }, "線程 1").start();
    
            new Thread(() -> {
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get resource1");
                    synchronized (resource1) {
                        System.out.println(Thread.currentThread() + "get resource1");
                    }
                }
            }, "線程 2").start();
        }
    }

    Output

    Thread[線程 1,5,main]get resource1
    Thread[線程 2,5,main]get resource2
    Thread[線程 1,5,main]waiting get resource2
    Thread[線程 2,5,main]waiting get resource1

    線程 A 通過 synchronized (resource1) 獲得 resource1 的監視器鎖,然後通過Thread.sleep(1000);讓線程 A 休眠 1s 為的是讓線程 B 得到執行然後獲取到 resource2 的監視器鎖。線程 A 和線程 B 休眠結束了都開始企圖請求獲取對方的資源,然後這兩個線程就會陷入互相等待的狀態,這也就產生了死鎖。上面的例子符合產生死鎖的四個必要條件。

    學過操作系統的朋友都知道產生死鎖必須具備以下四個條件:

    1. 互斥條件:該資源任意一個時刻只由一個線程佔用。
    2. 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
    3. 不剝奪條件:線程已獲得的資源在末使用完之前不能被其他線程強行剝奪,只有自己使用完畢后才釋放資源。
    4. 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關係。

    8.2. 如何避免線程死鎖?

    我們只要破壞產生死鎖的四個條件中的其中一個就可以了。

    破壞互斥條件

    這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。

    破壞請求與保持條件

    一次性申請所有的資源。

    破壞不剝奪條件

    佔用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它佔有的資源。

    破壞循環等待條件

    靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞循環等待條件。

    我們對線程 2 的代碼修改成下面這樣就不會產生死鎖了。

            new Thread(() -> {
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread() + "waiting get resource2");
                    synchronized (resource2) {
                        System.out.println(Thread.currentThread() + "get resource2");
                    }
                }
            }, "線程 2").start();

    Output

    Thread[線程 1,5,main]get resource1
    Thread[線程 1,5,main]waiting get resource2
    Thread[線程 1,5,main]get resource2
    Thread[線程 2,5,main]get resource1
    Thread[線程 2,5,main]waiting get resource2
    Thread[線程 2,5,main]get resource2
    
    Process finished with exit code 0

    我們分析一下上面的代碼為什麼避免了死鎖的發生?

    線程 1 首先獲得到 resource1 的監視器鎖,這時候線程 2 就獲取不到了。然後線程 1 再去獲取 resource2 的監視器鎖,可以獲取到。然後線程 1 釋放了對 resource1、resource2 的監視器鎖的佔用,線程 2 獲取到就可以執行了。這樣就破壞了破壞循環等待條件,因此避免了死鎖。

    9. 說說 sleep() 方法和 wait() 方法區別和共同點?

    • 兩者最主要的區別在於:sleep 方法沒有釋放鎖,而 wait 方法釋放了鎖
    • 兩者都可以暫停線程的執行。
    • Wait 通常被用於線程間交互/通信,sleep 通常被用於暫停執行。
    • wait() 方法被調用后,線程不會自動蘇醒,需要別的線程調用同一個對象上的 notify() 或者 notifyAll() 方法。sleep() 方法執行完成后,線程會自動蘇醒。或者可以使用wait(long timeout)超時后線程會自動蘇醒。

    10. 為什麼我們調用 start() 方法時會執行 run() 方法,為什麼我們不能直接調用 run() 方法?

    這是另一個非常經典的 java 多線程面試問題,而且在面試中會經常被問到。很簡單,但是很多人都會答不上來!

    new 一個 Thread,線程進入了新建狀態;調用 start() 方法,會啟動一個線程並使線程進入了就緒狀態,當分配到時間片后就可以開始運行了。 start() 會執行線程的相應準備工作,然後自動執行 run() 方法的內容,這是真正的多線程工作。 而直接執行 run() 方法,會把 run 方法當成一個 main 線程下的普通方法去執行,並不會在某個線程中執行它,所以這並不是多線程工作。

    總結: 調用 start 方法方可啟動線程並使線程進入就緒狀態,而 run 方法只是 thread 的一個普通方法調用,還是在主線程里執行。

    開源項目推薦

    作者的其他開源項目推薦:

    1. :【Java學習+面試指南】 一份涵蓋大部分Java程序員所需要掌握的核心知識。
    2. : 適合新手入門以及有經驗的開發人員查閱的 Spring Boot 教程(業餘時間維護中,歡迎一起維護)。
    3. : 我覺得技術人員應該有的一些好習慣!
    4. :從零入門 !Spring Security With JWT(含權限驗證)後端部分代碼。

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

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

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

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

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

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

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

    一二面(基礎面)

    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. 方式方法:先完成業務上的主要問題,做到極致,然後逐步向目標靠攏

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

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

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

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

  • JAVA繼承中子父類的構造方法

    JAVA繼承中子父類的構造方法

     

    首先,構造方法本身會有一個隱式的無參構造(默認):

      ①不寫構造方法,類中的第一行代碼事實上有一個默認的無參構造(系統會隱式為你寫好)

        

    public class Student {
            private String name;
    //        public Student() {}      隱式地“寫”在這裏,你看不見
    
    //  Strudent類中雖然沒有寫構造方法,但相當於有上面的無參構造
    //   只不過是隱式的,你看不見
    
    }

     

      ②只寫帶參構造方法,相當於只有該帶參構造方法(隱式的無參構造會被屏蔽無視掉,視為無效)

    public class Student {
            private String name;
            public Student(String name) {
                this.name=name;
            }
    //  此時原來Strudent類中的隱式的無參構造方法被屏蔽了,無效了
    //  類中只有帶參構造
    
    }

     

      ③若想同時擁有無參和帶參構造,必須顯式地寫出無參和帶參構造方法

    public class Student {
            private String name;
            public Student() {}
    // 顯式地將無參構造寫出來        
            public Student(String name) {
                this.name=name;
            }
    //  若想Strudent類中擁有無參構造方法,必須顯式地寫出來
    
    
    }

     

    進一步結合繼承,就需要考慮到子父類:

      ④在子類的構造方法(無論是無參和有參)中,方法中的第一行代碼事實上都隱式地包含了父類的無參構造方法
        即: super()

    public class Stu extends Student {
        private String name;
        public Stu() {
        // super();
        // 在子類的無參構造中,super()是隱式的“寫”在這裏的
        }
        
        public Stu(String name) {
        // super();
        this.name=name;
        // 在子類的帶參構造,上面的super()同樣也是隱式的“寫”在這裏的
        }
    
    }

    這就是為什麼,調用子類的構造方法時,都會先調用父類的無參構造方法了,因為默認的super()存在。

     ⑤同理,類似與上面的②,此時若寫一個有參構造,super(xx)會把隱式的super()屏蔽掉 

    public class Stu extends Student {
        private String name;
        
        public Stu(String name) {
        // super();  原來隱式寫在這裏的super()被屏蔽了,無效了
        super(name);
        
        // 在子類的帶參構造, 由於的super(name)的存在,super()無效了 //此時子類的帶參構造中,只有super(name)
        }
    
    }

     

    這就是為什麼當父類沒有無參構造(即只有帶參構造——對應情況②)時,子類的構造方法編譯無法通過。這是因為子類的構造函數(帶參或無參)將調用父類的無參構造函數。 由於編譯器試圖向子類中的2個構造函數中插入super() ,但父類的默認構造函數未定義,因此編譯器會報告錯誤消息

    要解決這個問題,只需要

          1)添加一個無參構造函數給父類——顯式地在父類中添加無參構造

          2)刪除父類中自定義的有參構造函數——等價於恢復了默認的無參構造
          3)將 Super(XXX) 添加到子類構造函數——通過⑤的原來來屏蔽默認的super()  

     

    一些關於子父類有參無參的組合情況(其實就是排列組合)練習,有興趣的可以自己驗證一下,如下(圖片來源):

     

     

     

     

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

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

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

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

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

  • 阿里雲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集群系列:

     

     

     

     

     

     

     

     

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

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

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

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