分類: 3C資訊

  • 電動車發展快速,旭化成增產鋰電池材料「分隔模」

    電動車發展快速,旭化成增產鋰電池材料「分隔模」

     

    日刊工業新聞23日報導,因車廠加快電動車(EV)的研發腳步、帶動電池材料市場成長速度超乎預期,故旭化成(Asahi Kasei)計畫上修鋰離子電池關鍵材料「分隔膜(separator)」的增產計畫,目標在2020年結束前將分隔膜年產能最高擴增至15億平方公尺(m2)、將達現行的2.5倍,且將遠高於原先規劃的11億m2目標,期望藉由積極投資、鞏固全球龍頭位置。預估追加擴產所需的投資額約300億日圓。

    據報導,2016年全球分隔膜市場規模約15億m2、且預估2020年最高將擴大至35億m2的水準。就用途別來看,車用需求佔整體比重7成;就國別來看,因強化環保規範、提振EV需求急增的中國為全球最大市場、佔全球比重過半。

    旭化成於3月30日宣布,因電動車(EV)、油電混合車(HV)等車用鋰離子電池需求預估將呈現急速增長,故決議擴增鋰離子電池關鍵材料「分隔膜」產能,計畫投下約150億日圓,在守山製造所(滋賀縣守山市)增設年產能約2億平方公尺(m2)的分隔膜產線,並預計於2019年度上半年商轉。

    旭化成指出,待上述增產工程完工後,該公司整體分隔膜年產能將從現行的約6.6億m2提高3成至約8.6億m2。

    旭化成為全球分隔膜龍頭廠、全球市佔率達5成,目前於滋賀縣、宮崎縣以及美國和南韓生產分隔膜。

    根據日本市調機構富士經濟(Fuji Keizai)預估,2020年全球分隔膜市場規模將增至3,000億日圓、將達2015年的2倍水準,而EV、HV等車用用途是推動分隔膜需求急增的最大功臣,預估2020年車用分隔膜佔整體市場比重將達約45%。

    富士經濟6月22日公布調查報告指出,預估2030年時EV年銷售量將增至407萬台、超越HV(2030年銷售量預估為391萬台),且之後雙方的差距將持續擴大。富士經濟預估,在中國需求增加加持下,2035年EV全球銷售量將擴大至630萬台、將達2016年的13.4倍(較2016年增加12.4倍)。

    (本文內容由授權使用。圖片出處:)  

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

    【其他文章推薦】

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

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

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

  • Tesla計畫於上海建電動車廠,關稅考量為主因

    Tesla計畫於上海建電動車廠,關稅考量為主因

    電動車製造商特斯拉(Tesla)在Model X 的銷售和Model 3 的產能上都面臨著巨大挑戰,在全球最大的電動車市場──中國,Tesla 則看到了電動車需求持續成長帶來的機會,並希望能夠透過投資建廠、本土化製造等方式在中國電動車市場分一杯羹,據悉Tesla 將在上海建造海外第一座電動車製造工廠,未來可能會用於Model 3 電動車的生產。

    據知情人士透露,特斯拉已經與上海市政府達成合作協議,將首次在中國生產製造電動車,此次合作將有助於特斯拉進一步提升在中國市場的銷售,目前中國是全球最大的電動車市場,政府對於電動車的銷售和生產有許多優惠政策和補貼。

    特斯拉的製造工廠將建在上海臨港開發區,細節正在確認中,將在本月晚些時候對外公開,據悉上海市政府要求該製造工廠必須由特斯拉和上海的合作夥伴共同投資建造,但是否持有控股權還不得而知。市場諮詢公司Dunne Automotive 總裁Michael Dune 表示,特斯拉有機會佔據中國電動車市場的領先地位,許多有實力和知名度的品牌公司都會選擇在上海建造生產基地。

    特斯拉選擇在上海生產電動車,有助於直接與中國汽車廠商的產品競爭,這比在美國生產再進口到中國市場銷售,至少能夠降低25% 的進口關稅,正是由於關稅的成本,Tesla Model S 和Model X 電動車在中國市場的價格比美國市場高一倍。

    中國政府已經將電動車產業做為戰略性的新興產業,目標是在未來10 年內將混合式和全電動車的銷量提升10 倍,2016 年中國市場電動車的銷量約為28.3 萬台,佔比全球銷量的41%,Tesla 2016 年營收大約為70 億美元,其中15% 來自中國市場。目前大約有200 家公司宣布在中國製造電動車的計畫,其中北汽汽車和比亞迪公司的電動車佔總銷量的89%。

    特斯拉CEO 伊隆·馬斯克(Elon Musk)早在3 年前就表示希望能夠在中國建設製造工廠,自2014 年以後每次到訪中國都會與大量政府官員見面。2017 年6 月初特斯拉宣布2017 年下半年在中國超級充電站建設計畫。目前特斯拉在中國大約有117 個超級充電站。

    (合作媒體:。圖片出處:public domain CC0)

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

    【其他文章推薦】

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

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

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

  • 科斯創供電動車固化劑 參加澳洲太陽能汽車挑戰賽

    科斯創供電動車固化劑 參加澳洲太陽能汽車挑戰賽

    如何讓未來交通盡可能的實現永續發展?一群來自德國亞琛工業大學和亞琛應用技術大學,高度積極的大學生提出這樣一個問題。為了尋找答案,他們全心全意地投入一項偉大的專案中:開發一輛太陽能汽車,參加被譽為全球最艱難的太陽能汽車賽事─「世界太陽能汽車挑戰賽」,該賽事將於今年10月8日-15日在澳洲舉行。為了實現這一想法,大約45名青年研究人員在教授們的大力支持下成立「亞琛太陽能戰車」 (Sonnenwagen Aachen)協會。

    作為全球領先的創新和永續性材料解決方案供應商,科思創與學生們一樣對此充滿熱情,並希望與他們共同跨越極限,促使專案圓滿完成。 科思創已與德國亞琛工業大學建立了長期合作關係,並作為材料和技術服務提供者與金牌贊助商,為「太陽能戰車」提供支援。 日前,雙方就該專案簽署了合作協定。

    太陽能汽車合作

    「永續發展是我們策略的一部分,我們支援這一野心勃勃的專案,年輕的研究者們也希望借此機會證明眾多創新和永續交通的概念如今已經可以實現。」 科思創負責創新的董事會成員兼營運長施樂文博士(Dr. Markus Steilemann)說,「在氣候保護和節約化石燃料方面,太陽能汽車貢獻顯著。 隨著我們的技術發展以及合作關係的深化,我們希望彰顯科思創致力於創新和永續發展的承諾,以及對青年才俊的強力支持。」

    「太陽能戰車」團隊第一主席Hendrik Löbberding對科思創表示歡迎:「我們很高興可以得到科思創的鼎力相助,最重要的是,科思創在材料方面的強大實力將使我們受益匪淺。」總部位於利物庫森的科思創,在將創新材料應用到太陽能交通方面已累積了豐富的經驗:作為「陽光動力號」太陽能飛機專案的官方合作夥伴,科思創為全球首次完全依靠太陽能的載人環球飛行做出了重大貢獻。

    賽道測試:含生物基固化劑的汽車塗料

    該澳洲賽事所經路線在10月的氣溫可高達攝氏45度,紫外線輻射強,空氣含塵量高。科思創希望藉「太陽能戰車」專案,在嚴酷氣候條件下對各種材料進行測試。其中最重要的產品應用是由全球領先的汽車塗料製造商PPG提供的三塗層聚氨酯塗料,該塗料尤其適用於由碳纖維複合材料製成的汽車車身部件。

    氣候條件對面漆會產生顯著影響。該PPG面漆採用科思創生產的生物基固化劑Desmodur® eco N 7300,該固化劑中70%的碳含量來自於生物基。

    此外,科思創生產的聚氨酯和聚碳酸酯材料也應用於「太陽能戰車」,幫助實現輕量化和空氣動力學的設計概念。

    對太陽能汽車的嚴峻考驗

    被譽為全球最艱難的太陽能汽車賽事─「世界太陽能汽車挑戰賽」,今年將迎向第30周年。每兩年,來自全球各地的團隊會駕駛各自製造的汽車,在不使用一滴燃油的條件下,在達爾文至阿德萊德的3000公里道路上展開競賽。

    來自亞琛的「太陽能戰車」將是今年挑戰賽「挑戰者組」中唯一參賽的德國汽車。團隊對於贏得比賽非常樂觀,來自「亞琛太陽能戰車」的Hendrik Löbberding表示:「我們在零排放汽車領域擁有豐富的經驗,而且裝備精良,期待與來自五大洲的其他約40個團隊一決高下。」

    兩名團隊成員曾駕駛電動汽車贏得為期四天、橫穿北萊茵-威斯特伐利亞的e-CROSS Germany 「氣候中和」汽車拉力賽。 在那之前一個月,「太陽能戰車」成員還陪同一支來自波鴻的團隊參加了2016年European Solar Challenge 24小時太陽能汽車挑戰賽。

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

    【其他文章推薦】

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

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

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

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

  • 美國原油均價下跌,市場憂產油過剩

    美國原油均價下跌,市場憂產油過剩

    美國能源資訊局(EIA)公佈,6月26日全美普通汽油平均零售價格為每加侖2.288美元,創下半年新低;較前週下跌3美分,較去年同期下跌4.1美分。各地區零售汽油均價全面下跌,西岸地區的零售汽油均價最高達每加侖2.826美元,較前週下跌3.4美分;墨西哥灣地區的零售汽油均價最低為每加侖2.053美元,較前週下跌3.8美分。NYMEX原油期貨上週下跌3.9%,因擔憂油市過剩以及美國產量持續增長的影響。

    美國汽車協會(AAA)報告表示,6月26日全美普通無鉛汽油平均零售價格為每加侖2.26美元,較前週下跌3美分,較一個月前下跌11美分,較去年同期下跌4美分。AAA表示,包括美國煉油廠原油加工量處在新高水平、汽油以及原油庫存高企,以及今年以來的需求表現較為疲弱等,都是造成零售汽油均價下跌的主因。美國汽油需求已經有所回升,6月16日當週,美國汽油日均需求較前週926.9萬桶增至981.6萬桶,逼近5月底創下新高的982.2萬桶。

    AAA表示,即將到來的美國獨立紀念日假期(7月4日),預計將有創同期新高的4,420萬人出遊(離家超過50英里),比去年還要增加125萬人或2.9%。其中,預計將有3,750萬人開車出遊,同樣較去年同期增加2.9%。AAA資深副總裁Bill Sutherland表示,就業市場強勁、薪資增加以及消費信心提高等,都是今年出遊人數將創下歷年同期新高的主要原因。AAA表示,當前美國零售汽油均價逼近歷年的同期新低,但鑑於下週的假期來臨,零售汽油均價可能會有小幅上漲。

    《Oilprice.com》報導,相比十年前在油田自然衰竭的影響下,市場認為全球的產油上限即將到來;如今市場更多的是認為石油消費的巔峰將會到來,主要因為電動車興起的影響。一份調查顯示,如果電動車的年增長率維持在60%,則2023年的全球石油日需求量將會比當前減少200萬桶;如果年增長率為30%,則2025年的全球石油日需求量會比當前減少200萬桶。

    不過,實際數據顯示,至少目前為止石油需求增長並未下滑;過去十年全球石油日需求量年均增長110萬桶,過去五年則年均增長140萬桶,2016年則增長160萬桶,而去年的電動車銷售增長41%。報導認為,包括人口以及中產階級增長,以及開發中國家汽車銷售持續增加等,都是令全球石油需求仍持續攀高的主因。

    (本文內容由授權使用。圖片出處:wikipedia)

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

    【其他文章推薦】

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

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

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

  • 市場電動車需求上升,Nissan將對現有車款EV化

    市場電動車需求上升,Nissan將對現有車款EV化

    日經新聞報導,日產汽車(Nissan)將大舉擴充電動車(EV)產品陣容,日產社長兼CEO西川廣人27日於橫濱市舉行的定期股東會上表示,「今年度將推出新型『Leaf』,且中期來看,將推動現行已進行量產販售的車款EV化」。

    因北美、中國加強環保規範,帶動EV有望進一步普及。日產目前的EV車款僅有「Leaf」等少數幾款,而之後計畫將SUV、輕型汽車以及商用車進行EV化。

    另外,日產會長Carlos Ghosn也在股東會上表示,「日產在EV界居領導位置。日產EV累計銷售量超過60萬台、為美國特斯拉(Tesla)的2倍」。

    日本市調機構富士經濟(Fuji Keizai)6月22日公布銷售動向報告指出,EV在2025年以後需求將急速增加,預估2030年時EV年銷售量將增至407萬台、超越油電混合車(HV、2030年銷售量預估為391萬台),且之後雙方的差距將持續擴大。在中國需求增加加持下,2035年EV全球銷售量將擴大至630萬台、將達2016年的13.4倍(較2016年增加12.4倍)。

    (本文內容由授權使用。圖片出處:)

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

    【其他文章推薦】

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

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

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

  • Json模塊和Pickle模塊的使用

    在對數據進行序列化和反序列化是常見的數據操作,Python提供了兩個模塊方便開發者實現數據的序列化操作,即 json 模塊和 pickle 模塊。這兩個模塊主要區別如下:

    • json 是一個文本序列化格式,而 pickle 是一個二進制序列化格式;
    • json 是我們可以直觀閱讀的,而 pickle 不可以;
    • json 是可互操作的,在 Python 系統之外廣泛使用,而 pickle 則是 Python 專用的;
    • 默認情況下,json 只能表示 Python 內置類型的子集,不能表示自定義的類;但 pickle 可以表示大量的 Python 數據類型。

    Json 模塊

    Json 是一種輕量級的數據交換格式,由於其具有傳輸數據量小、數據格式易解析等特點,它被廣泛應用於各系統之間的交互操作,作為一種數據格式傳遞數據。它包含多個常用函數,具體如下:

    dumps()函數

    dumps()函數可以將 Python 對象編碼成 Json 字符串。例如:

    #字典轉成json字符串 加上ensure_ascii=False以後,可以識別中文, indent=4是間隔4個空格显示
    
    import json         
    d={'小明':{'sex':'男','addr':'上海','age':26},'小紅':{ 'sex':'女','addr':'上海', 'age':24},}
    print(json.dumps(d,ensure_ascii=False,indent=4))
    
    #執行結果:
    {
        "小明": {
            "sex": "男",
            "addr": "上海",
            "age": 26
        },
        "小紅": {
            "sex": "女",
            "addr": "上海",
            "age": 24
        }
    }

    dump()函數

    dump()函數可以將 Python對象編碼成 json 字符串,並自動寫入到文件中,不需要再單獨寫文件。例如:

    #字典轉成json字符串,不需要寫文件,自動轉成的json字符串寫入到‘users.json’的文件中 
    import json                                                                         
    d={'小明':{'sex':'男','addr':'上海','age':26},'小紅':{ 'sex':'女','addr':'上海', 'age':24},}
    #打開一個名字為‘users.json’的空文件
    fw =open('users.json','w',encoding='utf-8')
    
    json.dump(d,fw,ensure_ascii=False,indent=4)

    loads()函數

    loads()函數可以將 json 字符串轉換成 Python 的數據類型。例如:

    #這是users.json文件中的內容
    {
        "小明":{
            "sex":"男",
            "addr":"上海",
            "age":26
        },
        "小紅":{
            "sex":"女",
            "addr":"上海",
            "age":24
        }
    }
    
    #!/usr/bin/python3
    #把json串變成python的數據類型   
    import json  
    #打開‘users.json’的json文件
    f =open('users.json','r',encoding='utf-8')
    #讀文件
    res=f.read()
    print(json.loads(res))
    
    #執行結果:
    {'小明': {'sex': '男', 'addr': '上海', 'age': 26}, '小紅': {'sex': '女', 'addr': '上海', 'age': 24}}

    load()函數

    load()loads()功能相似,load()函數可以將 json 字符串轉換成 Python 數據類型,不同的是前者的參數是一個文件對象,不需要再單獨讀此文件。例如:

    #把json串變成python的數據類型:字典,傳一個文件對象,不需要再單獨讀文件 
    import json   
    #打開文件
    f =open('users.json','r',encoding='utf-8') 
    print(json.load(f))
    
    #執行結果:
    {'小明': {'sex': '男', 'addr': '上海', 'age': 26}, '小紅': {'sex': '女', 'addr': '上海', 'age': 24}}

    Pickle 模塊

    Pickle 模塊與 Json 模塊功能相似,也包含四個函數,即 dump()、dumps()、loads() 和 load(),它們的主要區別如下:

    • dumps 和 dump 的區別在於前者是將對象序列化,而後者是將對象序列化並保存到文件中。
    • loads 和 load 的區別在於前者是將序列化的字符串反序列化,而後者是將序列化的字符串從文件讀取並反序列化。

    dumps()函數

    dumps()函數可以將數據通過特殊的形式轉換為只有python語言認識的字符串,例如:

    import pickle
    # dumps功能
    import pickle
    data = ['A', 'B', 'C','D']  
    print(pickle.dumps(data))
    
    b'\x80\x03]q\x00(X\x01\x00\x00\x00Aq\x01X\x01\x00\x00\x00Bq\x02X\x01\x00\x00\x00Cq\x03X\x01\x00\x00\x00Dq\x04e.'

    dump()函數

    dump()函數可以將數據通過特殊的形式轉換為只有python語言認識的字符串,並寫入文件。例如:

    # dump功能
    with open('test.txt', 'wb') as f:
        pickle.dump(data, f)
    print('寫入成功')
    
    寫入成功

    loads()函數

    loads()函數可以將pickle數據轉換為python的數據結構。例如:

    # loads功能
    msg = pickle.loads(datastr)
    print(msg)
    
    ['A', 'B', 'C', 'D']

    load()函數

    load()函數可以從數據文件中讀取數據,並轉換為python的數據結構。例如:

    # load功能
    with open('test.txt', 'rb') as f:
       data = pickle.load(f)
    print(data)
    
    ['A', 'B', 'C', 'D']

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

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

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

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

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

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

  • 樹莓派3B/3B+和4B安裝OpenCV教程 (屢試不爽)

    樹莓派3B/3B+和4B安裝OpenCV教程 (屢試不爽)

        

    安裝前準備

    1. 在樹莓派上拓展文件系統

        如果你使用的樹莓派為新裝的系統,那麼第一件事情就是擴展文件系統,以包括microSD卡上的所有空間。

        具體步驟如下:

          1.在樹莓派終端(或者SSH)上輸入:

    1 $ sudo raspi-config

     

          2.然後選擇“高級選項”菜單項

       

           3.然後選擇“擴展文件系統”:

      

           4. 選擇第一個選項“A1.Expand Filesystem”,按鍵盤上的Enter鍵,完成後點擊“Finish”按鈕,重新啟動樹莓派。

        如果不能重啟,則可以執行以下操作:

    1 $ sudo reboot

     

          重新啟動后,文件系統已經擴展為包括micro-SD卡上的所有空間。可以通過執行 df -h 檢查輸出來驗證磁盤是否已擴展。

    1 $ df -h

     

     

      

     

          5. 此時我的樹莓派文件系統已擴展為包含16GB的micor-SD卡。如果您使用的是8GB卡,則可能使用了將近50%的可用空間,

        因此,一件簡單的事情就是刪除LibreOffice和Wolfram引擎以釋放Pi上的一些空間:

    1 $ sudo apt-get purge wolfram-engine
    2 $ sudo apt-get purge libreoffice*
    3 $ sudo apt-get clean
    4 $ sudo apt-get autoremove

     

     

      2.更換樹莓派源為清華鏡像源,防止後面下載GTK2.0失敗。

        換源方法參考:

    安裝步驟

       1.更新系統

    1 $ sudo apt-get update && sudo apt-get upgrade

       

       2.在樹莓派上安裝OpenCV所需要依賴的工具和一些圖像視頻庫

    • 安裝包括CMake的開發人員工具

      1 // 安裝build-essential、cmake、git和pkg-config
      2 sudo apt-get install build-essential cmake git pkg-config 

       

    • 安裝常用圖像工具包

      1 // 安裝jpeg格式圖像工具包
      2 sudo apt-get install libjpeg8-dev 

       

      1 // 安裝tif格式圖像工具包
      2 sudo apt-get install libtiff5-dev 

       

      1 // 安裝JPEG-2000圖像工具包
      2 sudo apt-get install libjasper-dev 

       

      1 // 安裝png圖像工具包
      2 sudo apt-get install libpng12-dev 

       

    • 安裝常用的視頻庫

    1 //v4l中4後面的是 英文字母“l”
    2 sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev

     

      

    • 安裝GTK2.0

      1 sudo apt-get install libgtk2.0-dev

       

    • 安裝OpenCV數值優化函數包

    1 sudo apt-get install libatlas-base-dev gfortran

     

        

     

        3.下載編譯OpenCV源碼

    • 下載opencv3.4.3和opencv_contrib3.4.3

    1 // 下載OpenCV
    2 wget -O opencv-3.4.3.zip https://github.com/Itseez/opencv/archive/3.4.3.zip

     

       

    1 // 解壓OpenCV
    2 unzip opencv-3.4.3.zip

     

       

    1 // 下載OpenCV_contrib庫:
    2 wget -O opencv_contrib-3.4.3.zip https://github.com/Itseez/opencv_contrib/archive/3.4.3.zip

     

       

     

    1 // 解壓OpenCV_contrib庫:
    2 unzip opencv_contrib-3.4.3.zip

       

    • 配置CMake編譯OpenCV 3環境

        使用CMake設置編譯,然後運行 make 來編譯OpenCV。這是整個過程中耗時最長的步驟,大約4個小時。

        回到OpenCV存儲庫並創建 build 文件夾,用來存放 CMake 編譯時產生的臨時文件。

    1 //具體路徑請以實際為準
    2 cd ~/opencv-3.4.3
    3 
    4 // 新建build文件夾
    5 mkdir build
    6      
    7 // 進入build文件夾
    8 cd build
    9   

       

     

    •  設置CMake編譯參數,安裝目錄默認為/usr/local

        注意參數名、等號和參數值之間不能有空格,每行末尾“\”之前有空格,這裏使用換行符“\”是為了看起來工整,參數值最後是兩個英文的點,意思是上級

      目錄(【注意】如果在root用戶下執行cmake命令,請將OPENCV_EXTRA_MODULES_PATH的值改為絕對路徑,如:/home/pi/opencv_contrib-3.4.3/modules):

    /** CMAKE_BUILD_TYPE是編譯方式
    * CMAKE_INSTALL_PREFIX是安裝目錄
    * OPENCV_EXTRA_MODULES_PATH是加載額外模塊
    * INSTALL_PYTHON_EXAMPLES是安裝官方python例程
    * BUILD_EXAMPLES是編譯例程(這兩個可以不加,不加編譯稍微快一點點,想要C語言的例程的話,在最後一行前加參數INSTALL_C_EXAMPLES=ON,要C++例程的話在最後一行前加參數INSTALL_C_EXAMPLES=ONINSTALL_CXX_EXAMPLES=ON)
    **/
     
    sudo cmake -D CMAKE_BUILD_TYPE=RELEASE \
        -D CMAKE_INSTALL_PREFIX=/usr/local \
        -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib-3.4.3/modules \
        -D INSTALL_PYTHON_EXAMPLES=ON \
        -D INSTALL_CXX_EXAMPLES=ON \
        -D BUILD_EXAMPLES=ON ..
     

       

     

         配置完后如下圖:

       

     

    • 備份build文件中的東西

         因為下一步的編譯會使用build文件中的東西,假如編譯失敗后還要重新進行cmake,比較耽誤時間,這裏可以直接備份一下cmake好的build文件夾,

      命名為build1,重新make的時候可以拿來用。

    1 //返回上層目錄
    2 cd ..
    3 //備份release文件夾
    4 cp -r release ./release1

      

    • 為樹莓派增加SWAP

        在開始編譯之前,建議你增加交換空間。這將使你使用樹莓派的所有四個內核來編譯OpenCV,而不會由於內存耗盡導致編譯掛起。

        打開 etc dphys – swapfile   文件:

    1 $ sudo nano /etc/dphys-swapfile

        然後編輯 CONF_SWAPSIZE  變量:

       

     

          注意:此處我將交換空間從100MB增加到2048MB;如果你不執行此步驟,你的樹莓派編譯時很可能掛起。

        重新啟動交換服務:

    1 $ sudo /etc/init.d/dphys-swapfile stop
    2 $ sudo /etc/init.d/dphys-swapfile start

       

     

         注意:增加交換空間的大小是燒壞樹莓派 microSD卡的好方法。基於閃存的存儲只能執行有限數量的寫操作,直到該卡基本不能夠容納1和0。我們只能在短時間內

      啟動大型交換,所以這沒什麼大問題的。

    • 編譯OpenCV 3

    1 /**
    2 * 以管理員身份,否則容易出錯
    3 * make命令參數-j4指允許make使用最多4個線程進行編譯,這樣編譯速度會更快
    4 * 可以根據自己機器的情況進行更改
    5 * 使用tee命令可以將編譯過程中終端显示的信息保存到make.log文件中,便於查看,這樣即使VNC斷線,終端的* 信息太多看不到,也可以通過make.log文件查看編譯過程。
    6 **/
    7  
    8 sudo make -j4 2>&1 | tee make.log

       

     

        如果看到進度編譯到100%,那麼說明編譯安裝成功。

       

    1 // 安裝
    2 sudo make install
    3  
    4 // 更新動態鏈接庫
    5 sudo ldconfig

        

        注意: 不要忘記回到 /etc/dphysswapfile 文件:

           1.將 CONF_SWAPSIZE 重置為 100MB

           2.重新啟動交換服務

    其他配置

       設置庫的路徑,相當於windows下的環境變量,便於使用OpenCV庫,也可以不進行設置,使用的時候說明路徑也可。例如在編譯時說明使用庫的路徑是 -L/usr/local/lib 

     

      1.配置opencv.conf 

       打開opencv.conf配置文件,在末端加入如下內容: 

     1 //這裏我使用的是樹莓派默認的nano,也可以使用vim、gedit.打開opencv.conf文件
     2 sudo nano /etc/ld.so.conf.d/opencv.conf
     3 
     4 /**
     5 *在末端添加如下內容
     6 *注意:?表示一個空格,可能原因是有的語言要求最後有一個空格才可以編譯通過。
     7 **/
     8 /usr/local/lib
     9 ?
    10 
    11 //加載一下
    12sudo ldconfig

       

      2.打開 bash.bashrc 配置文件 

    1 //打開bash.bashrc配置文件
    2 sudo gedit /etc/bash.bashrc
    3 
    4 
    5 // 在最後添加如下內容
    6 PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig       
    7 export PKG_CONFIG_PATH

        

      3.重啟樹莓派

    1 sudo reboot

     

     檢測OpenCV使用是否正常

      python程序

     1 import cv2
     2 import numpy as np
     3 cv2.namedWindow("gray")
     4 img = np.zeros((512,512),np.uint8)#生成一張空的灰度圖像
     5 cv2.line(img,(0,0),(511,511),255,5)#繪製一條白色直線
     6 cv2.imshow("gray",img)#显示圖像
     7 #循環等待,按q鍵退出
     8 while True:
     9     key=cv2.waitKey(1)
    10     if key==ord("q"):
    11         break
    12 cv2.destoryWindow("gray")

      保存文件為 test.py ,並在終端運行程序

    1 sudo python3 test.py

      運行結果如下:

      

     

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

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

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

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

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

  • 精通awk系列(8):awk劃分字段的3種方式

    精通awk系列(8):awk劃分字段的3種方式

    回到:

    詳細分析awk字段分割

    awk讀取每一條記錄之後,會將其賦值給$0,同時還會對這條記錄按照預定義變量FS劃分字段,將劃分好的各個字段分別賦值給$1 $2 $3 $4...$N,同時將劃分的字段數量賦值給預定義變量NF

    引用字段的方式

    $N引用字段:

    • N=0:即$0,引用記錄本身
    • 0<N<=NF:引用對應字段
    • N>NF:表示引用不存在的字段,返回空字符串
    • N<0:報錯

    可使用變量或計算的方式指定要獲取的字段序號。

    awk '{n = 5;print $n}' a.txt
    awk '{print $(2+2)}' a.txt   # 括號必不可少,用於改變優先級
    awk '{print $(NF-3)}' a.txt

    分割字段的方式

    讀取record之後,將使用預定義變量FS、FIELDWIDTHS或FPAT中的一種來分割字段。分割完成之後,再進入main代碼段(所以,在main中設置FS對本次已經讀取的record是沒有影響的,但會影響下次讀取)。

    劃分字段方式(一):FS或-F

    FS或者-F:字段分隔符

    • FS為單個字符時,該字符即為字段分隔符
    • FS為多個字符時,則採用正則表達式模式作為字段分隔符
    • 特殊的,也是FS默認的情況,FS為單個空格時,將以連續的空白(空格、製表符、換行符)作為字段分隔符
    • 特殊的,FS為空字符串””時,將對每個字符都進行分隔,即每個字符都作為一個字段
    • 設置預定義變量IGNORECASE為非零值,正則匹配時表示忽略大小寫(隻影響正則,所以FS為單字時無影響)
    • 如果record中無法找到FS指定的分隔符(例如將FS設置為”\n”),則整個記錄作為一個字段,即$1$0相等
    # 字段分隔符指定為單個字符
    awk -F":" '{print $1}' /etc/passwd
    awk 'BEGIN{FS=":"}{print $1}' /etc/passwd
    
    # 字段分隔符指定為正則表達式
    awk 'BEGIN{FS=" +|@"}{print $1,$2,$3,$4,$5,$6}' a.txt

    劃分字段方式(二):FIELDWIDTHS

    指定預定義變量FIELDWIDTHS按字符寬度分割字段,這是gawk提供的高級功能。在處理某字段缺失時非常好用。

    用法:

    示例1:

    # 沒取完的字符串DDD被丟棄,且NF=3
    $ awk 'BEGIN{FIELDWIDTHS="2 3 2"}{print $1,$2,$3,$4}' <<<"AABBBCCDDDD"
    AA BBB CC 
    
    # 字符串不夠長度時無視
    $ awk 'BEGIN{FIELDWIDTHS="2 3 2 100"}{print $1,$2,$3,$4"-"}' <<<"AABBBCCDDDD"
    AA BBB CC DDDD-
    
    # *號取剩餘所有,NF=3
    $ awk 'BEGIN{FIELDWIDTHS="2 3 *"}{print $1,$2,$3}' <<<"AABBBCCDDDD"      
    AA BBB CCDDDD
    
    # 字段數多了,則取完字符串即可,NF=2
    $ awk 'BEGIN{FIELDWIDTHS="2 30 *"}{print $1,$2,NF}' <<<"AABBBCCDDDD"  
    AA BBBCCDDDD 2

    示例2:處理某些字段缺失的數據。

    如果按照常規的FS進行字段分割,則對於缺失字段的行和沒有缺失字段的行很難統一處理,但使用FIELDWIDTHS則非常方便。

    假設a.txt文本內容如下:

    ID  name    gender  age  email          phone
    1   Bob     male    28   abc@qq.com     18023394012
    2   Alice   female  24   def@gmail.com  18084925203
    3   Tony    male    21   aaa@163.com    17048792503
    4   Kevin   male    21   bbb@189.com    17023929033
    5   Alex    male    18                  18185904230
    6   Andy    female  22   ddd@139.com    18923902352
    7   Jerry   female  25   exdsa@189.com  18785234906
    8   Peter   male    20   bax@qq.com     17729348758
    9   Steven  female  23   bc@sohu.com    15947893212
    10  Bruce   female  27   bcbd@139.com   13942943905

    因為email字段有的是空字段,所以直接用FS劃分字段不便處理。可使用FIELDWIDTHS。

    # 字段1:4字符
    # 字段2:8字符
    # 字段3:8字符
    # 字段4:2字符
    # 字段5:先跳過3字符,再讀13字符,該字段13字符
    # 字段6:先跳過2字符,再讀11字符,該字段11字符
    awk '
    BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
    NR>1{
        print "<"$1">","<"$2">","<"$3">","<"$4">","<"$5">","<"$6">"
    }' a.txt
    
    # 如果email為空,則輸出它
    awk '
    BEGIN{FIELDWIDTHS="4 8 8 2 3:13 2:11"}
    NR>1{
        if($5 ~ /^ +$/){print $0}
    }' a.txt

    劃分字段方式(三):FPAT

    FS是指定字段分隔符,來取得除分隔符外的部分作為字段。

    FPAT是取得匹配的字符部分作為字段。它是gawk提供的一個高級功能。

    FPAT根據指定的正則來全局匹配record,然後將所有匹配成功的部分組成$1、$2...,不會修改$0

    • awk 'BEGIN{FPAT="[0-9]+"}{print $3"-"}' a.txt
    • 之後再設置FS或FPAT,該變量將失效

    FPAT常用於字段中包含了字段分隔符的場景。例如,CSV文件中的一行數據如下:

    Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA

    其中逗號分隔每個字段,但雙引號包圍的是一個字段整體,即使其中有逗號。

    這時使用FPAT來劃分各字段比使用FS要方便的多。

    echo 'Robbins,Arnold,"1234 A Pretty Street, NE",MyTown,MyState,12345-6789,USA' |\
    awk '
        BEGIN{FPAT="[^,]*|(\"[^\"]*\")"}
        {
            for (i=1;i<NF;i++){
                print "<"$i">"
            }
        }
    '

    最後,patsplit()函數和FPAT的功能一樣。

    檢查字段劃分的方式

    有FS、FIELDWIDTHS、FPAT三種獲取字段的方式,可使用PROCINFO數組來確定本次使用何種方式獲得字段。

    PROCINFO是一個數組,記錄了awk進程工作時的狀態信息。

    如果:

    • PROCINFO["FS"]=="FS",表示使用FS分割獲取字段
    • PROCINFO["FPAT"]=="FPAT",表示使用FPAT匹配獲取字段
    • PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS",表示使用FIELDWIDTHS分割獲取字段

    例如:

    if(PROCINFO["FS"]=="FS"){
        ...FS spliting...
    } else if(PROCINFO["FPAT"]=="FPAT"){
        ...FPAT spliting...
    } else if(PROCINFO["FIELDWIDTHS"]=="FIELDWIDTHS"){
        ...FIELDWIDTHS spliting...
    }

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

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

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

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

  • ThreadLocal原理分析與代碼驗證

    ThreadLocal提供了線程安全的數據存儲和訪問方式,利用不帶key的get和set方法,居然能做到線程之間隔離,非常神奇。

    比如

    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    in thread 1

    //in thread1
    treadLocal.set("value1");
    .....
    //value的值是value1
    String value = threadLocal.get();

    in thread 2

    //in thread2
    treadLocal.set("value2");
    .....
    //value的值是value2
    String value = threadLocal.get();

    不論thread1和thread2是不是同時執行,都不會有線程安全問題,我們來測試一下。

    線程安全測試

    開10個線程,每個線程內都對同一個ThreadLocal對象set不同的值,會發現ThreadLocal在每個線程內部get出來的值,只會是自己線程內set進去的值,不會被別的線程影響。

    static void testUsage() throws InterruptedException {
        Utils.println("-------------testUsage-------------------");
        ThreadLocal<Long> threadLocal = new ThreadLocal<>();
    
        AtomicBoolean threadSafe = new AtomicBoolean(true);
        int count = 10;
        CountDownLatch countDownLatch = new CountDownLatch(count);
        Random random = new Random(736832);
        for (int i = 0; i < count; i ++){
            new Thread(() -> {
                try {
                    //生成一個隨機數
                    Long value = System.nanoTime() + random.nextInt();
                    threadLocal.set(value);
                    Thread.sleep(1000);
    
                    Long value2 = threadLocal.get();
                    if (!value.equals(value2)) {
                        //get和set的value不一致,說明被別的線程修改了,但這是不可能出現的
                        threadSafe.set(false);
                        Utils.println("thread unsafe, this could not be happen!");
                    }
                } catch (InterruptedException e) {
    
                }finally {
                    countDownLatch.countDown();
                }
    
            }).start();
        }
    
        countDownLatch.await();
    
        Utils.println("all thread done, and threadSafe is " + threadSafe.get());
        Utils.println("------------------------------------------");
    }

    輸出:

    -------------testUsage------------------
    all thread done, and threadSafe is true
    -----------------------------------------

    原理淺析

    翻開ThreadLocal的源碼,會發現ThreadLocal只是一個空殼子,它並不存儲具體的value,而是利用當前線程(Thread.currentThread())的threadLocalMap來存儲value,key就是這個threadLocal對象本身。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    Thread的threadLocals字段是ThreadLocalMap類型(你可以簡單理解為一個key value的Map),key是ThreadLocal對象,value是我們在外層設置的值

    • 當我們調用threadLocal.set(value)方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去set key value
    • 當我們調用threadLocal.get()方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去get value
    • 當我們調用threadLocal.remove()方法的時候,會找到當前線程的threadLocals這個map,然後以this作為key去remove

    這就相當於:

    Thread.currentThread().threadLocals.set(threadLocal1, "value1");
    .....
    //value的值是value1
    String value = Thread.currentThread().threadLocals.get(threadLocal1);

    因為每個Thread都是不同的對象,所以他們的threadLocals也是不同的map,threadLocal在不同的線程里工作時,實際上是從不同的map里get/set,這也就是線程安全的原因了,了解到這一點就差不多了。

    再深入一些,ThreadLocalMap的結構

    如果繼續翻ThreadLocalMap的源碼,會發現它有個字段table,是Entry類型的數組。

    我們不妨寫段代碼,把ThreadLocalMap的結構輸出出來。

    由於Thread.threadLocals和ThreadLocalMap類不是public的,我們只有通過反射來獲取它的值。反射的代碼如下(如果嫌長可以不看,直接看輸出):

    static Object getThreadLocalMap(Thread thread) throws NoSuchFieldException, IllegalAccessException {        
        //get thread.threadLocals
        Field threadLocals = Thread.class.getDeclaredField("threadLocals");
        threadLocals.setAccessible(true);
        return threadLocals.get(thread);
    }
    
    static void printThreadLocalMap(Object threadLocalMap) throws NoSuchFieldException, IllegalAccessException {
        String threadName = Thread.currentThread().getName();
        
        if(threadLocalMap == null){
            Utils.println("threadMap is null, threadName:" + threadName);
            return;
        }
    
        Utils.println(threadName);
    
        //get threadLocalMap.table
        Field tableField = threadLocalMap.getClass().getDeclaredField("table");
        tableField.setAccessible(true);
        Object[] table = (Object[])tableField.get(threadLocalMap);
        Utils.println("----threadLocals (ThreadLocalMap), table.length = " + table.length);
    
        for (int i = 0; i < table.length; i ++){
            WeakReference<ThreadLocal<?>> entry = (WeakReference<ThreadLocal<?>>)table[i];
            printEntry(entry, i);
        }
    }
    static void printEntry(WeakReference<ThreadLocal<?>> entry, int i) throws NoSuchFieldException, IllegalAccessException {
        if(entry == null){
            Utils.println("--------table[" + i + "] -> null");
            return;
        }
        ThreadLocal key = entry.get();
        //get entry.value
        Field valueField = entry.getClass().getDeclaredField("value");
        valueField.setAccessible(true);
        Object value = valueField.get(entry);
    
        Utils.println("--------table[" + i + "] -> entry key = " + key + ", value = " + value);
    }

    測試代碼:

    static void testStructure() throws InterruptedException {
        Utils.println("-------------testStructure----------------");
        ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
        ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
    
        Thread thread1 = new Thread(() -> {
            threadLocal1.set("threadLocal1-value");
            threadLocal2.set("threadLocal2-value");
    
            try {
                Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
                printThreadLocalMap(threadLocalMap);
    
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }, "thread1");
    
        thread1.start();
    
        //wait thread1 done
        thread1.join();
    
        Thread thread2 = new Thread(() -> {
            threadLocal1.set("threadLocal1-value");
            try {
                Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
                printThreadLocalMap(threadLocalMap);
    
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }, "thread2");
    
        thread2.start();
        thread2.join();
        Utils.println("------------------------------------------");
    }

    我們在創建了兩個ThreadLocal的對象threadLocal1和threadLocal2,在線程1里為這兩個對象設置值,在線程2里只為threadLocal1設置值。然後分別打印出這兩個線程的threadLocalMap。

    輸出結果為:

    -------------testStructure----------------
    thread1
    ----threadLocals (ThreadLocalMap), table.length = 16
    --------table[0] -> null
    --------table[1] -> entry key = java.lang.ThreadLocal@33baa315, value = threadLocal2-value
    --------table[2] -> null
    --------table[3] -> null
    --------table[4] -> null
    --------table[5] -> null
    --------table[6] -> null
    --------table[7] -> null
    --------table[8] -> null
    --------table[9] -> null
    --------table[10] -> entry key = java.lang.ThreadLocal@4d42db5c, value = threadLocal1-value
    --------table[11] -> null
    --------table[12] -> null
    --------table[13] -> null
    --------table[14] -> null
    --------table[15] -> null
    thread2
    ----threadLocals (ThreadLocalMap), table.length = 16
    --------table[0] -> null
    --------table[1] -> null
    --------table[2] -> null
    --------table[3] -> null
    --------table[4] -> null
    --------table[5] -> null
    --------table[6] -> null
    --------table[7] -> null
    --------table[8] -> null
    --------table[9] -> null
    --------table[10] -> entry key = java.lang.ThreadLocal@4d42db5c, value = threadLocal1-value
    --------table[11] -> null
    --------table[12] -> null
    --------table[13] -> null
    --------table[14] -> null
    --------table[15] -> null
    ------------------------------------------

    從結果上可以看出:

    • 線程1和線程2的threadLocalMap對象的table字段,是個數組,長度都是16
    • 由於線程1里給兩個threadLocal對象設置了值,所以線程1的ThreadLocalMap里有兩個entry,數組下標分別是1和10,其餘的是null(如果你自己寫代碼驗證,下標不一定是1和10,不需要糾結這個問題,只要前後對的上就行)
    • 由於線程2里只給一個threadLocal對象設置了值,所以線程1的ThreadLocalMap里只有一個entry,數組下標是10,其餘的是null
    • threadLocal1這個對象在兩個線程里都設置了值,所以當它作為key加入二者的threadLocalMap時,key是一樣的,都是java.lang.ThreadLocal@4d42db5c;下標也是一樣的,都是10。

    為什麼是WeakReference

    查看Entry的源碼,會發現Entry繼承自WeakReference:

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

    構造函數里把key傳給了super,也就是說,ThreadLocalMap中對key的引用,是WeakReference的。

    Weak reference objects, which do not prevent their referents from being
    made finalizable, finalized, and then reclaimed. Weak references are most
    often used to implement canonicalizing mappings.

    通俗點解釋:

    當一個對象僅僅被weak reference(弱引用), 而沒有任何其他strong reference(強引用)的時候, 不論當前的內存空間是否足夠,當GC運行的時候, 這個對象就會被回收。

    看不明白沒關係,還是寫代碼測試一下什麼是WeakReference吧…

    static void testWeakReference(){
        Object obj1 = new Object();
        Object obj2 = new Object();
        WeakReference<Object> obj1WeakRef = new WeakReference<>(obj1);
        WeakReference<Object> obj2WeakRf = new WeakReference<>(obj2);
        //obj32StrongRef是強引用
        Object obj2StrongRef = obj2;
        Utils.println("before gc: obj1WeakRef = " + obj1WeakRef.get() + ", obj2WeakRef = " + obj2WeakRf.get() + ", obj2StrongRef = " + obj2StrongRef);
    
        //把obj1和obj2設為null
        obj1 = null;
        obj2 = null;
        //強制gc
        forceGC();
    
        Utils.println("after gc: obj1WeakRef = " + obj1WeakRef.get() + ", obj2WeakRef = " + obj2WeakRf.get() + ", obj2StrongRef = " + obj2StrongRef);
    }

    結果輸出:

    before gc: obj1WeakRef = java.lang.Object@4554617c, obj2WeakRef = java.lang.Object@74a14482, obj2StrongRef = java.lang.Object@74a14482
    after gc: obj1WeakRef = null, obj2WeakRef = java.lang.Object@74a14482, obj2StrongRef = java.lang.Object@74a14482

    從結果上可以看出:

    • 我們先new了兩個對象(為避免混淆,稱他們為Object1和Object2),分別用變量obj1和obj2指向它們,同時定義了一個obj2StrongRef,也指向Object2,最後把obj1和obj2均指向null
    • 由於Object1沒有變量強引用它了,所以在gc后,Object1被回收了,obj1WeakRef.get()返回了null
    • 由於Object2還有obj2StrongRef在引用它,所以gc后,Object2依然存在,沒有被回收。

    那麼,ThreadLocalMap中對key的引用,為什麼是WeakReference的呢?

    因為大部分情況下,線程不死

    大部分情況下,線程不會頻繁的創建和銷毀,一般都會用線程池。所以線程對象一般不會被清除,線程的threadLocalMap就一直存在。
    如果key對ThreadLocal是強引用,那麼key永遠不會被回收,即使我們程序里再也不用它了。

    但是key是弱引用的話,情況就會得到改善:只要沒有指向threadLocal的強引用了,這個ThreadLocal對象就會被清理。

    我們還是寫代碼測試一下吧。

    /**
     * 測試ThreadLocal對象什麼時候被回收
     * @throws InterruptedException
     */
    static void testGC() throws InterruptedException {
        Utils.println("-----------------testGC-------------------");
        Thread thread1 = new Thread(() -> {
            ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
            ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
    
            threadLocal1.set("threadLocal1-value");
            threadLocal2.set("threadLocal2-value");
    
            try {
                Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
                Utils.println("print threadLocalMap before gc");
                printThreadLocalMap(threadLocalMap);
    
                //set threadLocal1 unreachable
                threadLocal1 = null;
    
                forceGC();
    
                Utils.println("print threadLocalMap after gc");
                printThreadLocalMap(threadLocalMap);
    
    
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }, "thread1");
    
        thread1.start();
        thread1.join();
        Utils.println("------------------------------------------");
    }
    

    我們在一個線程里為兩個ThreadLocal對象賦值,最後把其中一個對象的強引用移除,gc后打印當前線程的threadLocalMap。
    輸出結果如下:

    -----------------testGC-------------------
    print threadLocalMap before gc
    thread1
    ----threadLocals (ThreadLocalMap), table.length = 16
    --------table[0] -> null
    --------table[1] -> entry key = java.lang.ThreadLocal@7bf9cebf, value = threadLocal2-value
    --------table[2] -> null
    --------table[3] -> null
    --------table[4] -> null
    --------table[5] -> null
    --------table[6] -> null
    --------table[7] -> null
    --------table[8] -> null
    --------table[9] -> null
    --------table[10] -> entry key = java.lang.ThreadLocal@56342d38, value = threadLocal1-value
    --------table[11] -> null
    --------table[12] -> null
    --------table[13] -> null
    --------table[14] -> null
    --------table[15] -> null
    print threadLocalMap after gc
    thread1
    ----threadLocals (ThreadLocalMap), table.length = 16
    --------table[0] -> null
    --------table[1] -> entry key = java.lang.ThreadLocal@7bf9cebf, value = threadLocal2-value
    --------table[2] -> null
    --------table[3] -> null
    --------table[4] -> null
    --------table[5] -> null
    --------table[6] -> null
    --------table[7] -> null
    --------table[8] -> null
    --------table[9] -> null
    --------table[10] -> entry key = null, value = threadLocal1-value
    --------table[11] -> null
    --------table[12] -> null
    --------table[13] -> null
    --------table[14] -> null
    --------table[15] -> null
    ------------------------------------------

    從輸出結果可以看到,當我們把threadLocal1的強引用移除並gc之後,table[10]的key變成了null,說明threadLocal1這個對象被回收了;threadLocal2的強引用還在,所以table[1]的key不是null,沒有被回收。

    但是你發現沒有,table[10]的key雖然是null了,但value還活着! table[10]這個entry對象,也活着!

    是的,因為只有key是WeakReference….

    無用的entry什麼時候被回收?

    通過查看ThreadLocal的源碼,發現在ThreadLocal對象的get/set/remove方法執行時,都有機會清除掉map中已經無用的entry。

    最容易驗證清除無用entry的場景分別是:

    • remove:這個不用說了,這哥們本來就是做這個的
    • get:當一個新的threadLocal對象(沒有set過value)發生get調用時,也會作為新的entry加入map,在加入的過程中,有機會清除掉無用的entry,邏輯和下面的set相同。
    • set: 當一個新的threadLocal對象(沒有set過value)發生set調用時,會在map中加入新的entry,此時有機會清除掉無用的entry,清除的邏輯是:
      • 清除掉table數組中的那些無用entry中的一部分,記住是一部分,這個一部分可能全部,也可能是0,具體算法請看ThreadLocalMap.cleanSomeSlots,這裏不解釋了。
      • 如果上一步的”一部分”是0(即清除了0個),並且map的size(是真實size,不是table.length)大於等於threshold(table.length的2/3),會執行一次rehash,在rehash的過程中,清理掉所有無用的entry,並減小size,清理后的size如果還大於等於threshold – threshold/4,則把table擴容為原來的兩倍大小。

    還有其他場景,但不好驗證,這裏就不提了。

    ThreadLocal源碼就不貼了,貼了也講不明白,相關邏輯在setInitialValue、cleanSomeSlots、expungeStaleEntries、rehash、resize等方法里。

    在我們寫代碼驗證entry回收邏輯之前,還需要簡單的提一下ThreadLocalMap的hash算法。

    entry數組的下標如何確定?

    每個ThreadLocal對象,都有一個threadLocalHashCode變量,在加入ThreadLocalMap的時候,根據這個threadLocalHashCode的值,對entry數組的長度取余(hash & (len – 1)),餘數作為下標。

    那麼threadLocalHashCode是怎麼計算的呢?看源碼:

    public class ThreadLocal<T>{
        private final int threadLocalHashCode = nextHashCode();
        private static AtomicInteger nextHashCode = new AtomicInteger();
    
        private static final int HASH_INCREMENT = 0x61c88647;
    
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }
        ...
    }

    ThreadLocal類維護了一個全局靜態字段nextHashCode,每new一個ThreadLocal對象,nextHashCode都會遞增0x61c88647,作為下一個ThreadLocal對象的threadLocalHashCode。

    這個0x61c88647,是個神奇的数字,只要以它為遞增值,那麼和2的N次方取余時,在有限的次數內不會發生重複。
    比如和16取余,那麼在16次遞增內,不會發生重複。還是寫代碼驗證一下吧。

    int hashCode = 0;
    int HASH_INCREMENT = 0x61c88647;
    int length = 16;
    
    for(int i = 0; i < length ; i ++){
        int h = hashCode & (length - 1);
        hashCode += HASH_INCREMENT;
        System.out.println("h = " + h + ", i = " + i);
    }

    輸出結果為:

    h = 0, i = 0
    h = 7, i = 1
    h = 14, i = 2
    h = 5, i = 3
    h = 12, i = 4
    h = 3, i = 5
    h = 10, i = 6
    h = 1, i = 7
    h = 8, i = 8
    h = 15, i = 9
    h = 6, i = 10
    h = 13, i = 11
    h = 4, i = 12
    h = 11, i = 13
    h = 2, i = 14
    h = 9, i = 15
    

    你看,h的值在16次遞增內,沒有發生重複。 但是要記住,2的N次方作為長度才會有這個效果,這也解釋了為什麼ThreadLocalMap的entry數組初始長度是16,每次都是2倍的擴容。

    驗證新threadLocal的get和set時回收部分無效的entry

    為了驗證出結果,我們需要先給ThreadLocal的nextHashCode重置一個初始值,這樣在測試的時候,每個threadLocal的數組下標才會按照我們設計的思路走。

    static void resetNextHashCode() throws NoSuchFieldException, IllegalAccessException {
        Field nextHashCodeField = ThreadLocal.class.getDeclaredField("nextHashCode");
        nextHashCodeField.setAccessible(true);
        nextHashCodeField.set(null, new AtomicInteger(1253254570));
    }

    然後在測試代碼里,我們先調用resetNextHashCode方法,然後加兩個ThreadLocal對象並set值,gc前把強引用去除,gc后再new兩個新的theadLocal對象,分別調用他們的get和set方法。
    在每個關鍵點打印出threadLocalMap做比較。

    static void testExpungeSomeEntriesWhenGetOrSet() throws InterruptedException {
        Utils.println("----------testExpungeStaleEntries----------");
        Thread thread1 = new Thread(() -> {
            try {
                resetNextHashCode();
    
                //注意,這裏必須有兩個ThreadLocal,才能驗證出threadLocal1被清理
                ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
                ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
    
                threadLocal1.set("threadLocal1-value");
                threadLocal2.set("threadLocal2-value");
    
    
                Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
                //set threadLocal1 unreachable
                threadLocal1 = null;
                threadLocal2 = null;
                forceGC();
    
                Utils.println("print threadLocalMap after gc");
                printThreadLocalMap(threadLocalMap);
    
                ThreadLocal<String> newThreadLocal1 = new ThreadLocal<>();
                newThreadLocal1.get();
                Utils.println("print threadLocalMap after call a new newThreadLocal1.get");
                printThreadLocalMap(threadLocalMap);
    
                ThreadLocal<String> newThreadLocal2 = new ThreadLocal<>();
                newThreadLocal2.set("newThreadLocal2-value");
                Utils.println("print threadLocalMap after call a new newThreadLocal2.set");
                printThreadLocalMap(threadLocalMap);
    
    
            } catch (NoSuchFieldException | IllegalAccessException e) {
                e.printStackTrace();
            }
    
        }, "thread1");
    
        thread1.start();
        thread1.join();
        Utils.println("------------------------------------------");
    }

    程序輸出結果為:

    ----------testExpungeStaleEntries----------
    print threadLocalMap after gc
    thread1
    ----threadLocals (ThreadLocalMap), table.length = 16
    --------table[0] -> null
    --------table[1] -> entry key = null, value = threadLocal2-value
    --------table[2] -> null
    --------table[3] -> null
    --------table[4] -> null
    --------table[5] -> null
    --------table[6] -> null
    --------table[7] -> null
    --------table[8] -> null
    --------table[9] -> null
    --------table[10] -> entry key = null, value = threadLocal1-value
    --------table[11] -> null
    --------table[12] -> null
    --------table[13] -> null
    --------table[14] -> null
    --------table[15] -> null
    print threadLocalMap after call a new newThreadLocal1.get
    thread1
    ----threadLocals (ThreadLocalMap), table.length = 16
    --------table[0] -> null
    --------table[1] -> entry key = null, value = threadLocal2-value
    --------table[2] -> null
    --------table[3] -> null
    --------table[4] -> null
    --------table[5] -> null
    --------table[6] -> null
    --------table[7] -> null
    --------table[8] -> entry key = java.lang.ThreadLocal@2b63dc81, value = null
    --------table[9] -> null
    --------table[10] -> null
    --------table[11] -> null
    --------table[12] -> null
    --------table[13] -> null
    --------table[14] -> null
    --------table[15] -> null
    print threadLocalMap after call a new newThreadLocal2.set
    thread1
    ----threadLocals (ThreadLocalMap), table.length = 16
    --------table[0] -> null
    --------table[1] -> null
    --------table[2] -> null
    --------table[3] -> null
    --------table[4] -> null
    --------table[5] -> null
    --------table[6] -> null
    --------table[7] -> null
    --------table[8] -> entry key = java.lang.ThreadLocal@2b63dc81, value = null
    --------table[9] -> null
    --------table[10] -> null
    --------table[11] -> null
    --------table[12] -> null
    --------table[13] -> null
    --------table[14] -> null
    --------table[15] -> entry key = java.lang.ThreadLocal@2e93c547, value = newThreadLocal2-value
    ------------------------------------------

    從結果上來看,

    • gc后table[1]和table[10]的key變成了null
    • new newThreadLocal1.get后,新增了table[8],table[10]被清理了,但table[1]還在(這就是cleanSomeSlots中some的意思)
    • new newThreadLocal2.set后,新增了table[15],table[1]被清理了。

    驗證map的size大於等於table.length的2/3時回收所有無效的entry

        static void testExpungeAllEntries() throws InterruptedException {
            Utils.println("----------testExpungeStaleEntries----------");
            Thread thread1 = new Thread(() -> {
                try {
                    resetNextHashCode();
    
                    int threshold = 16 * 2 / 3;
                    ThreadLocal[] threadLocals = new ThreadLocal[threshold - 1];
                    for(int i = 0; i < threshold - 1; i ++){
                        threadLocals[i] = new ThreadLocal<String>();
                        threadLocals[i].set("threadLocal" + i + "-value");
                    }
    
                    Object threadLocalMap = getThreadLocalMap(Thread.currentThread());
    
                    threadLocals[1] = null;
                    threadLocals[8] = null;
                    //threadLocals[6] = null;
                    //threadLocals[4] = null;
                    //threadLocals[2] = null;
                    forceGC();
    
                    Utils.println("print threadLocalMap after gc");
                    printThreadLocalMap(threadLocalMap);
    
                    ThreadLocal<String> newThreadLocal1 = new ThreadLocal<>();
                    newThreadLocal1.set("newThreadLocal1-value");
                    Utils.println("print threadLocalMap after call a new newThreadLocal1.get");
                    printThreadLocalMap(threadLocalMap);
    
                } catch (NoSuchFieldException | IllegalAccessException e) {
                    e.printStackTrace();
                }
    
            }, "thread1");
    
            thread1.start();
            thread1.join();
            Utils.println("------------------------------------------");
        }
    

    我們先創建了9個threadLocal對象並設置了值,然後去掉了其中2個的強引用(注意這2個可不是隨意挑選的)。
    gc后再添加一個新的threadLocal,最後打印出最新的map。輸出為:

    ----------testExpungeStaleEntries----------
    print threadLocalMap after gc
    thread1
    ----threadLocals (ThreadLocalMap), table.length = 16
    --------table[0] -> null
    --------table[1] -> entry key = null, value = threadLocal1-value
    --------table[2] -> entry key = null, value = threadLocal8-value
    --------table[3] -> null
    --------table[4] -> entry key = java.lang.ThreadLocal@60523912, value = threadLocal6-value
    --------table[5] -> null
    --------table[6] -> entry key = java.lang.ThreadLocal@48fccd7a, value = threadLocal4-value
    --------table[7] -> null
    --------table[8] -> entry key = java.lang.ThreadLocal@188bbe72, value = threadLocal2-value
    --------table[9] -> null
    --------table[10] -> entry key = java.lang.ThreadLocal@19e0ebe8, value = threadLocal0-value
    --------table[11] -> entry key = java.lang.ThreadLocal@688bcb6f, value = threadLocal7-value
    --------table[12] -> null
    --------table[13] -> entry key = java.lang.ThreadLocal@46324c19, value = threadLocal5-value
    --------table[14] -> null
    --------table[15] -> entry key = java.lang.ThreadLocal@38f1283, value = threadLocal3-value
    print threadLocalMap after call a new newThreadLocal1.get
    thread1
    ----threadLocals (ThreadLocalMap), table.length = 32
    --------table[0] -> null
    --------table[1] -> null
    --------table[2] -> null
    --------table[3] -> null
    --------table[4] -> null
    --------table[5] -> null
    --------table[6] -> entry key = java.lang.ThreadLocal@48fccd7a, value = threadLocal4-value
    --------table[7] -> null
    --------table[8] -> null
    --------table[9] -> entry key = java.lang.ThreadLocal@1dae16b1, value = newThreadLocal1-value
    --------table[10] -> entry key = java.lang.ThreadLocal@19e0ebe8, value = threadLocal0-value
    --------table[11] -> null
    --------table[12] -> null
    --------table[13] -> entry key = java.lang.ThreadLocal@46324c19, value = threadLocal5-value
    --------table[14] -> null
    --------table[15] -> null
    --------table[16] -> null
    --------table[17] -> null
    --------table[18] -> null
    --------table[19] -> null
    --------table[20] -> entry key = java.lang.ThreadLocal@60523912, value = threadLocal6-value
    --------table[21] -> null
    --------table[22] -> null
    --------table[23] -> null
    --------table[24] -> entry key = java.lang.ThreadLocal@188bbe72, value = threadLocal2-value
    --------table[25] -> null
    --------table[26] -> null
    --------table[27] -> entry key = java.lang.ThreadLocal@688bcb6f, value = threadLocal7-value
    --------table[28] -> null
    --------table[29] -> null
    --------table[30] -> null
    --------table[31] -> entry key = java.lang.ThreadLocal@38f1283, value = threadLocal3-value
    ------------------------------------------

    從結果上看:

    • gc后table[1]和table[2](即threadLocal1和threadLocal8)的key變成了null
    • 加入新的threadLocal后,table的長度從16變成了32(因為此時的size是8,正好等於10 – 10/4,所以擴容),並且threadLocal1和threadLocal8這兩個entry不見了。

    如果在gc前,我們把threadLocals[1、8、6、4、2]都去掉強引用,加入新threadLocal後會發現1、8、6、4、2被清除了,但沒有擴容,因為此時size是5,小於10-10/4。這個邏輯就不貼測試結果了,你可以取消註釋上面代碼中相關的邏輯試試。

    大部分場景下,ThreadLocal對象的生命周期是和app一致的,弱引用形同虛設

    回到現實中。

    我們用ThreadLocal的目的,無非是在跨方法調用時更方便的線程安全地存儲和使用變量。這就意味着ThreadLocal的生命周期很長,甚至和app是一起存活的,強引用一直在。

    既然強引用一直存在,那麼弱引用就形同虛設了。

    所以在確定不再需要ThreadLocal中的值的情況下,還是老老實實的調用remove方法吧!

    代碼地址

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

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

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

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

  • 【故障公告】數據庫服務器 CPU 近 100% 引發的故障(源於 .NET Core 3.0 的一個 bug),雲計算之路-阿里雲上:數據庫連接數過萬的真相,從阿里雲RDS到微軟.NET Core

    【故障公告】數據庫服務器 CPU 近 100% 引發的故障(源於 .NET Core 3.0 的一個 bug),雲計算之路-阿里雲上:數據庫連接數過萬的真相,從阿里雲RDS到微軟.NET Core

    非常抱歉,這次故障給您帶來麻煩了,請您諒解。

    今天早上 10:54 左右,我們所使用的數據庫服務(阿里雲 RDS 實例 SQL Server 2016 標準版)CPU 突然飆升至 90% 以上,應用日誌中出現大量數據庫查詢超時的錯誤。

    Microsoft.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired.  The timeout period elapsed prior to completion of the operation or the server is not responding.
     ---> System.ComponentModel.Win32Exception (258): Unknown error 258

    我們收到告警通知並確認問題后,在 11:06 啟動了阿里雲 RDS 的主備切換, 11:08 完成切換,數據庫 CPU 恢復正常。但是關鍵時候 docker swarm 總是雪上加霜,在數據庫恢復正常后,部署博客站點的 docker swarm 集群有一個節點出現異常情況,部分請求會出現 50x 錯誤,將這個異常節點退出集群並啟動新的節點后在 11:15 左右才恢復正常。

    通過阿里雲 RDS 控制台的 CloudDBA 發現了 CPU 近 100% 期間執行次數異常多的 SQL 語句。

    SELECT TOP @__p_1 [b].[TagName] AS [Name], [b].[TagID] AS [Id], [b].[UseCount], [b].[BlogId]
    FROM [blog_Tag] [b]
    WHERE [b].[BlogId] = @__blogId_0
        AND @__blogId_0 IS NOT NULL
        AND [b].[UseCount] > ?
    ORDER BY [b].[UseCount] DESC

    上面的 SQL 語句是 EF Core 3.0 生成的,其中加粗的  IS NOT NULL  就是 EF Core 3.0 的一個臭名還沒昭著的 bug —— 生成 SQL 語句時會生成額外的  IS NOT NULL  查詢條件。

    誰也沒想到(連微軟自己也沒想到)這個看似無傷大雅的多此一舉卻存在致命隱患 —— 在某些情況下會讓整個數據庫服務器 CPU 持續 100% (或者近 100%)。一開始遇到這個問題時,我們也沒想到,還因此錯怪了阿里雲(),後來在阿里雲數據庫專家分析了我們遇到的問題后才發現原來罪魁禍首是 EF Core 生成的多餘的 “IS NOT NULL” ,它會在某些情況下會造成 SQL Server 緩存了性能極其低下(很耗CPU)的執行計劃,然後後續的查詢都走這個執行計劃,CPU 就會居高不下。這個錯誤的執行計劃有雙重殺傷力,一邊巨耗數據庫 CPU ,一邊造成對應的查詢無法正常完成從而查詢結果不能被緩存到 memcached ,於是針對這個執行計劃的查詢就越多,雪崩效應就發生了。唯一的解決方法就是清除這個錯誤的執行計劃緩存,主備切換或者重啟服務器只是清除執行計劃緩存的一種簡單粗暴的方法。

    在我們開始遇到這個問題,就已經有人在 github 上了這個問題:

    Yeah this needs to be fixed asap. We just deployed code that uses 3.0 and had to immediately revert to 2.2 because simple queries blew up our SQL Azure CPU usage. Went from under 50% to 100% and stayed there until we rolled back.

    但當時沒有引起微軟的足夠重視,在我們知道錯怪了阿里雲實際是微軟的問題之後,我們向微軟 .NET 團隊反饋了這個問題,這次得到了微軟的重視,很快就修復了,但是是通過 .NET Core 3.0 Preview 版發布的,我們在非生產環境下驗證了  IS NOT NULL 的確修復了,由於是 Preview 版,再加上 .NET Core 3.1 正式版年底前會發布,所以我們沒有在生產環境中更新這個修復,只是將上次出現問題的複雜 SQL 語句改為用 Dapper 調用存儲過程。後來阿里雲數據庫專家進一步對我們的數據庫進行分析,連平時數據庫 CPU 的毛刺(偶爾跑高的波動)都與  IS NOT NULL  有關。

    這就是這次故障的背景,在我們等待 .NET Core 3.1 正式版修復這個 bug 的過程中又被坑了一次,與上次不同的是這次出現問題的 SQL 語句非常簡單,而且只有一個 “IS NOT NULL” ,由此可見這個坑的殺傷力。

    這個坑足以載入 .NET Core 的史冊,另一個讓我們記憶猶新的那次也讓我們錯怪阿里雲的 .NET Core 坑是正式版的 .NET Core 中 SqlClient 竟然漏寫了 Dispose ,詳見 。

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

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

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

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

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