實力媲美榮威RX5 又一台爆款SUV準備圈錢了

相信很多年輕人第一眼看到名爵ZS,都會不由自主地被它全新的設計感所吸引。全新樣式的盾形進氣格珊,搭配別具一格的倫敦眼前大燈,視覺層面上非常凌厲驚艷。再加上優雅動感的車身設計,以及層次感豐富的尾部設計,讓名爵ZS渾身上下充滿國際範氣質。

如今SUV市場早已是一片紅海,合資自主你爭我趕,互不讓步。這其中做到逆襲上位、脫穎而出,上汽榮威RX5絕對稱得上是業內的一個新標桿。從上市之初就獲得極大關注度,再到上市第三個月銷量強勢突破2萬輛,順利闖入熱銷SUV車型前十位當中。

榮威RX5之所以能快速成為國人眼中的“新網紅”,一方面是得益於榮威RX5擁有國際範的設計外觀,另一方面是來自於“全球首款量產的互聯網汽車”的創新概念。

而在此次廣州車展上,除了上汽榮威RX5備受消費者關注之外,看到上汽旗下的MG品牌展館也是人氣爆棚。原來,藉著廣州車展的熱鬧,上汽集團也順勢推出了一款全新互聯網SUV-名爵ZS。

事實上,自打名爵ZS的首張設計圖曝光以來,關於名爵ZS的議論話題就沒斷過,單單在微博的“我的第一台互聯網汽車”話題閱讀量就超過了1.2億,更有眾多汽車圈、時尚圈以及互聯網的知名大咖們紛紛轉發討論名爵ZS。隨後在名爵ZS在廣州車展發布當天,一時間話題熱度飆升,在新浪汽車頻道,名爵ZS的話題閱讀量累計達到3.3億。無論是線上還是線下,名爵ZS可謂是賺飽了眼球,毫不誇張地說,名爵ZS儼然就是未來網紅的節奏呀!

於是,懷著無比激動好奇的心情走進去一瞧,然而,這款名爵ZS卻大大出乎的預料,整個外觀形象煥然一新,尤其是那前臉像足了“小捷豹”的姿態。但和榮威RX5的“律動設計”語言不同,名爵ZS採用的是全新的“感性力”設計理念,同時有別於以往的名爵車型,外形上更接近當下年輕人的審美觀念。相信很多年輕人第一眼看到名爵ZS,都會不由自主地被它全新的設計感所吸引。

全新樣式的盾形進氣格珊,搭配別具一格的倫敦眼前大燈,視覺層面上非常凌厲驚艷。再加上優雅動感的車身設計,以及層次感豐富的尾部設計,讓名爵ZS渾身上下充滿國際範氣質。況且,再說起顏值來,後來之秀的名爵ZS並不在大哥榮威RX5之下,這也難怪有網友稱名爵ZS為汽車界的彭於晏,既有高顏值,又有好身材!

憑藉於互聯網汽車的定位,榮威RX5成功打破自主品牌定價天花板。而作為上汽MG品牌旗下的首款互聯SUV,名爵ZS自然沒讓年輕人失望。名爵ZS將繼續搭載最先進的阿里YUN OS車載系統,而且該車載系統已經在榮威RX5身上得到廣泛應用,好評如潮。這也意味著,名爵ZS一樣能夠為追求時尚便捷的年輕人,提供強大的互聯網汽車服務,享受智能導航、遠程控制、人車互聯等輕鬆舒適的汽車生活。足以可見名爵ZS在研究年輕人的消費心理也是費了不少功夫,要不然,怎麼會說年輕人的第一台車就是它呢?

名爵ZS未上先熱的“反常”現象,在看來,一定程度上反映了名爵zs的產品實力是深受年輕人認可的。換句話說,名爵ZS將很有很大可能成為榮威RX5之後,又一位爆款SUV選手。既然如此,那還等什麼?馬上到年底了,趕緊叫老闆加工資,一起來期待明年名爵ZS的驚喜上市。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

防疫比環保重要?新罕布什爾州暫時重推塑膠袋

摘錄自2020年03月22日中央通訊社美國報導

美國新罕布什爾州長蘇努努今天(22日)敦促州內民眾,將可重複使用的環保袋留在家中暫時不用。他說,為對抗武漢肺炎,在此過渡期,購物宜用店家提供的新塑膠袋或紙袋。

此舉主要是愈發擔心賣場員工面對店內人潮擁擠,加上新型冠狀病毒疾病(COVID-19,武漢肺炎)具高度傳染性,且可能附著於各種物件表面,增加感染風險。州長蘇努努(Christopher Sununu)今天推文說:「由於確認(武漢肺炎病毒)社區傳染,顧及賣場裝袋人員、雜貨商和顧客潛在風險,購物者將環保袋暫留家中,這很重要。」

本週在新英格蘭醫學期刊(New England Journal of Medicine)發表的研究顯示,武漢肺炎病毒可在空氣中存活數小時,而在不同的物件表面甚至可存活數天之久。

公害污染
污染治理
國際新聞
美國
環保袋
武漢肺炎
疫情下的食衣住行
廢棄物

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

※教你寫出一流的銷售文案?

※超省錢租車方案

※回頭車貨運收費標準

疫情衝擊泰國觀光業 大象賺不到生活費恐餓死

摘錄自2020年3月31日中央社報導

隨著武漢肺炎大流行讓觀光客銳減,倡議人士警告,在泰國旅遊業工作的許多飢餓並遭長時間鍊住的大象,可能會因沒有收入而餓死、被賣給動物園,或轉入非法伐木業。

在疫情爆發前,在泰國旅遊業工作的約2000頭大象生活已不容易,時常傳出虐待情事,好馴化牠們提供乘載服務或在動物秀上表演雜耍賺錢。但因全球旅遊業停擺,大象現在甚至賺不到一天必須的300公斤食物錢。

大象生態營與保育人士警告,再不獲得緊急援助,象群即將面臨飢餓及新的剝削威脅。泰國大象聯盟協會(Thai Elephant Alliance Association)主席提拉帕(Theerapat Trungprakan)表示,隨著病毒重挫泰國觀光業,約2000頭大象現在失業了。

國際新聞
泰國
大象
觀光業
動物與大環境變遷
武漢肺炎
展示動物
動物福利

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

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

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

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

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

※別再煩惱如何寫文案,掌握八大原則!

網頁設計最專業,超強功能平台可客製化

※回頭車貨運收費標準

荷蘭研究:城市污水測得病毒 或可扮演疫情預警

摘錄自2020年3月31日中央社報導

荷蘭科學家可以在一個城市的2019冠狀病毒疾病(COVID-19,武漢肺炎)病例通報前,在都市污水中發現引發這種疾病的冠狀病毒,顯示這或許有可能成為新疫情早期預警系統。

所謂的SARS-CoV-2冠狀病毒通常會從感染者的糞便中排出。位於荷蘭中部城市尼沃海恩(Nieuwegein)的KWR水資源研究所(KWR Water Research Institute)的首席微生物學家麥德瑪(Gertjan Medema)和他的同僚今(30日)表示,儘管污水不太可能成為重要的傳播途徑。但病原體在社區裡不斷增加的循環會增加它流入下水道系統的數量。

他們3月5日在阿默斯福特(Amersfoort)一座污水處理廠發現武漢肺炎的遺傳物質,當時這個位於阿姆斯特丹東南方約50公里處的城市還沒傳出任何病例。荷蘭2月27日出現境內首起2019冠狀病毒疾病病例,幾天後南部的醫療人員感染生病,顯示疫情已在社區蔓延。

他們表示,即使在武漢肺炎盛行率還很低時,就可以在污水中偵測到引發這種疾病的冠狀病毒,顯示這可以當成監控這種病毒在人口中循環的高敏感度工具。

公害污染
污染治理
國際新聞
荷蘭
疫情
水污染

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

【其他文章推薦】

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

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

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

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

※教你寫出一流的銷售文案?

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

分解塑膠救星?科學家在垃圾場中發現吃聚氨酯的微生物

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

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

【其他文章推薦】

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

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※想知道最厲害的網頁設計公司"嚨底家"!

※別再煩惱如何寫文案,掌握八大原則!

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

台中搬家公司費用怎麼算?

Java多線程之深入解析ThreadLocal和ThreadLocalMap

ThreadLocal概述

ThreadLocal是線程變量,ThreadLocal中填充的變量屬於當前線程,該變量對其他線程而言是隔離的。ThreadLocal為變量在每個線程中都創建了一個副本,那麼每個線程可以訪問自己內部的副本變量。

它具有3個特性:

  1. 線程併發:在多線程併發場景下使用。
  2. 傳遞數據:可以通過ThreadLocal在同一線程,不同組件中傳遞公共變量。
  3. 線程隔離:每個線程變量都是獨立的,不會相互影響。

在不使用ThreadLocal的情況下,變量不隔離,得到的結果具有隨機性。

public class Demo {
    private String variable;

    public String getVariable() {
        return variable;
    }

    public void setVariable(String variable) {
        this.variable = variable;
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                demo.setVariable(Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName()+" "+demo.getVariable());
            }).start();
        }
    }
}

輸出結果:

Thread-2 Thread-2
Thread-4 Thread-4
Thread-1 Thread-2
Thread-0 Thread-2
Thread-3 Thread-3

View Code

在不使用ThreadLocal的情況下,變量隔離,每個線程有自己專屬的本地變量variable,線程綁定了自己的variable,只對自己綁定的變量進行讀寫操作。

public class Demo {
    private ThreadLocal<String> variable = new ThreadLocal<>();

    public String getVariable() {
        return variable.get();
    }

    public void setVariable(String variable) {
        this.variable.set(variable);
    }

    public static void main(String[] args) {
        Demo demo = new Demo();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                demo.setVariable(Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName()+" "+demo.getVariable());
            }).start();
        }
    }
}

輸出結果:

Thread-0 Thread-0
Thread-1 Thread-1
Thread-2 Thread-2
Thread-3 Thread-3
Thread-4 Thread-4

View Code

synchronized和ThreadLocal的比較

上述需求,通過synchronized加鎖同樣也能實現。但是加鎖對性能和併發性有一定的影響,線程訪問變量只能排隊等候依次操作。TreadLocal不加鎖,多個線程可以併發對變量進行操作。

public class Demo {
    private String variable;
    public String getVariable() {
        return variable;
    }

    public void setVariable(String variable) {
        this.variable = variable;
    }

    public static void main(String[] args) {
        Demo demo = new Demo1();
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                synchronized (Demo.class){
                    demo.setVariable(Thread.currentThread().getName());
                    System.out.println(Thread.currentThread().getName()+" "+demo.getVariable());
                }
            }).start();
        }
    }
}

ThreadLocal和synchronized都是用於處理多線程併發訪問資源的問題。ThreadLocal是以空間換時間的思路,每個線程都擁有一份變量的拷貝,從而實現變量隔離,互相不干擾。關注的重點是線程之間數據的相互隔離關係。synchronized是以時間換空間的思路,只提供一個變量,線程只能通過排隊訪問。關注的是線程之間訪問資源的同步性。ThreadLocal可以帶來更好的併發性,在多線程、高併發的環境中更為合適一些。

ThreadLocal使用場景

轉賬事務的例子

JDBC對於事務原子性的控制可以通過setAutoCommit(false)設置為事務手動提交,成功后commit,失敗后rollback。在多線程的場景下,在service層開啟事務時用的connection和在dao層訪問數據庫的connection應該要保持一致,所以併發時,線程只能隔離操作自已的connection。

解決方案1:service層的connection對象作為參數傳遞給dao層使用,事務操作放在同步代碼塊中。

存在問題:傳參提高了代碼的耦合程度,加鎖降低了程序的性能。

解決方案2:當需要獲取connection對象的時候,通過ThreadLocal對象的get方法直接獲取當前線程綁定的連接對象使用,如果連接對象是空的,則去連接池獲取連接,並通過ThreadLocal對象的set方法綁定到當前線程。使用完之後調用ThreadLocal對象的remove方法解綁連接對象。

ThreadLocal的優勢:

  1. 可以方便地傳遞數據:保存每個線程綁定的數據,需要的時候可以直接獲取,避免了傳參帶來的耦合。
  2. 可以保持線程間隔離:數據的隔離在併發的情況下也能保持一致性,避免了同步的性能損失。

ThreadLocal的原理

每個ThreadLocal維護一個ThreadLocalMap,Map的Key是ThreadLocal實例本身,value是要存儲的值。

每個線程內部都有一個ThreadLocalMap,Map裏面存放的是ThreadLocal對象和線程的變量副本。Thread內部的Map通過ThreadLocal對象來維護,向map獲取和設置變量副本的值。不同的線程,每次獲取變量值時,只能獲取自己對象的副本的值。實現了線程之間的數據隔離。

JDK1.8的設計相比於之前的設計(通過ThreadMap維護了多個線程和線程變量的對應關係,key是Thread對象,value是線程變量)的好處在於,每個Map存儲的Entry數量變少了,線程越多鍵值對越多。現在的鍵值對的數量是由ThreadLocal的數量決定的,一般情況下ThreadLocal的數量少於線程的數量,而且並不是每個線程都需要創建ThreadLocal變量。當Thread銷毀時,ThreadLocal也會隨之銷毀,減少了內存的使用,之前的方案中線程銷毀后,ThreadLocalMap仍然存在。

ThreadLocal源碼解析

set方法

首先獲取線程,然後獲取線程的Map。如果Map不為空則將當前ThreadLocal的引用作為key設置到Map中。如果Map為空,則創建一個Map並設置初始值。

get方法

首先獲取當前線程,然後獲取Map。如果Map不為空,則Map根據ThreadLocal的引用來獲取Entry,如果Entry不為空,則獲取到value值,返回。如果Map為空或者Entry為空,則初始化並獲取初始值value,然後用ThreadLocal引用和value作為key和value創建一個新的Map。

 

remove方法

刪除當前線程中保存的ThreadLocal對應的實體entry。

initialValue方法

該方法的第一次調用發生在當線程通過get方法訪問線程的ThreadLocal值時。除非線程先調用了set方法,在這種情況下,initialValue才不會被這個線程調用。每個線程最多調用依次這個方法。

該方法只返回一個null,如果想要線程變量有初始值需要通過子類繼承ThreadLocal的方式去重寫此方法,通常可以通過匿名內部類的方式實現。這個方法是protected修飾的,是為了讓子類覆蓋而設計的。

ThreadLocalMap源碼分析

ThreadLocalMap是ThreadLocal的靜態內部類,沒有實現Map接口,獨立實現了Map的功能,內部的Entry也是獨立實現的。

與HashMap類似,初始容量默認是16,初始容量必須是2的整數冪。通過Entry類的數據table存放數據。size是存放的數量,threshold是擴容閾值。

 Entry繼承自WeakReference,key是弱引用,其目的是將ThreadLocal對象的生命周期和線程生命周期解綁。

弱引用和內存泄漏

內存溢出:沒有足夠的內存供申請者提供

內存泄漏:程序中已動態分配的堆內存由於某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等驗證后溝。內存泄漏的堆積會導致內存溢出。

弱引用:垃圾回收器一旦發現了弱引用的對象,不管內存是否足夠,都會回收它的內存。

內存泄漏的根源是ThreadLocalMap和Thread的生命周期是一樣長的。

如果在ThreadLocalMap的key使用強引用還是無法完全避免內存泄漏,ThreadLocal使用完后,ThreadLocal Reference被回收,但是Map的Entry強引用了ThreadLocal,ThreadLocal就無法被回收,因為強引用鏈的存在,Entry無法被回收,最後會內存泄漏。

在實際情況中,ThreadLocalMap中使用的key為ThreadLocal的弱引用,value是強引用。如果ThreadLocal沒有被外部強引用的話,在垃圾回收的時候,key會被清理,value不會。這樣ThreadLocalMap就出現了為null的Entry。如果不做任何措施,value永遠不會被GC回收,就會產生內存泄漏。

ThreadLocalMap中考慮到這個情況,在set、get、remove操作后,會清理掉key為null的記錄(將value也置為null)。使用完ThreadLocal后最後手動調用remove方法(刪除Entry)。

也就是說,使用完ThreadLocal后,線程仍然運行,如果忘記調用remove方法,弱引用比強引用可以多一層保障,弱引用的ThreadLocal會被回收,對應的value會在下一次ThreadLocalMap調用get、set、remove方法的時候被清除,從而避免了內存泄漏。

Hash衝突的解決

ThreadLocalMap的構造方法

構造函數創建一個長隊為16的Entry數組,然後計算firstKey的索引,存儲到table中,設置size和threshold。

firstKey.threadLocalHashCode & (INITIAL_CAPACITY-1)用來計算索引,nextHashCode是Atomicinteger類型的,Atomicinteger類是提供原子操作的Integer類,通過線程安全的方式來加減,適合高併發使用。

每次在當前值上加上一個HASH_INCREMENT值,這個值和斐波拉契數列有關,主要目的是為了讓哈希碼可以均勻的分佈在2的n次方的數組裡,從而盡量的避免衝突。

當size為2的冪次的時候,hashCode & (size – 1)相當於取模運算hashCode % size,位運算比取模更高效一些。為了使用這種取模運算, 所有size必須是2的冪次。這樣一來,在保證索引不越界的情況下,減少衝突的次數。

ThreadLocalMap的set方法

ThreadLocalMao使用了線性探測法來解決衝突。線性探測法探測下一個地址,找到空的地址則插入,若整個空間都沒有空餘地址,則產生溢出。例如:長度為8的數組中,當前key的hash值是6,6的位置已經被佔用了,則hash值加一,尋找7的位置,7的位置也被佔用了,回到0的位置。直到可以插入為止,可以將這個數組看成一個環形數組

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

【其他文章推薦】

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

※別再煩惱如何寫文案,掌握八大原則!

※教你寫出一流的銷售文案?

※超省錢租車方案

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※回頭車貨運收費標準

Java 多線程基礎(一)基本概念

Java 多線程基礎(一)基本概念

一、併發與并行

1、併發:指兩個或多個事件在同一個時間段發生。

2、并行:指兩個或多個事件在同一時刻發生(同時發生)。

在操作系統中,安裝了多個程序,併發指的是在一段時間內宏觀上有多個程序同時運行,這在單 CPU 系統中,每一時刻只能有一道程序執行,即微觀上這些程序是分時的交替運行,只不過是給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的。

而在多個 CPU 系統中,則這些可以併發執行的程序便可以分配到多個處理器上(CPU),實現多任務并行執行,即利用每個處理器來處理一個可以併發執行的程序,這樣多個程序便可以同時執行。目前電腦市場上說的多核 CPU,便是多核處理器,核 越多,并行處理的程序越多,能大大的提高電腦運行的效率。

3、注意點

單核處理器的計算機肯定是不能并行的處理多個任務的,只能是多個任務在單個CPU上併發運行。同理,線程也是一樣的,從宏觀角度上理解線程是并行運行的,但是從微觀角度上分析卻是串行運行的,即一個線程一個線程的去運行,當系統只有一個CPU時,線程會以某種順序執行多個線程,我們把這種情況稱之為線程調度。

二、線程與進程

1、進程:是指一個內存中運行的應用程序,每個進程都有一個獨立的內存空間,一個應用程序可以同時運行多個進程;進程也是程序的一次執行過程,是系統運行程序的基本單位;系統運行一個程序即是一個進程從創建、運行到消亡的過程。

2、線程:線程是進程中的一個執行單元,負責當前進程中程序的執行,一個進程中至少有一個線程。一個進程中是可以有多個線程的,這個應用程序也可以稱之為多線程程序。

操作系統調度的最小任務單位是線程。常用的Windows、Linux等操作系統都採用搶佔式多任務,如何調度線程完全由操作系統決定,程序自己不能決定什麼時候執行,以及執行多長時間。

(一)、線程的產生

每個進程都有自己的地址空間,即進程空間,在網絡或多用戶換機下,一個服務器通常需要接收大量不確定數量用戶的併發請求,為每一個請求都創建一個進程顯然行不通(系統開銷大響應用戶請求效率低),因此操作系統中線程概念被引進。線程的改變只代表CPU的執行過程的改變,而沒有發生進程所擁有的資源的變化。

  • 線程的執行過程是線性的,儘管中間會發生中斷或者暫停,但是進程所擁有的資源只為改線狀執行過程服務,一旦發生線程切換,這些資源需要被保護起來。
  • 進程分為單線程進程和多線程進程,單線程進程宏觀來看也是線性執行過程,微觀上只有單一的執行過程。多線程進程宏觀是線性的,微觀上多個執行操作。

(二)、進程與線程的區別

  • 地址空間。同一線程共享該進程的地址空間;進程之間是獨立的地址空間,
  • 用於資源。同一進程內的線程共享本進程的資源如內存、I/O、cpu等,但是進程之間的資源是獨立的。
  • 執行過程。每個獨立的進程程有一個程序運行的入口、順序執行序列和程序入口。但是線程不能獨立執行,必須依存在應用程序中,由應用程序提供多個線程執行控制。

(三)、優缺點

線程執行開銷小,但是不利於資源的管理和保護。線程適合在SMP機器(雙CPU系統)上運行。進程執行開銷大,但是能夠很好的進行資源管理和保護。進程可以跨機器前移。

(四)、使用場景

對資源的管理和保護要求高,不限制開銷和效率時,使用多進程。

要求效率高,頻繁切換時,資源的保護管理要求不是很高時,使用多線程。

三、線程的狀態

線程共包括以下5種狀態,也叫生命周期。
1. 新建狀態(New)         :線程對象被創建后,就進入了新建狀態。例如,Thread thread = new Thread()。
2. 就緒狀態(Runnable):也被稱為“可執行狀態”。線程對象被創建后,其它線程調用了該對象的start()方法,從而來啟動該線程。例如,thread.start()。處於就緒狀態的線程,隨時可能被CPU調度執行。
3. 運行狀態(Running)   :線程獲取CPU權限進行執行。需要注意的是,線程只能從就緒狀態進入到運行狀態。
4. 阻塞狀態(Blocked)    :阻塞狀態是線程因為某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,才有機會轉到運行狀態。阻塞的情況分三種:
    ① 等待阻塞 — 通過調用線程的wait()方法,讓線程等待某工作的完成。
    ② 同步阻塞 — 線程在獲取 synchronized 同步鎖失敗(因為鎖被其它線程所佔用),它會進入同步阻塞狀態。
    ③ 其他阻塞 — 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5. 死亡狀態(Dead)         :線程執行完了或者因異常退出了run()方法,該線程結束生命周期。

四、多線程的原理

五、進程、線程實現多任務模式

(一)、多進程模式(一個進程只有一個線程)

(二)、多線程模式(一個進程有多個線程)

(三)、多進程 + 多線程模式(複雜度最高)

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

【其他文章推薦】

※別再煩惱如何寫文案,掌握八大原則!

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

※超省錢租車方案

※教你寫出一流的銷售文案?

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

【Spring註解開發】組件註冊-使用@Configuration和@Bean給容器中註冊組件

寫在前面

在之前的Spring版本中,我們只能通過寫XML配置文件來定義我們的Bean,XML配置不僅繁瑣,而且很容易出錯,稍有不慎就會導致編寫的應用程序各種報錯,排查半天,發現是XML文件配置不對!另外,每個項目編寫大量的XML文件來配置Spring,也大大增加了項目維護的複雜度,往往很多個項目的Spring XML文件的配置大部分是相同的,只有很少量的配置不同,這也造成了配置文件上的冗餘。

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

Spring IOC和DI

在Spring容器的底層,最重要的功能就是IOC和DI,也就是控制反轉和依賴注入。

IOC:控制反轉,將類的對象的創建交給Spring類管理創建。
DI:依賴注入,將類裏面的屬性在創建類的過程中給屬性賦值。
DI和IOC的關係:DI不能單獨存在,DI需要在IOC的基礎上來完成。

在Spring內部,所有的組件都會放到IOC容器中,組件之間的關係通過IOC容器來自動裝配,也就是我們所說的依賴注入。接下來,我們就使用註解的方式來完成容器組件的註冊、管理及依賴、注入等功能。

在介紹使用註解完成容器組件的註冊、管理及依賴、注入等功能之前,我們先來看看使用XML文件是如何注入Bean的。

通過XML文件注入JavaBean

首先,我們在工程的io.mykit.spring.bean包下創建Person類,作為測試的JavaBean,代碼如下所示。

package io.mykit.spring.bean;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import java.io.Serializable;

/**
 * @author binghe
 * @version 1.0.0
 * @description 測試實體類
 */
@Data
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Person implements Serializable {
    private static final long serialVersionUID = 7387479910468805194L;
    private String name;
    private Integer age;
}

接下來,我們在工程的resources目錄下創建Spring的配置文件beans.xml,通過beans.xml文件將Person類注入到Spring的IOC容器中,配置如下所示。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id = "person" class="io.mykit.spring.bean.Person">
        <property name="name" value="binghe"></property>
        <property name="age" value="18"></property>
    </bean>
</beans>

到此,我們使用XML方式注入JavaBean就配置完成了。接下來,我們創建一個SpringBeanTest類來進行測試,這裏,我使用的是Junit進行測試,測試方法如下所示。

@Test
public void testXmlConfig(){
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    Person person = (Person) context.getBean("person");
    System.out.println(person);
}

運行testXmlConfig()方法,輸出的結果信息如下。

Person(name=binghe, age=18)

從輸出結果中,我們可以看出,Person類通過beans.xml文件的配置,已經注入到Spring的IOC容器中了。

通過註解注入JavaBean

通過XML文件,我們可以將JavaBean注入到Spring的IOC容器中。那使用註解又該如何實現呢?別急,其實使用註解比使用XML文件要簡單的多,我們在項目的io.mykit.spring.plugins.register.config包下創建PersonConfig類,並在PersonConfig類上添加@Configuration註解來標註PersonConfig類是一個Spring的配置類,通過@Bean註解將Person類注入到Spring的IOC容器中。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {
     @Bean
    public Person person(){
        return new Person("binghe001", 18);
    }
}

沒錯,通過PersonConfig類我們就能夠將Person類注入到Spring的IOC容器中,是不是很Nice!!主要我們在類上加上@Configuration註解,並在方法上加上@Bean註解,就能夠將方法中創建的JavaBean注入到Spring的IOC容器中。

接下來,我們在SpringBeanTest類中創建一個testAnnotationConfig()方法來測試通過註解注入的Person類,如下所示。

@Test
public void testAnnotationConfig(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    Person person = context.getBean(Person.class);
    System.out.println(person);
}

運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)

可以看出,通過註解將Person類注入到了Spring的IOC容器中。

到這裏,我們已經明確,通過XML文件和註解兩種方式都可以將JavaBean注入到Spring的IOC容器中。那麼,使用註解將JavaBean注入到IOC容器中時,使用的bean的名稱是什麼呢? 我們可以在testAnnotationConfig()方法中添加如下代碼來獲取Person類型下的註解名稱。

//按照類型找到對應的bean名稱數組
String[] names = context.getBeanNamesForType(Person.class);
Arrays.stream(names).forEach(System.out::println);

完整的testAnnotationConfig()方法的代碼如下所示。

@Test
public void testAnnotationConfig(){
    ApplicationContext context = new AnnotationConfigApplicationContext(PersonConfig.class);
    Person person = context.getBean(Person.class);
    System.out.println(person);

    //按照類型找到對應的bean名稱數組
    String[] names = context.getBeanNamesForType(Person.class);
    Arrays.stream(names).forEach(System.out::println);
}

運行testAnnotationConfig()方法輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person

那這裏的person是啥?我們修改下PersonConfig類中的person()方法,將person()方法修改成person01()方法,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {

    @Bean
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

此時,我們再次運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person01

看到這裏,大家應該有種豁然開朗的感覺了,沒錯!!使用註解注入Javabean時,bean在IOC中的名稱就是使用@Bean註解標註的方法名稱。我們可不可以為bean單獨指定名稱呢?那必須可以啊!只要在@Bean註解中明確指定名稱就可以了。比如下面的PersonConfig類的代碼,我們將person01()方法上的@Bean註解修改成@Bean(“person”)註解,如下所示。

package io.mykit.spring.plugins.register.config;

import io.mykit.spring.bean.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author binghe
 * @version 1.0.0
 * @description 以註解的形式來配置Person
 */
@Configuration
public class PersonConfig {

    @Bean("person")
    public Person person01(){
        return new Person("binghe001", 18);
    }
}

此時,我們再次運行testAnnotationConfig()方法,輸出的結果信息如下所示。

Person(name=binghe001, age=18)
person

可以看到,此時,輸出的JavaBean的名稱為person。

結論:我們在使用註解方式向Spring的IOC容器中注入JavaBean時,如果沒有在@Bean註解中明確指定bean的名稱,就使用當前方法的名稱來作為bean的名稱;如果在@Bean註解中明確指定了bean的名稱,則使用@Bean註解中指定的名稱來作為bean的名稱。

好了,咱們今天就聊到這兒吧!別忘了給個在看和轉發,讓更多的人看到,一起學習一起進步!!

項目工程源碼已經提交到GitHub:https://github.com/sunshinelyz/spring-annotation

寫在最後

如果覺得文章對你有點幫助,請微信搜索並關注「 冰河技術 」微信公眾號,跟冰河學習Spring註解驅動開發。公眾號回復“spring註解”關鍵字,領取Spring註解驅動開發核心知識圖,讓Spring註解驅動開發不再迷茫。

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

【其他文章推薦】

※教你寫出一流的銷售文案?

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

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※推薦台中搬家公司優質服務,可到府估價

容器技術之Dockerfile(三)

  前面我們聊到了dockerfile的 FROM、COPY 、ADD、LABEL、MAINTAINER、ENV、ARG、WORKDIR、VOLUME、EXPOSE、RUN、CMD、ENTRYPOINT指令的使用和說明,回顧請參考https://www.cnblogs.com/qiuhom-1874/tag/Dockerfile/;今天我們來聊聊剩下的dockerfile指令的使用和說明;

  1、USER:該指令用於指定運行image時的或運行dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序時的用戶名或UID;默認情況下,container的運行身份為root用戶;語法格式 USER <UID>|<UserName>; 需要注意的是,<UID>可以為任意数字,但實踐中其必須為/etc/passwd中某用戶的有效UID,否則,docker run命令將運行失敗;

  示例: 

[root@node1 test]# cat Dockerfile 
FROM centos:7

LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

RUN useradd nginx

USER nginx

CMD ["sleep","3000"]

[root@node1 test]# 

  提示:以上dockerfile表示在鏡像運行成容器時,以nginx用戶運行 sleep 3000

  驗證:編譯成鏡像,啟動為容器,然後進入到容器里看看sleep 3000 是否是nginx用戶在運行?

[root@node1 test]# docker build . -t test:v1
Sending build context to Docker daemon  1.051MB
Step 1/7 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/7 : LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"
 ---> Running in 0f503dae4448
Removing intermediate container 0f503dae4448
 ---> d31363b96f38
Step 3/7 : LABEL version="1.0"
 ---> Running in 8dad05999903
Removing intermediate container 8dad05999903
 ---> 2281f36d7c3c
Step 4/7 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Running in d2be9ed44aee
Removing intermediate container d2be9ed44aee
 ---> 8de872e222fb
Step 5/7 : RUN useradd nginx
 ---> Running in 37bda6ba6b60
Removing intermediate container 37bda6ba6b60
 ---> dc681f95f5ca
Step 6/7 : USER nginx
 ---> Running in 97d2357826f9
Removing intermediate container 97d2357826f9
 ---> ed277ac0c482
Step 7/7 : CMD ["sleep","3000"]
 ---> Running in 0ea578fa10bc
Removing intermediate container 0ea578fa10bc
 ---> 461f6ceabc88
Successfully built 461f6ceabc88
Successfully tagged test:v1
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                v1                  461f6ceabc88        3 seconds ago       204MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name t1 --rm -d test:v1
37e46346d6ca0ab05b67f5350d4c2a7b6b86b8d34c8d1622d78ef70b7d3dff86
[root@node1 test]# docker ps 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
37e46346d6ca        test:v1             "sleep 3000"        3 seconds ago       Up 2 seconds                            t1
[root@node1 test]# docker exec -it t1 /bin/bash
[nginx@37e46346d6ca /]$ ps aux
USER        PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
nginx         1  0.1  0.0   4364   352 ?        Ss   10:02   0:00 sleep 3000
nginx         6  0.4  0.0  11828  1808 pts/0    Ss   10:02   0:00 /bin/bash
nginx        23  0.0  0.0  51756  1708 pts/0    R+   10:02   0:00 ps aux
[nginx@37e46346d6ca /]$ exit
exit
[root@node1 test]#

  提示:可以看到基於上面的dockerfile構建的鏡像運行為容器,裏面默認跑的進程就是我們在dockerfile中指定用戶運行的進程;使用USER指定用戶運行容器里的進程,需要注意該用戶要對運行進程所需資源的所有權限;否則容器運行不起來;

  2、HEALTHCHECK:該指令用於定義如何對容器做健康狀態檢測;運行為容器后,容器里的進程不掛掉,當然容器也就不會掛掉,但是存在一種情況,容器沒有掛掉,容器里的進程無法正常提供服務了,這個時候我們就需要通過一定的手段,第一時間知道容器里的進程是否健康(是否能夠正常提供服務);healthcheck指令就是用來定義如果去檢測容器內部進程是否健康;語法格式HEALTHCHECK [OPTIONS] CMD command;其中CMD是固定格式,而後面的command是對容器里的進程做健康狀態檢查的命令;而options是用來指定對容器做健康狀態檢查的周期時間相關信息;–interval=DURATION (default: 30s),該選項用於指定對容器做健康狀態檢查的頻率,默認是30s一次;–timeout=DURATION (default: 30s),該選項用於指定對容器內部的進程做健康狀態檢查的超時時長,默認是30秒;–start-period=DURATION (default: 0s)指定對容器中的進程做健康狀態檢查延遲時間,默認0表示不延遲;這裏補充一點,之所以要延遲多少秒做健康狀態檢查是因為,docker運行為容器以後,會立刻把該容器的狀態標記為running狀態,而對於有些初始化比較慢的容器,如果馬上對它做健康狀態檢查,可能是不健康的狀態,這樣一來我們對了解容器是否健康就不是很準確了;如果配合某些工具,很可能存在檢測到容器不健康就把該容器刪除,然後重新創建,以此重複;這樣就會導致我們的容器啟動不起來; –retries=N (default: 3)表示指定對容器做健康狀態檢查的重試次數,默認是3次;也就是說檢查到容器不健康的前提或健康的前提,它都會檢查3次,如果3次檢查都是失敗狀態那麼就標記該容器不健康;而對於我們指定的命令來講,命令的返回值就決定了容器是否健康,通常命令返回值為0表示我們執行的命令正常退出,也就意味着容器是健康狀態;命令返回值為1表示容器不健康;返回值為2我們通常都是保留不使用;HEALTHCHECK NONE就表示不對容器做健康狀態檢查;

  示例:

[root@node1 test]# cat Dockerfile 
FROM centos:7

LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"

LABEL version="1.0"

LABEL description="this is test file \ that label-values can span multiple lines."

RUN yum install -y httpd 

ADD ok.html /var/www/html/

CMD ["/usr/sbin/httpd","-DFOREGROUND"]

HEALTHCHECK --interval=5s --timeout=5s --start-period=5s --retries=2 \
        CMD curl -f http://localhost/ok.html || exit 1

[root@node1 test]# 

  提示:以上HEALTHCHECK指令表示每5秒檢查一次,超時時長為5秒,延遲5秒開始檢查,重試2次;如果curl -f http://localhost/ok.html這條命令正常返回0,那麼就表示容器健康,否則就返回1,表示容器不健康;

  驗證:把以上dockerfile構建成鏡像啟動為容器,我們把ok.html刪除或移動到別的目錄,看看容器是否標記為不健康?

[root@node1 test]# docker build . -t test:v1.1
Sending build context to Docker daemon  1.052MB
Step 1/8 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/8 : LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> d31363b96f38
Step 3/8 : LABEL version="1.0"
 ---> Using cache
 ---> 2281f36d7c3c
Step 4/8 : LABEL description="this is test file \ that label-values can span multiple lines."
 ---> Using cache
 ---> 8de872e222fb
Step 5/8 : RUN yum install -y httpd
 ---> Running in 9964718a2c3e
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
 * base: mirrors.bfsu.edu.cn
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Resolving Dependencies
--> Running transaction check
---> Package httpd.x86_64 0:2.4.6-93.el7.centos will be installed
--> Processing Dependency: httpd-tools = 2.4.6-93.el7.centos for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: system-logos >= 7.92.1-1 for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Running transaction check
---> Package apr.x86_64 0:1.4.8-5.el7 will be installed
---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed
---> Package centos-logos.noarch 0:70.0.6-3.el7.centos will be installed
---> Package httpd-tools.x86_64 0:2.4.6-93.el7.centos will be installed
---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package             Arch          Version                    Repository   Size
================================================================================
Installing:
 httpd               x86_64        2.4.6-93.el7.centos        base        2.7 M
Installing for dependencies:
 apr                 x86_64        1.4.8-5.el7                base        103 k
 apr-util            x86_64        1.5.2-6.el7                base         92 k
 centos-logos        noarch        70.0.6-3.el7.centos        base         21 M
 httpd-tools         x86_64        2.4.6-93.el7.centos        base         92 k
 mailcap             noarch        2.1.41-2.el7               base         31 k

Transaction Summary
================================================================================
Install  1 Package (+5 Dependent packages)

Total download size: 24 M
Installed size: 32 M
Downloading packages:
warning: /var/cache/yum/x86_64/7/base/packages/apr-1.4.8-5.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
Public key for apr-1.4.8-5.el7.x86_64.rpm is not installed
--------------------------------------------------------------------------------
Total                                              2.0 MB/s |  24 MB  00:12     
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Importing GPG key 0xF4A80EB5:
 Userid     : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
 Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5
 Package    : centos-release-7-8.2003.0.el7.centos.x86_64 (@CentOS)
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : apr-1.4.8-5.el7.x86_64                                       1/6 
  Installing : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Installing : httpd-tools-2.4.6-93.el7.centos.x86_64                       3/6 
  Installing : centos-logos-70.0.6-3.el7.centos.noarch                      4/6 
  Installing : mailcap-2.1.41-2.el7.noarch                                  5/6 
  Installing : httpd-2.4.6-93.el7.centos.x86_64                             6/6 
  Verifying  : mailcap-2.1.41-2.el7.noarch                                  1/6 
  Verifying  : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Verifying  : httpd-2.4.6-93.el7.centos.x86_64                             3/6 
  Verifying  : apr-1.4.8-5.el7.x86_64                                       4/6 
  Verifying  : httpd-tools-2.4.6-93.el7.centos.x86_64                       5/6 
  Verifying  : centos-logos-70.0.6-3.el7.centos.noarch                      6/6 

Installed:
  httpd.x86_64 0:2.4.6-93.el7.centos                                            

Dependency Installed:
  apr.x86_64 0:1.4.8-5.el7                                                      
  apr-util.x86_64 0:1.5.2-6.el7                                                 
  centos-logos.noarch 0:70.0.6-3.el7.centos                                     
  httpd-tools.x86_64 0:2.4.6-93.el7.centos                                      
  mailcap.noarch 0:2.1.41-2.el7                                                 

Complete!
Removing intermediate container 9964718a2c3e
 ---> a931e93eea06
Step 6/8 : ADD ok.html /var/www/html/
 ---> 97e61f41911d
Step 7/8 : CMD ["/usr/sbin/httpd","-DFOREGROUND"]
 ---> Running in e91ccdef90c2
Removing intermediate container e91ccdef90c2
 ---> 7c8af9bb7eb3
Step 8/8 : HEALTHCHECK --interval=5s --timeout=5s --start-period=5s --retries=2         CMD curl -f http://localhost/ok.html || exit 1
 ---> Running in 80682ab087d3
Removing intermediate container 80682ab087d3
 ---> aa53cba15046
Successfully built aa53cba15046
Successfully tagged test:v1.1
[root@node1 test]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                v1.1                aa53cba15046        8 seconds ago       312MB
test                v1                  461f6ceabc88        57 minutes ago      204MB
centos              7                   b5b4d78bc90c        4 weeks ago         203MB
[root@node1 test]# docker run --name t1 --rm -d test:v1.1
332590e683fcb29f60a28703548fce7aa83df715cbb840e1283472834867d6a1
[root@node1 test]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES
332590e683fc        test:v1.1           "/usr/sbin/httpd -DF…"   3 seconds ago       Up 2 seconds (health: starting)                       t1
[root@node1 test]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                   PORTS               NAMES
332590e683fc        test:v1.1           "/usr/sbin/httpd -DF…"   7 seconds ago       Up 6 seconds (healthy)                       t1
[root@node1 test]# 

  提示:可以看到基於我們寫的dockerfile構建的鏡像已經成功運行為容器,並且標記為healthy;接下來我們進入容器把ok.html幹掉,然後在看看容器是否標記為不健康狀態?

  提示:從上面的信息可以看到我們把ok.html移除后,容器狀態就變成不健康狀態了;我們再把ok.html還原到原有位置,看看容器是否會從不健康轉換為健康呢?

  提示:可以看到把ok.html還原到/var/www/html/目錄后,容器從不健康狀態變為了健康狀態;

  3、SHELL:該指令用於指定默認shell,該指令開始到下一個SHELL中間的命令都是SHELL指定的shell 運行,所以SHELL指令在dockerfile中可出現多次,後面的SHELL指令指定的shell會覆蓋前面所有SHELL指令指定的shell;默認在Linux上是[“/bin/sh”,”-c”]在Windows上述[“cmd”,”/s”,”/c”];SHELL指令必須是以json數組的格式定義;語法SHELL [“executable”, “parameters”];

  4、STOPSIGNAL:該指令用於定義停止容器的信號;默認停止容器是15號信號 SIGTERM;語法STOPSIGNAL signal

  5、ONBUILD:該指令用於在Dockerfile中定義一個觸發器;Dockerfile用於build映像文件,此映像文件亦可作為base image被另一個Dockerfile用作FROM指令的參數,並以之構建新的映像文件;在後面的這個Dockerfile中的FROM指令在build過程中被執行時,將會“觸發”創建其base image的Dockerfile文件中的ONBUILD指令定義的觸發器;用法格式ONBUILD <INSTRUCTION>;儘管任何指令都可註冊成為觸發器指令,但ONBUILD不能自我嵌套,且不會觸發FROM和MAINTAINER指令;使用包含ONBUILD指令的Dockerfile構建的鏡像應該使用特殊的標籤,例如ruby:2.0-onbuild;在ONBUILD指令中使用ADD或COPY指令應該格外小心,因為新構建過程的上下文在缺少指定的源文件時會失敗;

  示例:

[root@node1 test]# cat Dockerfile
FROM centos:7

LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"

ONBUILD RUN yum install -y httpd




[root@node1 test]# 

  提示:以上dockerfile表示在本次構建鏡像中不運行yum install -y httpd這條命令,而是在後面的dockerfile中以本dockerfile製作的進行作為基礎繼續時,yum install -y httpd這條命令就會被觸發執行;簡單講onbuild就是指定dockerfile指令延遲執行;這裏一定要記住一點onbuild指令後面一定是跟的是dockerfile指令;

  驗證:將上面的dockerfile編譯鏡像,看看yum install -y httpd 是否執行了?

[root@node1 test]# docker build . -t test:v1.5
Sending build context to Docker daemon  1.052MB
Step 1/3 : FROM centos:7
 ---> b5b4d78bc90c
Step 2/3 : LABEL maintainer="qiuhom <qiuhom@linux-1874.com>"
 ---> Using cache
 ---> d31363b96f38
Step 3/3 : ONBUILD RUN yum install -y httpd
 ---> Running in d3601fa1c3b7
Removing intermediate container d3601fa1c3b7
 ---> 370e3a843c3c
Successfully built 370e3a843c3c
Successfully tagged test:v1.5
[root@node1 test]# 

  提示:可以看到yum install -y httpd 這條命令並沒有執行;

  驗證:將我們上面製作好的鏡像作為基礎鏡像,再來製作其他鏡像,看看yum install -y httpd 被執行?

[root@node1 aaa]# pwd
/root/test/aaa
[root@node1 aaa]# ls
Dockerfile
[root@node1 aaa]# cat Dockerfile 
FROM test:v1.5

LABEL maintainer="qiuhom <admin@admin.com>"
[root@node1 aaa]# docker build . -t myweb:v1
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM test:v1.5
# Executing 1 build trigger
 ---> Running in cf93e9f03e89
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
 * base: mirrors.huaweicloud.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
Resolving Dependencies
--> Running transaction check
---> Package httpd.x86_64 0:2.4.6-93.el7.centos will be installed
--> Processing Dependency: httpd-tools = 2.4.6-93.el7.centos for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: system-logos >= 7.92.1-1 for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: /etc/mime.types for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libaprutil-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Processing Dependency: libapr-1.so.0()(64bit) for package: httpd-2.4.6-93.el7.centos.x86_64
--> Running transaction check
---> Package apr.x86_64 0:1.4.8-5.el7 will be installed
---> Package apr-util.x86_64 0:1.5.2-6.el7 will be installed
---> Package centos-logos.noarch 0:70.0.6-3.el7.centos will be installed
---> Package httpd-tools.x86_64 0:2.4.6-93.el7.centos will be installed
---> Package mailcap.noarch 0:2.1.41-2.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
 Package             Arch          Version                    Repository   Size
================================================================================
Installing:
 httpd               x86_64        2.4.6-93.el7.centos        base        2.7 M
Installing for dependencies:
 apr                 x86_64        1.4.8-5.el7                base        103 k
 apr-util            x86_64        1.5.2-6.el7                base         92 k
 centos-logos        noarch        70.0.6-3.el7.centos        base         21 M
 httpd-tools         x86_64        2.4.6-93.el7.centos        base         92 k
 mailcap             noarch        2.1.41-2.el7               base         31 k

Transaction Summary
================================================================================
Install  1 Package (+5 Dependent packages)

Total download size: 24 M
Installed size: 32 M
Downloading packages:
warning: /var/cache/yum/x86_64/7/base/packages/apr-1.4.8-5.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
Public key for apr-1.4.8-5.el7.x86_64.rpm is not installed
--------------------------------------------------------------------------------
Total                                              7.2 MB/s |  24 MB  00:03     
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Importing GPG key 0xF4A80EB5:
 Userid     : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
 Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5
 Package    : centos-release-7-8.2003.0.el7.centos.x86_64 (@CentOS)
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : apr-1.4.8-5.el7.x86_64                                       1/6 
  Installing : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Installing : httpd-tools-2.4.6-93.el7.centos.x86_64                       3/6 
  Installing : centos-logos-70.0.6-3.el7.centos.noarch                      4/6 
  Installing : mailcap-2.1.41-2.el7.noarch                                  5/6 
  Installing : httpd-2.4.6-93.el7.centos.x86_64                             6/6 
  Verifying  : mailcap-2.1.41-2.el7.noarch                                  1/6 
  Verifying  : apr-util-1.5.2-6.el7.x86_64                                  2/6 
  Verifying  : httpd-2.4.6-93.el7.centos.x86_64                             3/6 
  Verifying  : apr-1.4.8-5.el7.x86_64                                       4/6 
  Verifying  : httpd-tools-2.4.6-93.el7.centos.x86_64                       5/6 
  Verifying  : centos-logos-70.0.6-3.el7.centos.noarch                      6/6 

Installed:
  httpd.x86_64 0:2.4.6-93.el7.centos                                            

Dependency Installed:
  apr.x86_64 0:1.4.8-5.el7                                                      
  apr-util.x86_64 0:1.5.2-6.el7                                                 
  centos-logos.noarch 0:70.0.6-3.el7.centos                                     
  httpd-tools.x86_64 0:2.4.6-93.el7.centos                                      
  mailcap.noarch 0:2.1.41-2.el7                                                 

Complete!
Removing intermediate container cf93e9f03e89
 ---> a89914bda4b5
Step 2/2 : LABEL maintainer="qiuhom <admin@admin.com>"
 ---> Running in e175e0542b5e
Removing intermediate container e175e0542b5e
 ---> 4f406abeaab7
Successfully built 4f406abeaab7
Successfully tagged myweb:v1
[root@node1 aaa]#

  提示:可以看到在我們的dockerfile中並沒有寫 RUN  yum install -y httpd  ,但build時卻執行了 yum install -y httpd ;這是因為onbuild指令被觸發了;我們可以理解為如果我們製作的鏡像有onbuild指令指定的命令,那麼該鏡像被其他dockerfile 作為基礎鏡像時(或者被其他docker FROM指令引用時)onbuild指定就會被激活,被執行;

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

網頁設計最專業,超強功能平台可客製化

Alink漫談(六) : TF-IDF算法的實現

Alink漫談(六) : TF-IDF算法的實現

目錄

  • Alink漫談(六) : TF-IDF算法的實現
    • 0x00 摘要
    • 0x01 TF-IDF
      • 1.1 原理
      • 1.2 計算方法
    • 0x02 Alink示例代碼
      • 2.1 示例代碼
      • 2.2 TF-IDF模型
      • 2.3 TF-IDF預測
    • 0x03 分詞 Segment
      • 3.1 結巴分詞
      • 3.2 分詞過程
    • 0x04 訓練
      • 4.1 計算IDF
      • 4.2 排序
        • 4.2.1 SortUtils.pSort
          • 採樣SampleSplitPoint
          • 歸併 SplitPointReducer
          • SplitData把真實數據IDF插入
          • reduceGroup計算同類型單詞數目
        • 4.2.2 localSort
      • 4.3 過濾
    • 0x05 生成模型
      • 5.1 DocCountVectorizerModelData
      • 5.2 BuildDocCountModel
    • 0x06 預測
    • 0x07 參考

0x00 摘要

Alink 是阿里巴巴基於實時計算引擎 Flink 研發的新一代機器學習算法平台,是業界首個同時支持批式算法、流式算法的機器學習平台。TF-IDF(term frequency–inverse document frequency)是一種用於信息檢索與數據挖掘的常用加權技術。本文將為大家展現Alink如何實現TF-IDF。

0x01 TF-IDF

TF-IDF(term frequency–inverse document frequency)是一種統計方法,一種用於信息檢索與數據挖掘的常用加權技術。

TF是詞頻(Term Frequency),IDF是逆文本頻率指數(Inverse Document Frequency)。

為什麼要用TF-IDF?因為計算機只能識別数字,對於一個一個的單詞,計算機是看不懂的,更別說是一句話,或是一篇文章。而TF-IDF就是用來將文本轉換成計算機看得懂的語言,或者說是機器學習或深度學習模型能夠進行學習訓練的數據集

1.1 原理

TF-IDF用以評估一個詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。

TF-IDF的主要思想是:如果某個詞或短語在一篇文章中出現的頻率TF高,並且在其他文章中很少出現,則認為此詞或者短語具有很好的類別區分能力,適合用來分類。

TF-IDF實際上是:TF * IDF,TF詞頻(Term Frequency),IDF逆向文件頻率(Inverse Document Frequency)。

詞頻(term frequency,TF)指的是某一個給定的詞語在該文件中出現的頻率。這個数字是對詞數(term count)的歸一化,以防止它偏向長的文件(同一個詞語在長文件里可能會比短文件有更高的詞數,而不管該詞語重要與否)。

IDF逆向文件頻率 (inverse document frequency, IDF)反應了一個詞在所有文本(整個文檔)中出現的頻率,如果一個詞在很多的文本中出現,那麼它的IDF值應該低。而反過來如果一個詞在比較少的文本中出現,那麼它的IDF值應該高。比如一些專業的名詞如“Machine Learning”。這樣的詞IDF值應該高。一個極端的情況,如果一個詞在所有的文本中都出現,那麼它的IDF值應該為0。

如果單單以TF或者IDF來計算一個詞的重要程度都是片面的,因此TF-IDF綜合了TF和IDF兩者的優點,用以評估一字詞對於一個文件集或一個語料庫中的其中一份文件的重要程度。字詞的重要性隨着它在文件中出現的次數成正比增加,但同時會隨着它在語料庫中出現的頻率成反比下降。上述引用總結就是:一個詞語在一篇文章中出現次數越多, 同時在所有文檔中出現次數越少, 越能夠代表該文章,越能與其它文章區分開來。

1.2 計算方法

TF的計算公式如下:

\[TF_w = \frac {N_w}{N} \]

其中 N_w 是在某一文本中詞條w出現的次數,N 是該文本總詞條數。

IDF的計算公式如下:

\[IDF_w = log(\frac {Y}{Y_w + 1}) \]

其中 Y 是語料庫的文檔總數,Y_w 是包含詞條w的文檔數,分母加一是為了避免w 未出現在任何文檔中從而導致分母為0 的情況。

TF-IDF 就是將TF和IDF相乘 :

\[TF-IDF_w = TF_w * IDF_w \]

從以上計算公式便可以看出,某一特定文件內的高詞語頻率,以及該詞語在整個文件集合中的低文件頻率,可以產生出高權重的TF-IDF。因此,TF-IDF傾向於過濾掉常見的詞語,保留重要的詞語。

0x02 Alink示例代碼

2.1 示例代碼

首先我們給出示例代碼,下文是通過一些語料來訓練出一個模型,然後用這個模型來做預測:

public class DocCountVectorizerExample {

    AlgoOperator getData(boolean isBatch) {
        Row[] rows = new Row[]{
                Row.of(0, "二手舊書:醫學電磁成像"),
                Row.of(1, "二手美國文學選讀( 下冊 )李宜燮南開大學出版社 9787310003969"),
                Row.of(2, "二手正版圖解象棋入門/謝恩思主編/華齡出版社"),
                Row.of(3, "二手中國糖尿病文獻索引"),
                Row.of(4, "二手郁達夫文集( 國內版 )全十二冊館藏書")
        };

        String[] schema = new String[]{"id", "text"};

        if (isBatch) {
            return new MemSourceBatchOp(rows, schema);
        } else {
            return new MemSourceStreamOp(rows, schema);
        }
    }

    public static void main(String[] args) throws Exception {
        DocCountVectorizerExample test = new DocCountVectorizerExample();
        BatchOperator batchData = (BatchOperator) test.getData(true);

         // 分詞
        SegmentBatchOp segment = new SegmentBatchOp() 
                                                .setSelectedCol("text")
                                                .linkFrom(batchData);
        // TF-IDF訓練
        DocCountVectorizerTrainBatchOp model = new DocCountVectorizerTrainBatchOp()
                                                .setSelectedCol("text")
                                                .linkFrom(segment);
        // TF-IDF預測
        DocCountVectorizerPredictBatchOp predictBatch = new 
            																		DocCountVectorizerPredictBatchOp()
                                                .setSelectedCol("text")
                                                .linkFrom(model, segment);
        model.print();
        predictBatch.print();
    }
}

2.2 TF-IDF模型

TF-IDF模型打印出來如下:

model_id|model_info
--------|----------
0|{"minTF":"1.0","featureType":"\"WORD_COUNT\""}
1048576|{"f0":"二手","f1":0.0,"f2":0}
2097152|{"f0":"/","f1":1.0986122886681098,"f2":1}
3145728|{"f0":"出版社","f1":0.6931471805599453,"f2":2}
4194304|{"f0":")","f1":0.6931471805599453,"f2":3}
5242880|{"f0":"(","f1":0.6931471805599453,"f2":4}
6291456|{"f0":"入門","f1":1.0986122886681098,"f2":5}
......
36700160|{"f0":"美國","f1":1.0986122886681098,"f2":34}
37748736|{"f0":"謝恩","f1":1.0986122886681098,"f2":35}
38797312|{"f0":"象棋","f1":1.0986122886681098,"f2":36}

2.3 TF-IDF預測

TF-IDF預測結果如下:

id|text
--|----
0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0
1|$37$0:1.0 1:1.0 2:1.0 4:1.0 11:1.0 15:1.0 16:1.0 19:1.0 20:1.0 32:1.0 34:1.0
2|$37$0:1.0 3:2.0 4:1.0 5:1.0 8:1.0 22:1.0 23:1.0 24:1.0 29:1.0 35:1.0 36:1.0
3|$37$0:1.0 12:1.0 27:1.0 31:1.0 33:1.0
4|$37$0:1.0 1:1.0 2:1.0 7:1.0 9:1.0 13:1.0 14:1.0 17:1.0 18:1.0 21:1.0 30:1.0

0x03 分詞 Segment

中文分詞(Chinese Word Segmentation) 指的是將一個漢字序列切分成一個一個單獨的詞。分詞就是將連續的字序列按照一定的規範重新組合成詞序列的過程。

示例代碼中,分詞部分如下:

    SegmentBatchOp segment = new SegmentBatchOp() 
                                            .setSelectedCol("text")
                                            .linkFrom(batchData);

分詞主要是如下兩個類,其作用就是把中文文檔分割成單詞。

public final class SegmentBatchOp extends MapBatchOp <SegmentBatchOp>
	implements SegmentParams <SegmentBatchOp> {

	public SegmentBatchOp(Params params) {
		super(SegmentMapper::new, params);
	}
}

public class SegmentMapper extends SISOMapper {
	private JiebaSegmenter segmentor;
}

3.1 結巴分詞

有經驗的同學看到這裏就會露出微笑:結巴分詞。

jieba分詞是國內使用人數最多的中文分詞工具https://github.com/fxsjy/jieba。jieba分詞支持四種分詞模式:

  • 精確模式,試圖將句子最精確地切開,適合文本分析;
  • 全模式,把句子中所有的可以成詞的詞語都掃描出來, 速度非常快,但是不能解決歧義;
  • 搜索引擎模式,在精確模式的基礎上,對長詞再次切分,提高召回率,適合用於搜索引擎分詞。
  • paddle模式,利用PaddlePaddle深度學習框架,訓練序列標註(雙向GRU)網絡模型實現分詞。

Alink使用了com.alibaba.alink.operator.common.nlp.jiebasegment.viterbi.FinalSeg;來 完成分詞。具體是在https://github.com/huaban/jieba-analysis的基礎上稍微做了調整。

public class JiebaSegmenter implements Serializable {
    private static FinalSeg finalSeg = FinalSeg.getInstance();
    private WordDictionary wordDict;
    ......
    private Map<Integer, List<Integer>> createDAG(String sentence) 
}

從Alink代碼中看,實現了索引分詞和查詢分詞兩種模式,應該是有分詞粒度粗細之分。

createDAG函數的作用是 :在處理句子過程中,基於前綴詞典實現高效的詞圖掃描,生成句子中漢字所有可能成詞情況所構成的有向無環圖 (DAG)。

結巴分詞對於未登錄詞,採用了基於漢字成詞能力的 HMM 模型,使用了 Viterbi 算法。

3.2 分詞過程

分詞過程主要是在SegmentMapper.mapColumn函數中完成的,當輸入是 “二手舊書:醫學電磁成像”,結巴分詞將這個句子分成了六個單詞。具體參見如下:

input = "二手舊書:醫學電磁成像"
tokens = {ArrayList@9619}  size = 6
 0 = {SegToken@9630} "[二手, 0, 2]"
 1 = {SegToken@9631} "[舊書, 2, 4]"
 2 = {SegToken@9632} "[:, 4, 5]"
 3 = {SegToken@9633} "[醫學, 5, 7]"
 4 = {SegToken@9634} "[電磁, 7, 9]"
 5 = {SegToken@9635} "[成像, 9, 11]"
 
mapColumn:44, SegmentMapper (com.alibaba.alink.operator.common.nlp)
apply:-1, 35206803 (com.alibaba.alink.common.mapper.SISOMapper$$Lambda$646)
handleMap:75, SISOColsHelper (com.alibaba.alink.common.mapper)
map:52, SISOMapper (com.alibaba.alink.common.mapper)
map:21, MapperAdapter (com.alibaba.alink.common.mapper)
map:11, MapperAdapter (com.alibaba.alink.common.mapper)
collect:79, ChainedMapDriver (org.apache.flink.runtime.operators.chaining)
collect:35, CountingCollector (org.apache.flink.runtime.operators.util.metrics)
invoke:196, DataSourceTask (org.apache.flink.runtime.operators)

0x04 訓練

訓練是在DocCountVectorizerTrainBatchOp類完成的,其通過linkFrom完成了模型的構建。其實計算TF IDF相對 簡單,複雜之處在於之後的大規模排序。

public DocCountVectorizerTrainBatchOp linkFrom(BatchOperator<?>... inputs) {
        BatchOperator<?> in = checkAndGetFirst(inputs);
  
        DataSet<DocCountVectorizerModelData> resDocCountModel = generateDocCountModel(getParams(), in);

        DataSet<Row> res = resDocCountModel.mapPartition(new MapPartitionFunction<DocCountVectorizerModelData, Row>() {
            @Override
            public void mapPartition(Iterable<DocCountVectorizerModelData> modelDataList, Collector<Row> collector) {
                new DocCountVectorizerModelDataConverter().save(modelDataList.iterator().next(), collector);
            }
        });
        this.setOutput(res, new DocCountVectorizerModelDataConverter().getModelSchema());
        return this;
}

4.1 計算IDF

計算 IDF 的工作是在generateDocCountModel完成的,具體步驟如下:

第一步 通過DocWordSplitCount和UDTF的混合使用得到了文檔中的單詞數目docWordCnt

BatchOperator<?> docWordCnt = in.udtf(
        params.get(SELECTED_COL),
        new String[] {WORD_COL_NAME, DOC_WORD_COUNT_COL_NAME},
        new DocWordSplitCount(NLPConstant.WORD_DELIMITER),
        new String[] {});

DocWordSplitCount.eval的輸入是已經分詞的句子,然後按照空格分詞,按照單詞計數。其結果是:

map = {HashMap@9816}  size = 6
 "醫學" -> {Long@9833} 1
 "電磁" -> {Long@9833} 1
 ":" -> {Long@9833} 1
 "成像" -> {Long@9833} 1
 "舊書" -> {Long@9833} 1
 "二手" -> {Long@9833} 1

第二步 得到了文檔數目docCnt

BatchOperator docCnt = in.select("COUNT(1) AS " + DOC_COUNT_COL_NAME);

這個數目會廣播出去 .withBroadcastSet(docCnt.getDataSet(), "docCnt");,後面的CalcIdf會繼續使用,進行 行數統計。

第三步 會通過CalcIdf計算出每一個單詞的DF和IDF

open時候會獲取docCnt。然後reduce會計算IDF,具體計算如下:

double idf = Math.log((1.0 + docCnt) / (1.0 + df));
collector.collect(Row.of(featureName, -wordCount, idf));

具體得到如下

df = 1.0
wordCount = 1.0
featureName = "中國"
idf = 1.0986122886681098
docCnt = 5

這裏一個重點是:返回值中,是 -wordCount,因為單詞越多權重越小,為了比較所以取負

4.2 排序

得到所有單詞的IDF之後,就得到了一個IDF字典,這時候需要對字典按照權重進行排序。排序具體分為兩步。

4.2.1 SortUtils.pSort

第一步是SortUtils.pSort,大規模并行抽樣排序。

Tuple2<DataSet<Tuple2<Integer, Row>>, DataSet<Tuple2<Integer, Long>>> partitioned = SortUtils.pSort(sortInput, 1);

這步非常複雜,Alink參考了論文,如果有興趣的兄弟可以深入了解下。

* reference: Yang, X. (2014). Chong gou da shu ju tong ji (1st ed., pp. 25-29).
* Note: This algorithm is improved on the base of the parallel sorting by regular sampling(PSRS).

pSort返回值是:

* @return f0: dataset which is indexed by partition id, f1: dataset which has partition id and count.

pSort中又分如下幾步

採樣SampleSplitPoint

SortUtils.SampleSplitPoint.mapPartition這裏完成了採樣。

DataSet <Tuple2 <Object, Integer>> splitPoints = input
   .mapPartition(new SampleSplitPoint(index))
   .reduceGroup(new SplitPointReducer());

這裏的輸入row就是上文IDF的返回數值。

用allValues記錄了本task目前處理的句子有多少個單詞。

用splitPoints做了採樣。如何選擇呢,通過genSampleIndex函數。

public static Long genSampleIndex(Long splitPointIdx, Long count, Long splitPointSize) {
   splitPointIdx++;
   splitPointSize++;

   Long div = count / splitPointSize;
   Long mod = count % splitPointSize;

   return div * splitPointIdx + ((mod > splitPointIdx) ? splitPointIdx : mod) - 1;
}

後續操作也使用同樣的genSampleIndex函數來做選擇,這樣保證在操作所有序列上可以選取同樣的採樣點。

allValues = {ArrayList@10264}  size = 8  //本task有多少單詞
 0 = {Double@10266} -2.0
 1 = {Double@10271} -1.0
 2 = {Double@10272} -1.0
 3 = {Double@10273} -1.0
 4 = {Double@10274} -1.0
 5 = {Double@10275} -1.0
 6 = {Double@10276} -1.0
 7 = {Double@10277} -1.0
 
splitPoints = {ArrayList@10265}  size = 7 //採樣了7個
 0 = {Double@10266} -2.0
 1 = {Double@10271} -1.0
 2 = {Double@10272} -1.0
 3 = {Double@10273} -1.0
 4 = {Double@10274} -1.0
 5 = {Double@10275} -1.0
 6 = {Double@10276} -1.0

最後返回採樣數據,返回時候附帶當前taskIDnew Tuple2 <Object, Integer>(obj,taskId)

這裡有一個trick點

  for (Object obj : splitPoints) {
     Tuple2 <Object, Integer> cur
        = new Tuple2 <Object, Integer>(
        obj,
        taskId); //這裏返回的是類似 (-5.0,2) :其中2就是task id,-5.0是-wordcount。
     out.collect(cur);
  }

  out.collect(new Tuple2(
     getRuntimeContext().getNumberOfParallelSubtasks(),
     -taskId - 1));//這裏返回的是一個特殊元素,類似(4,-2) :其中4是本應用中并行task數目,-2是當前-taskId - 1。這個task數目後續就會用到。

具體數據參見如下:

row = {Row@10211} "中國,-1.0,1.0986122886681098"
 fields = {Object[3]@10214} 
 
cur = {Tuple2@10286} "(-5.0,2)" // 返回採樣數據,返回時候附帶當前taskID
 f0 = {Double@10285} -5.0 // -wordcount。
 f1 = {Integer@10300} 2 // 當前taskID
歸併 SplitPointReducer

歸併所有task生成的sample。然後再次sample,把sample數據組成一個數據塊,這個數據塊選擇的原則是:每個task都盡量選擇若干sample

這裏其實是有一個轉換,就是從正常單詞的抽樣 轉換到 某一類單詞的抽樣,這某一類的意思舉例是:出現次數為一,或者出現次數為五 這種單詞

這裏all是所有採樣數據,其中一個元素內容舉例 (-5.0,2) :其中2就是task id,-5.0是-wordcount。

這裏用 Collections.sort(all, new PairComparator()); 來對所有採樣數據做排序。排序基準是首先對 -wordcount,然後對task ID。

SplitPointReducer的返回採樣數值就作為廣播變量存儲起來:.withBroadcastSet(splitPoints, "splitPoints");

這裏的trick點是:

for (Tuple2 <Object, Integer> value : values) {
   if (value.f1 < 0) { 
      instanceCount = (int) value.f0;  // 特殊數據,類似(4,-2) :其中4是本應用中task數目,這個就是後續選擇哪些taskid的基準
      continue;
   }
   all.add(new Tuple2 <>(value.f0, value.f1)); // (-5.0,2) 正常數據
}

選擇sample index splitPoints.add(allValues.get(index));也使用了同樣的genSampleIndex。

計算中具體數據如下:

for (int i = 0; i < splitPointSize; ++i) {
		int index = genSampleIndex(
					Long.valueOf(i),
					Long.valueOf(count),
					Long.valueOf(splitPointSize))
					.intValue();
		spliters.add(all.get(index));
}
for (Tuple2 <Object, Integer> spliter : spliters) {
		out.collect(spliter);
}

count = 33
all = {ArrayList@10245}  size = 33 // 所有採樣數據,
0 = {Tuple2@10256} "(-5.0,2)"// 2就是task id,-5.0是-wordcount。
1 = {Tuple2@10285} "(-2.0,0)"
......
6 = {Tuple2@10239} "(-1.0,0)"
7 = {Tuple2@10240} "(-1.0,0)"
8 = {Tuple2@10241} "(-1.0,0)"
9 = {Tuple2@10242} "(-1.0,0)"
10 = {Tuple2@10243} "(-1.0,0)"
11 = {Tuple2@10244} "(-1.0,1)"
......
16 = {Tuple2@10278} "(-1.0,1)"
......
24 = {Tuple2@10279} "(-1.0,2)"
......
32 = {Tuple2@10313} "(-1.0,3)"
  
// spliters是返回結果,這裏分別選取了all中index為8,16,24這個三個record。每個task都選擇了一個元素。
spliters = {HashSet@10246}  size = 3
 0 = {Tuple2@10249} "(-1.0,0)" // task 0 被選擇。就是說,這裏從task 0中選擇了一個count是1的元素,具體選擇哪個單詞其實不重要,就是為了選擇count是1的這種即可。
 1 = {Tuple2@10250} "(-1.0,1)" // task 1 被選擇。具體同上。
 2 = {Tuple2@10251} "(-1.0,2)" // task 2 被選擇。具體同上。
SplitData把真實數據IDF插入

use binary search to partition data into sorted subsets。前面函數給出的是詞的count,但是沒有IDF。這裏將用二分法查找 找到IDF,然後把IDF插入到partition data中。

首先要注意一點:splitData的輸入就是原始輸入input, 和splitPoints的輸入是一樣 的

DataSet <Tuple2 <Integer, Row>> splitData = input
   .mapPartition(new SplitData(index))
   .withBroadcastSet(splitPoints, "splitPoints");

open函數中會取出廣播變量 splitPoints。

splitPoints = {ArrayList@10248}  size = 3
 0 = {Tuple2@10257} "(-1.0,0)"
 1 = {Tuple2@10258} "(-1.0,1)"
 2 = {Tuple2@10259} "(-1.0,2)"

本函數的輸入舉例

row = {Row@10232} "入門,-1.0,1.0986122886681098"

會在splitPoints中二分法查找,得到splits中每一個 sample 對應的真實IDF。然後發送出去。

這裏需要特殊說明下,這個二分法查找查找的是IDF數值,比如count為1的這種單詞對應的IDF數值,可能很多單詞都是count為1,所以找到一個這樣單詞的IDF即可

splitPoints = {ArrayList@10223}  size = 3
 0 = {Tuple2@10229} "(-1.0,0)"
 1 = {Tuple2@10230} "(-1.0,1)"
 2 = {Tuple2@10231} "(-1.0,2)"
curTuple.f0 = {Double@10224} -1.0
  
int bsIndex = Collections.binarySearch(splitPoints, curTuple, new PairComparator());

		int curIndex;
		if (bsIndex >= 0) {
			curIndex = bsIndex;
		} else {
			curIndex = -bsIndex - 1;
		}

// 假設單詞是 "入門",則發送的是 "入門" 這類單詞在本partition的index,和 "入門" 的單詞本身
// 其實,從調試過程看,是否發送單詞信息本身並不重要,因為接下來的那一步操作中,並沒有用到單詞本身信息
out.collect(new Tuple2 <>(curIndex, row)); 
reduceGroup計算同類型單詞數目

這裡是計算在某一partition中,某一種類單詞的數目。比如count為1的單詞,這種單詞總共有多少個

後續會把new Tuple2 <>(id, count)作為partitionCnt廣播變量存起來。

id就是這類單詞在這partition中間的index,我們暫時稱之為partition index。count就是這類單詞在本partition的數目。

// 輸入舉例
value = {Tuple2@10312} "(0,入門,-1.0,1.0986122886681098)"
 f0 = {Integer@10313} 0
 
// 計算數目
for (Tuple2 <Integer, Row> value : values) {
		id = value.f0;
		count++;
}

out.collect(new Tuple2 <>(id, count));  
  
// 輸出舉例,假如是序號為0的這類單詞,其總體數目是12。這個序號0就是這類單詞在某一partition中的序號。就是上面的 curIndex。
id = {Integer@10313} 0
count = {Long@10338} 12

4.2.2 localSort

第二步是localSort。Sort a partitioned dataset. 最終排序並且會返回最終數值,比如 (29, “主編,-1.0,1.0986122886681098″), 29就是”主編” 這個單詞在 IDF字典中的序號。

DataSet<Tuple2<Long, Row>> ordered = localSort(partitioned.f0, partitioned.f1, 1);

open函數中會獲取partitionCnt。然後計算出某一種類單詞,其在本partition之前所有partition中,這類單詞數目。

public void open(Configuration parameters) throws Exception {
		List <Tuple2 <Integer, Long>> bc = getRuntimeContext().getBroadcastVariable("partitionCnt");
		startIdx = 0L;
		int taskId = getRuntimeContext().getIndexOfThisSubtask();
		for (Tuple2 <Integer, Long> pcnt : bc) {
			if (pcnt.f0 < taskId) {
					startIdx += pcnt.f1;
			}
		}
}

bc = {ArrayList@10303}  size = 4
 0 = {Tuple2@10309} "(0,12)"  // 就是task0裏面,這種單詞有12個
 1 = {Tuple2@10310} "(2,9)"// 就是task1裏面,這種單詞有2個
 2 = {Tuple2@10311} "(1,7)"// 就是task2裏面,這種單詞有1個
 3 = {Tuple2@10312} "(3,9)"// 就是task3裏面,這種單詞有3個
// 如果本task id是4,則其startIdx為30。就是所有partition之中,它前面index所有單詞的和。  

然後進行排序。Collections.sort(valuesList, new RowComparator(field));

valuesList = {ArrayList@10405}  size = 9
 0 = {Row@10421} ":,-1.0,1.0986122886681098"
 1 = {Row@10422} "主編,-1.0,1.0986122886681098"
 2 = {Row@10423} "國內,-1.0,1.0986122886681098"
 3 = {Row@10424} "文獻,-1.0,1.0986122886681098"
 4 = {Row@10425} "李宜燮,-1.0,1.0986122886681098"
 5 = {Row@10426} "糖尿病,-1.0,1.0986122886681098"
 6 = {Row@10427} "美國,-1.0,1.0986122886681098"
 7 = {Row@10428} "謝恩,-1.0,1.0986122886681098"
 8 = {Row@10429} "象棋,-1.0,1.0986122886681098"
  
  
// 最後返回時候,就是  (29, "主編,-1.0,1.0986122886681098"),29就是“主編”這個單詞在最終字典中的序號。
// 這個序號是startIdx + cnt,startIdx是某一種類單詞,其在本partition之前所有partition中,這類單詞數目。比如在本partition之前,這類單詞有28個,則本partition中,從29開始計數。就是最終序列號
	for (Row row : valuesList) {
		out.collect(Tuple2.of(startIdx + cnt, row));
		cnt++; // 這裏就是在某一類單詞中,單調遞增,然後賦值一個字典序列而已
	}  
cnt = 1
row = {Row@10336} "主編,-1.0,1.0986122886681098"
 fields = {Object[3]@10339} 
startIdx = 28

4.3 過濾

最後還要進行過濾,如果文字個數超出了字典大小,就拋棄多餘文字。

ordered.filter(new FilterFunction<Tuple2<Long, Row>>() {
    @Override
    public boolean filter(Tuple2<Long, Row> value) {
        return value.f0 < vocabSize;
    }
})

0x05 生成模型

具體生成模型代碼如下。

DataSet<DocCountVectorizerModelData> resDocCountModel = ordered.filter(new FilterFunction<Tuple2<Long, Row>>() {
    @Override
    public boolean filter(Tuple2<Long, Row> value) {
        return value.f0 < vocabSize;
    }
}).mapPartition(new BuildDocCountModel(params)).setParallelism(1);
return resDocCountModel;

其中關鍵類是 DocCountVectorizerModelData 和 BuildDocCountModel。

5.1 DocCountVectorizerModelData

這是向量信息。

/**
 * Save the data for DocHashIDFVectorizer.
 *
 * Save a HashMap: index(MurMurHash3 value of the word), value(Inverse document frequency of the word).
 */
public class DocCountVectorizerModelData {
    public List<String> list;
    public String featureType;
    public double minTF;
}

5.2 BuildDocCountModel

最終生成的模型信息如下,這個也就是之前樣例代碼給出的輸出。

modelData = {DocCountVectorizerModelData@10411} 
 list = {ArrayList@10409}  size = 37
  0 = "{"f0":"9787310003969","f1":1.0986122886681098,"f2":19}"
  1 = "{"f0":"下冊","f1":1.0986122886681098,"f2":20}"
  2 = "{"f0":"全","f1":1.0986122886681098,"f2":21}"
  3 = "{"f0":"華齡","f1":1.0986122886681098,"f2":22}"
  4 = "{"f0":"圖解","f1":1.0986122886681098,"f2":23}"
  5 = "{"f0":"思","f1":1.0986122886681098,"f2":24}"
  6 = "{"f0":"成像","f1":1.0986122886681098,"f2":25}"
  7 = "{"f0":"舊書","f1":1.0986122886681098,"f2":26}"
  8 = "{"f0":"索引","f1":1.0986122886681098,"f2":27}"
  9 = "{"f0":":","f1":1.0986122886681098,"f2":28}"
  10 = "{"f0":"主編","f1":1.0986122886681098,"f2":29}"
  11 = "{"f0":"國內","f1":1.0986122886681098,"f2":30}"
  12 = "{"f0":"文獻","f1":1.0986122886681098,"f2":31}"
  13 = "{"f0":"李宜燮","f1":1.0986122886681098,"f2":32}"
  14 = "{"f0":"糖尿病","f1":1.0986122886681098,"f2":33}"
  15 = "{"f0":"美國","f1":1.0986122886681098,"f2":34}"
  16 = "{"f0":"謝恩","f1":1.0986122886681098,"f2":35}"
  17 = "{"f0":"象棋","f1":1.0986122886681098,"f2":36}"
  18 = "{"f0":"二手","f1":0.0,"f2":0}"
  19 = "{"f0":")","f1":0.6931471805599453,"f2":1}"
  20 = "{"f0":"/","f1":1.0986122886681098,"f2":2}"
  21 = "{"f0":"出版社","f1":0.6931471805599453,"f2":3}"
  22 = "{"f0":"(","f1":0.6931471805599453,"f2":4}"
  23 = "{"f0":"入門","f1":1.0986122886681098,"f2":5}"
  24 = "{"f0":"醫學","f1":1.0986122886681098,"f2":6}"
  25 = "{"f0":"文集","f1":1.0986122886681098,"f2":7}"
  26 = "{"f0":"正版","f1":1.0986122886681098,"f2":8}"
  27 = "{"f0":"版","f1":1.0986122886681098,"f2":9}"
  28 = "{"f0":"電磁","f1":1.0986122886681098,"f2":10}"
  29 = "{"f0":"選讀","f1":1.0986122886681098,"f2":11}"
  30 = "{"f0":"中國","f1":1.0986122886681098,"f2":12}"
  31 = "{"f0":"書","f1":1.0986122886681098,"f2":13}"
  32 = "{"f0":"十二冊","f1":1.0986122886681098,"f2":14}"
  33 = "{"f0":"南開大學","f1":1.0986122886681098,"f2":15}"
  34 = "{"f0":"文學","f1":1.0986122886681098,"f2":16}"
  35 = "{"f0":"郁達夫","f1":1.0986122886681098,"f2":17}"
  36 = "{"f0":"館藏","f1":1.0986122886681098,"f2":18}"
 featureType = "WORD_COUNT"
 minTF = 1.0

0x06 預測

預測業務邏輯是DocCountVectorizerModelMapper

首先我們可以看到 FeatureType,這個可以用來配置輸出哪種信息。比如可以輸出以下若干種:

public enum FeatureType implements Serializable {
    /**
     * IDF type, the output value is inverse document frequency.
     */
    IDF(
        (idf, termFrequency, tokenRatio) -> idf
    ),
    /**
     * WORD_COUNT type, the output value is the word count.
     */
    WORD_COUNT(
        (idf, termFrequency, tokenRatio) -> termFrequency
    ),
    /**
     * TF_IDF type, the output value is term frequency * inverse document frequency.
     */
    TF_IDF(
        (idf, termFrequency, tokenRatio) -> idf * termFrequency * tokenRatio
    ),
    /**
     * BINARY type, the output value is 1.0.
     */
    BINARY(
        (idf, termFrequency, tokenRatio) -> 1.0
    ),
    /**
     * TF type, the output value is term frequency.
     */
    TF(
        (idf, termFrequency, tokenRatio) -> termFrequency * tokenRatio
    );
}

其次,在open函數中,會加載模型,比如:

wordIdWeight = {HashMap@10838}  size = 37
 "醫學" -> {Tuple2@10954} "(6,1.0986122886681098)"
 "選讀" -> {Tuple2@10956} "(11,1.0986122886681098)"
 "十二冊" -> {Tuple2@10958} "(14,1.0986122886681098)"
...
 "華齡" -> {Tuple2@11022} "(22,1.0986122886681098)"
 "索引" -> {Tuple2@11024} "(27,1.0986122886681098)"
featureType = {DocCountVectorizerModelMapper$FeatureType@10834} "WORD_COUNT"

最後,預測時候調用predictSparseVector函數,會針對輸入 二手 舊書 : 醫學 電磁 成像來進行匹配。生成稀疏向量SparseVector。

0|$37$0:1.0 6:1.0 10:1.0 25:1.0 26:1.0 28:1.0

以上表示那幾個單詞 分別對應0 6 10 25 26 28 這幾個字典中對應序號的單詞,其在本句對應的出現數目都是一個。

0x07 參考

Tf-Idf詳解及應用

https://github.com/fxsjy/jieba

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

【其他文章推薦】

※產品缺大量曝光嗎?你需要的是一流包裝設計!

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

※回頭車貨運收費標準

※推薦評價好的iphone維修中心

※超省錢租車方案

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

※推薦台中搬家公司優質服務,可到府估價