記一次接口性能優化實踐總結:優化接口性能的八個建議_台中搬家公司

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

前言

最近對外接口偶現504超時問題,原因是代碼執行時間過長,超過nginx配置的15秒,然後真槍實彈搞了一次接口性能優化。在這裏結合優化過程,總結了接口優化的八個要點,希望對大家有幫助呀~

  • 數據量比較大,批量操作數據入庫
  • 耗時操作考慮異步處理
  • 恰當使用緩存
  • 優化程序邏輯、代碼
  • SQL優化
  • 壓縮傳輸內容
  • 考慮使用文件/MQ等其他方式暫存,異步再落地DB
  • 跟產品討論需求最恰當,最舒服的實現方式

嘻嘻,先看一下我們對外轉賬接口的大概流程吧

1.數據量比較大,批量操作數據入庫

優化前:

//for循環單筆入庫
for(TransDetail detail:list){
  insert(detail);  
}

優化后:

// 批量入庫,mybatis demo實現
<insert id="insertBatch" parameterType="java.util.List">
insert into trans_detail( id,amount,payer,payee) values
 <foreach collection="list" item="item" index="index" separator=",">(
    #{item.id},	#{item.amount},
    #{item.payer},#{item.payee}
  )
</foreach>
</insert>

性能對比:

單位(ms) for循環單筆入庫 批量入庫
500條 1432 1153
1000條 1876 1425

解析

  • 批量插入性能更好,更加省時間,為什麼呢?
打個比喻:假如你需要搬一萬塊磚到樓頂,你有一個電梯,電梯一次可以放適量的磚(最多放500),
你可以選擇一次運送一塊磚,也可以一次運送500,你覺得哪種方式更方便,時間消耗更少?

2.耗時操作考慮異步處理

耗時操作,考慮用異步處理,這樣可以降低接口耗時。本次轉賬接口優化,匹配聯行號的操作耗時有點長,所以優化過程把它移到異步處理啦,如下:

優化前:

優化后

匹配聯行號的操作異步處理

性能對比:

假設一個聯行號匹配6ms

同步 異步
500條 3000ms ~
1000條 6000ms ~

解析:

  • 因為聯行號匹配比較耗時,放在異步處理的話,同步聯機返回可以省掉這部分時間,大大提升接口性能,並且不會影響到轉賬主流程功能。
  • 除了這個例子,平時我們類似功能,如用戶註冊成功后,短信郵件通知,也是可以異步處理的,這個優化建議香餑餑的~
  • 所以,太耗時的操作,在不影響主流程功能的情況下,可以考慮開子線程異步處理的啦。

3.恰當使用緩存

在適當的業務場景,恰當地使用緩存,是可以大大提高接口性能的。這裏的緩存包括:Redis,JVM本地緩存,memcached,或者Map等。

這次轉賬接口,使用到緩存啦,舉個簡單例子吧~

優化前

以下是輸入用戶賬號,匹配聯行號的流程圖

優化后:

恰當使用緩存,代替查詢DB表,流程圖如下:

解析:

  • 把熱點數據放到緩存,不用每次查詢都去DB拉取,節省了這部分查SQL的耗時,美滋滋呀~
  • 當然,不是什麼數據都適合放到緩存的哦,訪問比較頻繁的熱點數據才考慮緩存起來呢~

4. 優化程序邏輯、代碼

優化程序邏輯、程序代碼,是可以節省耗時的。

我這裏就本次的轉賬接口優化,舉個例子吧~

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

優化前:

優化前,聯行號查詢了兩次(檢驗參數一次,插入DB前查詢一次),如下偽代碼:


punlic void process(Req req){
  //檢驗參數,包括聯行號(前端傳來的payeeBankNo可以為空,但是如果後端沒匹配到,會拋異常)
   checkTransParams(Req req);
   //Save DB
  saveTransDetail(req); 
}

void checkTransParams(Req req){
    //check Amount,and so on.
    checkAmount(req.getamount);
    //check payeebankNo
    if(Utils.isEmpty(req.getPayeeBankNo())){
        String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
        if(Utils.isEmpty(payeebankNo){
            throws Exception();
        }
    }
}

int saveTransDetail(req){
    String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
    req.setPayeeBankNo(payeebankNo);
    insert(req);
    ...
}

優化后:

優化后,只在校驗參數的時候插敘一次,然後設置到對象裏面~ 入庫前就不用再查啦,偽代碼如下:

void checkTransParams(Req req){
    //check Amount,and so on.
    checkAmount(req.getamount);
    //check payeebankNo
    if(Utils.isEmpty(req.getPayeeBankNo())){
        String payeebankNo = getPayeebankNo(req.getPayeeAccountNo);
        if(Utils.isEmpty(payeebankNo){
            throws Exception();
        }
    }
    //查詢到有聯行號,直接設置進去啦,這樣等下入庫不用再插入多一次
    req.setPayeeBankNo(payeebankNo);
}

int saveTransDetail(req){
    insert(req);
    ...
}

解析:

  • 對於優化程序邏輯、代碼,是可以降低接口耗時的。以上demo只是一個很簡單的例子,就是優化前payeeBankNo查詢了兩次,但是其實只查一次就可以了。很多時候,我們都知道這個點,但就是到寫代碼的時候,又忘記了呀~所以,寫代碼的時候,留點心吧,優化你的程序邏輯、代碼哦。
  • 除了以上demo這點,還有其它的點,如優化if複雜的邏輯條件,考慮是否可以調整順序,或者for循環,是否重複實例化對象等等,這些適當優化,都是可以讓你的代碼跑得更快的。

之前我這篇文章,也提了幾個優化點噢,有興趣的朋友可以看一下哈~

寫代碼有這些想法,同事才不會認為你是複製粘貼程序員

5. 優化你的SQL

很多時候,你的接口性能瓶頸就在SQL這裏,慢查詢需要我們重點關注的點呢。

我們可以通過這些方式優化我們的SQL:

  • 加索引
  • 避免返回不必要的數據
  • 優化sql結構
  • 分庫分表
  • 讀寫分離

有興趣的朋友可以看一下我這篇文章呢,很詳細的SQL優化點:

後端程序員必備:書寫高質量SQL的30條建議

6.壓縮傳輸內容

壓縮傳輸內容,文件變得更小,因此傳輸會更快啦。10M帶寬,傳輸10k的報文,一般比傳輸1M的會快呀;打個比喻,一匹千里馬,它馱着一百斤的貨跑得快,還是馱着10斤的貨物跑得快呢?

解析:

  • 如果你的接口性能不好,然後傳輸報文比較大的話,這時候是可以考慮壓縮文件內容傳輸的,最後優化效果可能很不錯哦~

7. 考慮使用文件/MQ等其他方式暫存數據,異步再落地DB

如果數據太大,落地數據庫實在是慢的話,可以考慮先用文件的方式保存,或者考慮MQ,先落地,再異步保存到數據庫~

本次轉賬接口,如果是併發開啟,10個併發度,每個批次1000筆數據,數據庫插入會特別耗時,大概10秒左右,這個跟我們公司的數據庫同步機制有關,併發情況下,因為優先保證同步,所以并行的插入變成串行啦,就很耗時。

優化前:

優化前,1000筆先落地DB數據庫,再異步轉賬,如下:

優化后:

先保存數據到文件,再異步下載下來,插入數據庫,如下:

解析:

  • 如果你的耗時瓶頸就在數據庫插入操作這裏了,那就考慮文件保存或者MQ或者其他方式暫存吧,文件保存數據,對比一下耗時,有時候會有意想不到的效果哦。

8.跟產品討論需求最恰當,最舒服的實現方式

這點個人覺得還是很重要的,有些需求需要好好跟產品溝通的。

比如有個用戶連麥列表展示的需求,產品說要展示所有的連麥信息,如果一個用戶的連麥列表信息好大,你拉取所有連麥數據回來,接口性能就降下來啦。如果產品打樁分析,會發現,一般用戶看連麥列表,也就看前幾頁因此,奸笑,哈哈 其實,那個超大分頁加載問題也是類似的。即limit +一個超大的數,一般會很慢的~~

總結

本文呢,基於一次對外接口耗時優化的實踐,總結了優化接口性能的八個點,希望對大家日常開發有幫助哦~嘻嘻,有興趣可以逛逛我的github哈,本文會收藏到github里滴哈

https://github.com/whx123/JavaHome

公眾號

  • 歡迎關注我個人公眾號,交個朋友,一起學習哈~

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

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

廣場舞,俘獲多少人的心_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

“我和我的祖國”全國廣場舞成果彙報展演現場 傅德偉 攝

沒有哪個舞蹈像廣場舞有着這樣廣泛的群眾基礎,不管是在四季分明的北方,還是綠樹常青的南方,不管是城市還是鄉村,只要有一塊空地,都可以見到隨着音樂翩翩起舞的人群。簡單易學、沒有門檻、有益身心……廣場舞以親民的姿態走進了大眾的視野。

近些年,隨着國家相關政策的推出,廣場舞擾民等問題已不再突出。健康有序、积極向上的廣場舞正在煥發新的光彩,它將如何俘獲億萬國民的心?

“大媽”標籤漸漸淡去

廣場舞還是大媽們的專利么?如果你還這樣認為,你就out啦。

雖然,廣場舞的潮流前線中,大多是大媽們的身影,但隨着我國經濟水平提高,社會老齡化加劇,基本生活得到保障的老年人退休后,有了大把的空閑時間。基於鍛煉身體的需要,脫胎於健身操的廣場舞越來越受到老年人垂青,不僅有大媽,大爺們也越來越活躍在舞蹈的人群中。廣場舞正在籠絡男性以及其他年齡層的“人心”。

記者在“我和我的祖國”——文化新生活全國廣場舞展演中見到了來自河北張家口的武志剛,他所在的廣場舞隊伍中男性佔據了近三分之一。為什麼會跳廣場舞?武志剛說,以前男性退休后,大多打打牌、遛遛鳥,或者宅在家中。看着跳廣場舞的人每天都朝氣蓬勃的,他們也就加入進來,一可以鍛煉身體,二可以結識朋友,逐漸找到了屬於自己的朋友圈。

“廣場舞=大媽們的運動”這種傳統印象正在改變,全國各地的年輕人也開始對廣場舞趨之若鶩。街舞、拉丁舞、爵士舞等流行舞種被年輕人帶到廣場。江西省贛州市上猶縣文化館青年藝術團的16歲女孩張萍,通過跳舞來傾訴內心。為了提升舞蹈水平,她加入廣場舞團隊,在這裏既體味到長輩們的關愛,也可以學到新的舞蹈技巧。

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

近些年,青少年在廣場舞比賽中越來越頻繁地現身。據記者了解,青少年通過參加比賽增加了自我鍛煉和自我展示的機會,“多一個舞台,多一次鍛煉,就可以離自己的舞蹈夢想更近一步。”這是他們共同的想法。

健身需求逐漸多元

對跳廣場舞的老年群體進行調研,你會發現,他們選擇跳廣場舞不僅是為了健身,更重要的是可以“健心”。

退休后的生活單調而孤獨,不是圍著兒孫轉,就是抬腳跑醫院,老年人逐漸在單一的生活中失去了自己的社交圈,這是現代社會中需要關注的問題之一。作為一種集體舞蹈,廣場舞帶給老年人集體的溫暖,在一幫志同道合的舞友中,老年人獲得的不僅是身心愉悅,更是社會歸屬感的重拾。

令人驚喜的是,年輕人除了通過跳舞釋放壓力外,他們走上廣場還與街舞的流行有關:2018年,中國首部原創交響樂街舞作品《黃河》在“荷花獎”當代舞、現代舞評選中榮獲“當代舞獎”;北京舞蹈學院成立了中國街舞文化研究中心;《這就是街舞》《熱血街舞團》等多款街舞綜藝節目大熱;大學校園裡,街舞社團也越來越多……街舞場所與廣場舞場所的重合,是廣場上出現年輕人的重要原因。

從“草台”跳上了舞台

經過多年的發展,由群眾自發組織並在大江南北迅速風靡的廣場舞,已經不只是在公園和廣場上簡單呈現,它們登上了更大的舞台綻放魅力。

成立於2000年的湖北武漢的农民藝術團,是一個平均年齡55歲的群眾廣場舞團隊。最初他們只是在公園裡、廣場上進行活動,如今,由於廣場舞的火熱,他們多次受邀參加活動,足跡遍及北京、上海、香港、澳門等城市。中國舞蹈家協會民族民間舞蹈專業委員會副主任阮蘭玉說,廣場舞不僅體現出基層的文化,更能展現出群眾积極向上、蓬勃活躍的精神面貌。隨着新時代老百姓對生活的新期待和對美的追求的提升,他們更希望走上更大的舞台。

追求藝術性將成未來發展趨勢

對廣場舞未來的發展,舞者與專家各有期許。來自北京的蔣艷芝希望將來的廣場舞有更多專業老師的指導,“草台班子”已經不能滿足她們的需求,她們想要向更高水平邁進。來自江西的朱麗娟認為,越來越多的人加入廣場舞隊伍,需要充足的場地和空間以供使用。中國文化館協會舞蹈委員會主任委員、江蘇省南通市文化館館長曹錦揚則認為,廣場舞是一種表演形式,不是一個舞種。只要動作優美、音樂悅耳、風格濃郁、難易適度就可以歸入廣場舞。集藝術性、審美性、民族性、風格性、娛樂性、科學性為一體,一定是未來廣場舞的發展方向。(記者 杜潔芳 王彬)

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

還在花錢加裝導航?教授實測哪家手機導航最好用!_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

實際上,這幾款比較熱門的導航軟件,無論是定位的精準性還是導航時的精準性,都是值得肯定的,這幾位选手最大的區別,莫過於在路線、額外服務、以及使用便利性上。搜狗地圖推薦指數:在使用軟件過程中,你能發現搜狗地圖的界面設計十分簡潔,在主界面上所能用到功能主要有4個,分別是身邊服務、路線查詢、導航功能、軟件服務。

前言

認為,導航已經成為了我們生活息息相關的一個軟件,隨着導航軟件不斷地更新、換代,無論是我們駕車、還是坐公共交通工具、還是步行或者打車,一個導航軟件就能幫到你,然而市面上哪一款導航使用最為方便好用?為此特意將市面上使用最多的導航軟件下載到了手機上,進行了為期幾天的日常體驗。

從汽車的角度先來說說導航的發展,首先汽車上也具備GpS導航功能,但是使用效率比較低,而且帶有GpS導航功能的一般都要高配車型甚至是頂配車型。可以這麼說,其實買高配的人並不多,所以路上跑的車在原廠狀況下基本是不具備導航功能。

雖然在馬雲家的商店裡有着格式各樣的導航配件,從幾百到幾千元,讓人挑得眼花繚亂。但是這一行水很深,市場上充斥着大量的三無產品、山寨產品,先不說屏幕的分辨率低的問題,而且系統大多都是卡頓、反應緩慢,提供的導航軟件在實際體驗上更是慘不忍睹,也可以這麼說,為什麼現在導航市場還是這麼火熱?在看來大多數車主都是為了那一塊大屏幕。中控加裝一個大屏幕怎麼也比光禿禿的收音機更好看,更高檔次。

拋開山寨廠導航機的問題,雖然車載導航最大的問題就是不能像手機那樣更新迅速,而且更新還得跑回4S店更新有可能要收錢以外,相比手機導航,車載導航信號更為穩定,就算在部分較為偏僻地方手機沒信號,這時候航依舊會擔演一個重要的角色。

相比那一個功能少、反應遲鈍、更新慢的車載導航,手機導航更為靈活。你只需要用一個手機架,就能把手機固定在一個你喜歡的位置,成為導航的角色。另外對於手機這一種聯網的產物來說,導航是可以進行實時更新,並且能夠讓車主知道前方路況。

現在很多比較年輕的司機朋友都喜歡開車就開着導航,然而對於很大一部分老司機來說,實際上對於這一種軟件是持有一種保留的意見。最關鍵的一點就是他們總是認為導航總是繞遠路。又或者去同一個目的地,導航所指路線跟自己預想的不一樣,於是認為導航不準確,實際上雖然導航有可能帶你走遠了幾公里,卻能準確帶你到達目的地。但如果你對道路不熟悉,你卻有可能要多跑上幾十公里,又浪費更多的時間,這就划算了?

回到正題,為了能夠更好地將使用感受告訴大家,已經將App軟件中心下載了那幾款最實用、使用率最高的幾款導航軟件:搜狗地圖、騰訊地圖、高德地圖、百度地圖、蘋果地圖。

實際上,這幾款比較熱門的導航軟件,無論是定位的精準性還是導航時的精準性,都是值得肯定的,這幾位选手最大的區別,莫過於在路線、額外服務、以及使用便利性上。

搜狗地圖

推薦指數:

在使用軟件過程中,你能發現搜狗地圖的界面設計十分簡潔,在主界面上所能用到功能主要有4個,分別是身邊服務、路線查詢、導航功能、軟件服務;在實際體驗中,在導航界面清晰簡潔,而且提供了導航路段的擁堵信息,實際使用感覺良好;另外在軟件服務中,還提供違章查詢、繳款功能、網約車功能、地鐵路線功能、以及霧霾地圖,在功能完善度方面還算齊全。在最常用的導航功能中,在實際使用上跟普通導航並沒太大區別,

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

無論是路口提醒還是測速提醒也都清晰地提示,而且在行駛過程中路面上的路況,譬如塞車,均會提醒。但是在計劃路線的時間上,搜狗地圖所預計的時間都會偏長。

騰訊地圖

推薦指數:

作為騰訊出品的導航軟件在設計方面依舊走簡約風格,但是與上一款導航軟件有別,最常用到的導航地圖功能擺在了最顯眼的位置,使用頻率低的周邊服務等則被放到了發現功能的界面上。另外,在功能服務上,具備違章查詢功能(不具備代繳款功能),網約車服務。在最常使用導航功能,整體布局簡潔、清晰,但是提醒項目卻很多,譬如路上的加油站地點、測速攝像頭都會在路線上表明,另外在攝像頭圖標上還會標明限速標誌,對於我們的行駛時非常有幫助的。另外地圖還提供电子狗功能,實際使用效果一樣好用。

高德地圖

推薦指數:

高德地圖相信是這幾款導航中使用群眾比較多的一款軟件,在實際體驗上,高德地圖無論是實時路況還是時間計算上,都算準確,但是在軟件操作方面,雖然高德地圖都追求簡約,但是在實際操作方面卻發現軟件有種難以下手的感覺,需要一定的時間熟練。但是這款產品的功能非常全面,不但能根據不同交通工具提供了多種路線方案,而且還支持語音導航,能夠即時提示攝像頭、擁堵狀況的路況信息,駕駛時還是比較省心。

百度地圖

推薦指數:

實際上,在體驗各導航軟件時,所使用的導航軟件便是百度地圖,在操作界面方面,認為百度地圖更為簡便,而且每次使用所以提供的路線以及路面上的事實路況均比較準確,在功能性軟件方面也比較齊全,百度地圖也可以說是能媲美高德地圖的一個存在。但是由於使用時間比較長,卻發現百度地圖每月總有幾天的某個時段(譬如上下班時間)是搜索不到衛星信號的,這是扣分項。但是百度地圖卻又一個模式叫做路線雷達模式,在清楚路線的情況下能供更多的路線選擇,並能標出時差。在實用性功能上百度地圖和高德地圖一樣都有語音包選擇,而且語音包豐富度很高,只是語音包並不能完全更換系統聲音,而是在特定的情況譬如超速、前方有測速攝像頭這些特定情況語音包才會起到作用,最後成為一個導航兩種聲音。

蘋果地圖

推薦指數:

作為唯一一款手機原生導航,蘋果地圖可以是做得相當牛逼,整一個界面設計極致簡單,而且操作簡便,而且作為IOS系統下專屬導航,整個界面流暢、功能操作簡便,另外蘋果地圖還提供廣東話語音提示,對於廣東的朋友確實比較體貼。

總結

各款導航都能很好地提供實時交通信息,而提示都會使用綠色、黃色、紅色、深紅色…,只遇過最塞的就是深紅色,至於有沒有更深的紅色,還待探究。而經過多天的使用,得出綠色、黃色路況都是能夠選擇的,而遇到紅色路況也不用左過多的擔心(都是小塞一下),但是碰到深紅色,那就得小心,有可能你會為此塞上一段時間。總結這麼多款導航,其實最好使的,認為還是屬於高德地圖、蘋果地圖兩個,首要原因那就是各大車型都開始普及互聯繫統,先有蘋果的Carplay,再有安卓的AppLink等等一些列的互聯繫統,能夠將特定的導航軟件映射到中控大屏幕上,所以哪一款導航軟件支持汽車中控屏的映射功能,註定受眾更大。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

線性結構隊列以及應用(上)_台中搬家公司

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

隊列Queue:什麼是隊列?

對列是一種有次序的數據集合,其特徵是新數據項的添加總發生在一端(通常稱為“尾rear”端),而現存數據項的移除總發生在另一端(通常稱為“首front”端)

當數據項加入隊列,首先出現在隊尾,隨着隊首數據項的移除,它逐漸接近隊首。新加入的數據項必須在數據集末尾等待,而等待時間最長的數據項則是隊首。這種次序安排的原則稱為:先進先出。

隊列的例子出現在我們日常生活的方方面面:水管,排隊;隊列僅有一個入口和一個出口。不允許數據項直接插入隊中,也不允許從中間移除數據項。

抽象數據類型Queue

抽象數據類型Queue是一個有次序的數據集合,數據項僅添加到“尾”端,而且僅從”首“端移除,Queue具有先進先出的操作次序。

  • 抽象數據類型Queue由如下操作定義:

    Queue():創建一個空隊列對象,返回值為Queue對象;

    enqueue(item):將數據項item添加到隊尾,無返回值;

    dequeue():從隊首移除數據項,返回值為隊首數據項,隊列被修改;

    isEmpty():測試是否空隊列,返回值為bool;

    size():返回隊列中數據項的個數。

    隊列操作 隊列內容 返回值
    q=Queue() [] Queue object
    q.isEmpty() [] True
    q.enqueue(4) [4]
    q.enqueue(“dog”) [“dog”,4]
    q.enqueue(True) [True,”dog”,4]
    q.size() [True,”dog”,4] 3
    q.isEmpty() [True,”dog”,4] False
    q.enqueue(“dog”) [8.4,True,”dog”,4]
    q.dequeue() [8.4,True,”dog”] 4
    q.dequeue() [8.4,True] “dog”
    q.size() [8.4,True] 2

Python實現Queue

採用List來容納Queue的數據項,將List首端作為隊列尾端,List的末端作為隊列的首端,enqueue()複雜度為O(n),dequeue()複雜度為O(1)

class Queue:
    def __init__(self):
        self.items = []
        
    def isEmpty(self):
        return self.items == []
    
    def enqueue(self, item):
        self.items.insert(0, item)
        
    def dequeue(self):
        return self.items.pop()
    
    def size(self):
        return len(self.items)

注意:首尾倒過來的實現,複雜度也倒過來。

隊列的應用:約瑟夫問題

傳說猶太人反叛羅馬人,落到困境,約瑟夫和39人決定殉難,坐成一圈,報數1-7,報到7的人由旁邊殺死,結果略瑟夫給自己安排了個位置,最後活了下來……

模擬程序採用隊列來存放所有參加遊戲的人名,按照報數的方向從隊首排到隊尾,模擬遊戲開始,只需要將隊首的人出隊,隨機再到對尾入隊,算是一次報數完成,反覆n次后,將隊首的人移除,不再入隊,如此反覆,知道隊列中剩下一人。

def JosephQuestion(namelist,num):
    simqueue = Queue()
    for name in namelist:
        simqueue.enqueue(name)

    while simqueue.size() > 1:
        for i in range(num):
            simqueue.enqueue(simqueue.dequeue())  # 一次傳遞

        simqueue.dequeue()   # 處死隊首
    
    return simqueue.dequeue()

隊列的應用:打印任務

有如下場景:多人共享一台打印機,採取“先到先服務”的策略來執行打印任務,在這種設定下,一個首要問題就是:這種打印作業系統的容量有多大?在能夠接受的等待時間內,系統能夠容納多少用戶以多高頻率提交到少打印任務?

一個具體的實例配置如下:

一個實驗室,在任意的一個小時內,大約有10名學生在場,這一個小時中,每個人發起2次左右的打印,每次1-20頁。打印機的性能是:以草稿模式打印的話,每分鐘10頁,以正常模式打印的話,打印質量好,但是速度下降,每分鐘只能打印五頁。

問題:怎麼設定打印機的模式,讓大家都不會等太久的前提下,盡量提高打印質量?這是一個典型的決策支持問題,但無法通過規則直接計算,我們要用一段程序來模擬這種打印任務場景,然後對程序運行結果進行分析,以支持對打印機模式設定的決策。

如何對問題建模:

首先對問題進行抽象,確定相關的對象和過程。拋棄那些對問題實質沒有關係的學生性別,年齡,打印機型號,打印內容,智障大小等等眾多細節。

對象:打印任務,打印隊列,打印機。

打印任務的屬性:提交事件,打印頁數

打印隊列的屬性:具有“先進先出”性質的打印任務隊列

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

打印機的屬性:打印速度,是否忙

過程:生成和提交打印任務

確定生成概率:實例為每小時會有10個學生提交的20個作業,這樣概率是每180秒會有1個作業生成並提交,概率為每秒1/180。

確定打印頁數:實例是1-20頁,那麼就是1-20頁之間概率相同。

過程:實施打印

當前的打印作業:正在打印的作業

打印結束倒計時:新作業開始打印時開始倒計時,返回0表示打印完畢,可以處理下一個作業。

模擬時間:

統一的時間框架:以最小單位(秒)均勻流逝的時間,設定結束時間

同步所有過程:在一個時間單位里,對生成打印任務和實施打印兩個過程各處理一次

打印任務問題:流程模擬

創建打印隊列對象

時間按照秒的單位流逝

按照概率生成打印作業,加入打印隊列

如果打印機空閑,且隊列不空,則取出隊首作業打印,記錄此作業等待時間

如果打印機忙,則按照打印速度進行1秒打印

如果當前作業打印完成,則打印機進入空閑

時間用盡,開始統計平均等待時間

作業的等待時間

生成作業時,記錄生成的時間戳

開始打印時,當前時間減去生成時間即可

作業的打印時間

生成作業時,記錄作業的頁數

開始打印時,頁數除以打印速度即可

import random

class Printer:
    def __init__(self, ppm):
        self.pagerate = ppm  # 打印速度
        self.currentTask = None  # 打印任務
        self.timeRemaining = 0  # 任務倒計時

    def tick(self):  # 打印1秒
        if self.currentTask != None:
            self.timeRemaining -= 1
            if self.timeRemaining <= 0:
                self.currentTask = None

    def busy(self):  # 打印機忙
        if self.currentTask != None:
            return True
        else:
            return False

    def startNext(self, newtask):  # 打印新作業
        self.currentTask = newtask
        self.timeRemaining = newtask.getPages() * 60 / self.pagerate


class Task:
    def __init__(self, time):
        self.timestamp = time  # 生成時間戳
        self.pages = random.randrange(1, 21)  # 打印頁數

    def getStamp(self):
        return self.timestamp

    def getPages(self):
        return self.pages

    def waitTime(self, currenttime):
        return currenttime - self.timestamp  # 等待時間


def newPrintTask():
    num = random.randrange(1, 181)
    if num == 180:
        return True
    else:
        return False

def simulation(numSeconds,  pagesPerMinute): # 模擬
    labprinter = Printer(pagesPerMinute)
    printQueue = Queue()
    waittingtimes = []

    for currentSecond in range(numSeconds):
        if newPrintTask():
            task = Task(currentSecond)
            printQueue.enqueue(task)

        if not labprinter.busy() and not printQueue.isEmpty():
            nexttask = printQueue.dequeue()
            waittingtimes.append(nexttask.waitTime(currentSecond))
            labprinter.startNext(nexttask)

        labprinter.tick()

    averageWait = sum(waittingtimes) / len(waittingtimes)
    print("Average Wait %6.2f secs %3d tasks remaining" %(averageWait,printQueue.size()))

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

一個 json 轉換工具_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

      在前後端的數據協議(主要指httpwebsocket)的問題上,如果前期溝通好了,那麼數據協議上問題會很好解決,前後端商議一種都可以接受的格式即可。但是如果接入的是老系統、第三方系統,或者由於某些奇怪的需求(如為了節省流量,json 數據使用單字母作為key值,或者對某一段數據進行了加密),這些情況下就無法商議,需要在前端做數據轉換,如果不轉換,那麼奔放的數據格式可讀性差,也會造成項目難以維護。

 

      這也正是我在項目種遇到的問題,網上也找了一些方案,要麼過於複雜,要麼有些功能不能很好的支持,於是有了這個工具 class-converter。歡迎提 issue 和 star~~https://github.com/zquancai/class-converter

 

下面我們用例子來說明下:

面對如下的Server返回的一個用戶user數據:

{
    "i": 1234,
    "n": "name",
    "a": "1a2b3c4d5e6f7a8b"
}

或者這個樣的: 

{
    "user_id": 1234,
    "user_name": "name",
    "u_avatar": "1a2b3c4d5e6f7a8b"
}

數據里的 avatar 字段在使用時,可能需要拼接成一個 url,例如 https://xxx.cdn.com/1a2b3c4d5e6f7a8b.png

當然可以直接這麼做:

const json = {
    "i": 1234,
    "n": "name",
    "a": "1a2b3c4d5e6f7a8b",
};
const data = {};
const keyMap = {
    i: 'id',
    n: 'name',
    a: 'avatar',
}
Object.entries(json).forEach(([key, value]) => {
    data[keyMap[key]] = value;
});
// data = { id: 1234, name: 'name', avatar: '1a2b3c4d5e6f7a8b' }

然後我們進一步就可以把這個抽象成一個方法,像下面這個樣:

const jsonConverter = (json, keyMap) => {
    const data = {};
    Object.entries(json).forEach(([key, value]) => {
        data[keyMap[key]] = value;
    });
    return data;
}

如果這個數據擴展了,添加了教育信息,user 數據結構看起來這個樣:

{
    "i": 1234,
    "n": "name",
    "a": "1a2b3c4d5e6f7a8b",
    "edu": {
        "u": "South China Normal University",
        "ea": 1
    }
}

此時的 jsonConverter 方法已經無法正確轉換 edu 字段的數據,需要做一些修改:

const json = {
    "i": 1234,
    "n": "name",
    "a": "1a2b3c4d5e6f7a8b",
    "edu": {
        "u": "South China Normal University",
        "ea": 1
    }
};
const data = {};
const keyMap = {
    i: 'id',
    n: 'name',
    a: 'avatar',
    edu: {
        key: 'education',
        keyMap: {
            u: 'universityName',
            ea: 'attainment'
        }
    },
}

隨着數據複雜度的上升,keyMap 數據結構會變成一個臃腫的配置文件,此外 jsonConverter 方法會越來越複雜,以至於後面同樣難以維護。但是轉換后的數據格式,對於項目來說,數據的可讀性是很高的。所以,這個轉換必須做,但是方式可以更優雅一點。

寫這個工具的初衷也是為了更優雅的進行數據轉換。

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

 

工具用法

還是上面的例子(這裏使用typescript寫法):

import { toClass, property } from 'class-converter';
// 待解析的數據
const json = {
    "i": 1234,
    "n": "name",
    "a": "1a2b3c4d5e6f7a8b",
};
class User {
    @property('i')
    id: number;
    
    @property('n')
    name: string;
    
    @property('a')
    avatar: string;
}
const userIns = toClass(json, User);

你可以輕而易舉的獲得下面的數據:

// userIns 是 User 的一個實例
const userIns = {
    id: 1234,
    name: 'name',
    avatar: '1a2b3c4d5e6f7a8b',
}
userIns instanceof User // true

Json 類既是文檔又是類似於上文說的與keyMap類似的配置文件,並且可以反向使用。

import { toPlain } from 'class-converter';
const user = toPlain(userIns, User);
// user 數據結構
{
    i: 1234,
    n: 'name',
    a: '1a2b3c4d5e6f7a8b',
};

  

這是一個最簡單的例子,我們來一個複雜的數據結構:

{
  "i": 10000,
  "n": "name",
  "user": {
    "i": 20000,
    "n": "name1",
    "email": "zqczqc",
    // {"i":1111,"n":"department"}
    "d": "eyJpIjoxMTExLCJuIjoiZGVwYXJ0bWVudCJ9",
    "edu": [
      {
        "i": 1111,
        "sn": "szzx"
      },
      {
        "i": 2222,
        "sn": "scnu"
      },
      {
         "i": 3333
      }
    ]
  }
}

這是後端返回的一個叫package的json對象,字段意義在文檔中這麼解釋:

  • i:package 的 id
  • n:package 的名字
  • user:package 的所有者,一個用戶
    • i:用戶 id
    • n:用戶名稱
    • email:用戶email,但是只有郵箱前綴
    • d:用戶的所在部門,使用了base64編碼了一個json字符串
      • i:部門 id
      • n:部門名稱
    • edu:用戶的教育信息,數組格式
      • i:學校 id
      • sn:學校名稱

我們的期望是將這一段數據解析成,不看文檔也能讀懂的一個json對象,首先我們經過分析得出上面一共有4類實體對象:package、用戶信息、部門信息、教育信息。

下面是代碼實現:

import {
    toClass, property, array, defaultVal,
    beforeDeserialize, deserialize, optional
} from 'class-converter';
// 教育信息
class Education {
    @property('i')
    id: number;
    
    // 提供一個默認值
    @defaultVal('unknow')
    @prperty('sn')
    schoolName: string;
}
// 部門信息
class Department {
    @property('i')
    id: number;
    
    @prperty('n')
    name: string;
}
// 用戶信息
class User {
  @property('i')
  id: number;
  @property('n')
  name: string;
  
  // 保留一份郵箱前綴數據
  @optional()
  @property()
  emailPrefix: string;
  
  @optional()
  // 這裏希望自動把後綴加上去
  @deserialize(val => `${val}@xxx.com`)
  @property()
  email: string;
  
  @beforeDeserialize(val => JSON.parse(atob(val)))
  @typed(Department)
  @property('d')
  department: Department;
  
  @array()
  @typed(Education)
  @property('edu')
  educations: Education[];
}
// package
class Package {
  @property('i')
  id: number;
  
  @property('n')
  name: string;
  
  @property('user', User)
  owner: User;
} 

數據已經定義完畢,這時只要我們執行toClass方法就可以得到我們想要的數據格式:

{
  id: 10000,
  name: 'name',
  owner: {
    id: 20000,
    name: 'name1',
    emailPrefix: 'zqczqc',
    email: "zqczqc@xxx.com",
    department: {
        id: 1111,
        name: 'department'
    },
    educations: [
      {
        id: 1111,
        schoolName: 'szzx'
      },
      {
        id: 2222,
        schoolName: 'scnu'
      },
      {
        id: 3333,
        schoolName: 'unknow'
      }
    ]
  }
}

上面這一份數據,相比後端返回的數據格式,可讀性大大提升。這裏的用法出現了@deserialize@beforeDeserialize@yped的裝飾器,這裏對這幾個裝飾器是管道方式調用的(前一個的輸出一個的輸入),這裏做一個解釋:

  • beforeDeserialize 第一個參數可以最早拿到當前屬性值,這裏可以做一些解碼操作
  • typed這個是轉換的類型,入參是一個類,相當於自動調用toClass,並且調動時的第一個參數是beforeDeserialize的返回值或者當前屬性值(如果沒有@beforeDeserialize裝飾器)。如果使用了@array裝飾器,則會對每一項數組元素都執行這個轉換
  • deserialize這個裝飾器是最後執行的,第一個參數是beforeDeserialize返回值,@typed返回值,或者當前屬性值(如果前面兩個裝飾器都沒設置的話)。在這個裝飾器里可以做一些數據訂正的操作

這三個裝飾器是在執行toClass時才會調用的,同樣的,當調用toPlain時也會有對應的裝飾器@serialize@fterSerialize,結合@typed進行一個相反的過程。下面將這兩個轉換過程的流程繪製出來。

調用 toClass的過程:

調用 toPlain的過程是調用 toClass的逆過程,但是有些許不一樣,有一個注意點就是:在調用 toClass時允許出現一對多的情況,就是一個屬性可以派生出多個屬性,所以調用調用 toPlain時需要使用 @serializeTarget來標記使用哪一個值作為逆過程的原始值,具體用法可以參考文檔。

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

Netty學習筆記(番外篇) – ChannelHandler、ChannelPipeline和ChannelHandlerContext的聯繫_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

這一篇是 ChannelHandler 和 ChannelPipeline 的番外篇,主要從源碼的角度來學習 ChannelHandler、ChannelHandler 和 ChannelPipeline 相互之間是如何建立聯繫和運行的。

一、添加 ChannelHandler

從上一篇的 demo 中可以看到在初始化 Server 和 Client 的時候,都會通過 ChannelPipeline 的 addLast 方法將 ChannelHandler 添加進去

// Server.java

// 部分代碼片段
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
serverBootstrap.group(group)
        .channel(NioServerSocketChannel.class)Channel
        .localAddress(new InetSocketAddress("localhost", 9999))
        .childHandler(new ChannelInitializer<SocketChannel>() {
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                // 添加ChannelHandler
                socketChannel.pipeline().addLast(new OneChannelOutBoundHandler());

                socketChannel.pipeline().addLast(new OneChannelInBoundHandler());
                socketChannel.pipeline().addLast(new TwoChannelInBoundHandler());
            }
        });

在上面的代碼片段中,socketChannel.pipeline()方法返回的是一個類型是 DefaultChannelPipeline 的實例,DefaultChannelPipeline 實現了 ChannelPipeline 接口

DefaultChannelPipeline 的 addLast 方法實現如下:

// DefaultChannelPipeline.java

@Override
public final ChannelPipeline addLast(ChannelHandler... handlers) {
    return addLast(null, handlers);
}

@Override
public final ChannelPipeline addLast(EventExecutorGroup executor, ChannelHandler... handlers) {
    ObjectUtil.checkNotNull(handlers, "handlers");

    for (ChannelHandler h: handlers) {
        if (h == null) {
            break;
        }
        addLast(executor, null, h);
    }

    return this;
}

經過一系列重載方法調用,最終進入到下面的 addLast 方法

// DefaultChannelPipeline.java

@Override
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    final AbstractChannelHandlerContext newCtx;
    synchronized (this) {
        checkMultiplicity(handler);

        newCtx = newContext(group, filterName(name, handler), handler);

        addLast0(newCtx);

        // If the registered is false it means that the channel was not registered on an eventLoop yet.
        // In this case we add the context to the pipeline and add a task that will call
        // ChannelHandler.handlerAdded(...) once the channel is registered.
        if (!registered) {
            newCtx.setAddPending();
            callHandlerCallbackLater(newCtx, true);
            return this;
        }

        EventExecutor executor = newCtx.executor();
        if (!executor.inEventLoop()) {
            callHandlerAddedInEventLoop(newCtx, executor);
            return this;
        }
    }
    callHandlerAdded0(newCtx);
    return this;
}

在這個方法實現中,利用傳進來的 ChannelHandler 在 newContext 創建了一個 AbstractChannelHandlerContext 對象。newContext 方法實現如下:

// DefaultChannelPipeline.java

private AbstractChannelHandlerContext newContext(EventExecutorGroup group, String name, ChannelHandler handler) {
    return new DefaultChannelHandlerContext(this, childExecutor(group), name, handler);
}

這裏創建並返回了一個類型為 DefaultChannelHandlerContext 的對象。從傳入的參數可以看到,在這裏將 ChannelHandlerContext、ChannelPipeline(this)和 ChannelHandler 三者建立了關係。
最後再看看 addLast0 方法實現:

// DefaultChannelPipeline.java

private void addLast0(AbstractChannelHandlerContext newCtx) {
    AbstractChannelHandlerContext prev = tail.prev;
    newCtx.prev = prev;
    newCtx.next = tail;
    prev.next = newCtx;
    tail.prev = newCtx;
}

這裏出現了 AbstractChannelHandlerContext 的兩個屬性 prev 和 next,而 DefaultChannelPipeline 有一個屬性 tail。從實現邏輯上看起來像是建立了一個雙向鏈表的結構。下面的代碼片段是關於 tail 和另一個相關屬性 head:

// DefaultChannelPipeline.java

public class DefaultChannelPipeline implements ChannelPipeline {
    final AbstractChannelHandlerContext head;
    final AbstractChannelHandlerContext tail;

    // ......
    protected DefaultChannelPipeline(Channel channel) {
        // ......

        tail = new TailContext(this);
        head = new HeadContext(this);

        head.next = tail;
        tail.prev = head;
    }

    // ......
}

// HeaderContext.java
final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler {
    // ......

    @Override
    public ChannelHandler handler() {
        return this;
    }

    //......
}

// TailContext.java
final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
    // ......

    @Override
    public ChannelHandler handler() {
        return this;
    }

    // ......
}

DefaultChannelPipeline 內部維護了兩個 AbstractChannelHandlerContext 類型的屬性 head、tail,而這兩個屬性又都實現了 ChannelHandler 的子接口。構造方法里將這兩個屬性維護成了一個雙向鏈表。結合上面的 addLast0 方法實現,可以知道在添加 ChannelHandler 的時候,其實是在對 ChannelPipeline 內部維護的雙向鏈表做插入操作。
下面是 ChannelHandlerContext 相關類的結構

所以,對 ChannelPipeline 做 add 操作添加 ChannelHandler 后,內部結構大體是這樣的:

所有的 ChannelHandlerContext 組成了一個雙向鏈表,頭部是 HeadContext,尾部是 TailContext,因為它們都實現了 ChannelHandler 接口,所以它們內部的 Handler 也是自己。每次添加一個 ChannelHandler,將會新創建一個 DefaultChannelHandler 關聯,並按照一定的順序插入到鏈表中。
在 AbstractChannelHandlerContext 類里有一個屬性 executionMask,在構造方法初始化時會對它進行賦值

// AbstractChannelHandlerContext.java

// 省略部分代碼

AbstractChannelHandlerContext(DefaultChannelPipeline pipeline, EventExecutor executor,
                                String name, Class<? extends ChannelHandler> handlerClass) {
    this.name = ObjectUtil.checkNotNull(name, "name");
    this.pipeline = pipeline;
    this.executor = executor;
    this.executionMask = mask(handlerClass);
    // Its ordered if its driven by the EventLoop or the given Executor is an instanceof OrderedEventExecutor.
    ordered = executor == null || executor instanceof OrderedEventExecutor;
}

// 省略部分代碼

mask 是一個靜態方法,來自於 ChannelHandlerMask 類

// ChannelHandlerMask.java

// 省略部分代碼

/**
* Return the {@code executionMask}.
*/
static int mask(Class<? extends ChannelHandler> clazz) {
    // Try to obtain the mask from the cache first. If this fails calculate it and put it in the cache for fast
    // lookup in the future.
    Map<Class<? extends ChannelHandler>, Integer> cache = MASKS.get();
    Integer mask = cache.get(clazz);
    if (mask == null) {
        mask = mask0(clazz);
        cache.put(clazz, mask);
    }
    return mask;
}

/**
* Calculate the {@code executionMask}.
*/
private static int mask0(Class<? extends ChannelHandler> handlerType) {
    int mask = MASK_EXCEPTION_CAUGHT;
    try {
        if (ChannelInboundHandler.class.isAssignableFrom(handlerType)) {
            mask |= MASK_ALL_INBOUND;

            if (isSkippable(handlerType, "channelRegistered", ChannelHandlerContext.class)) {
                mask &= ~MASK_CHANNEL_REGISTERED;
            }
            if (isSkippable(handlerType, "channelUnregistered", ChannelHandlerContext.class)) {
                mask &= ~MASK_CHANNEL_UNREGISTERED;
            }
            if (isSkippable(handlerType, "channelActive", ChannelHandlerContext.class)) {
                mask &= ~MASK_CHANNEL_ACTIVE;
            }
            if (isSkippable(handlerType, "channelInactive", ChannelHandlerContext.class)) {
                mask &= ~MASK_CHANNEL_INACTIVE;
            }
            if (isSkippable(handlerType, "channelRead", ChannelHandlerContext.class, Object.class)) {
                mask &= ~MASK_CHANNEL_READ;
            }
            if (isSkippable(handlerType, "channelReadComplete", ChannelHandlerContext.class)) {
                mask &= ~MASK_CHANNEL_READ_COMPLETE;
            }
            if (isSkippable(handlerType, "channelWritabilityChanged", ChannelHandlerContext.class)) {
                mask &= ~MASK_CHANNEL_WRITABILITY_CHANGED;
            }
            if (isSkippable(handlerType, "userEventTriggered", ChannelHandlerContext.class, Object.class)) {
                mask &= ~MASK_USER_EVENT_TRIGGERED;
            }
        }

        if (ChannelOutboundHandler.class.isAssignableFrom(handlerType)) {
            mask |= MASK_ALL_OUTBOUND;

            if (isSkippable(handlerType, "bind", ChannelHandlerContext.class,
                    SocketAddress.class, ChannelPromise.class)) {
                mask &= ~MASK_BIND;
            }
            if (isSkippable(handlerType, "connect", ChannelHandlerContext.class, SocketAddress.class,
                    SocketAddress.class, ChannelPromise.class)) {
                mask &= ~MASK_CONNECT;
            }
            if (isSkippable(handlerType, "disconnect", ChannelHandlerContext.class, ChannelPromise.class)) {
                mask &= ~MASK_DISCONNECT;
            }
            if (isSkippable(handlerType, "close", ChannelHandlerContext.class, ChannelPromise.class)) {
                mask &= ~MASK_CLOSE;
            }
            if (isSkippable(handlerType, "deregister", ChannelHandlerContext.class, ChannelPromise.class)) {
                mask &= ~MASK_DEREGISTER;
            }
            if (isSkippable(handlerType, "read", ChannelHandlerContext.class)) {
                mask &= ~MASK_READ;
            }
            if (isSkippable(handlerType, "write", ChannelHandlerContext.class,
                    Object.class, ChannelPromise.class)) {
                mask &= ~MASK_WRITE;
            }
            if (isSkippable(handlerType, "flush", ChannelHandlerContext.class)) {
                mask &= ~MASK_FLUSH;
            }
        }

        if (isSkippable(handlerType, "exceptionCaught", ChannelHandlerContext.class, Throwable.class)) {
            mask &= ~MASK_EXCEPTION_CAUGHT;
        }
    } catch (Exception e) {
        // Should never reach here.
        PlatformDependent.throwException(e);
    }

    return mask;
}

@SuppressWarnings("rawtypes")
private static boolean isSkippable(
        final Class<?> handlerType, final String methodName, final Class<?>... paramTypes) throws Exception {
    return AccessController.doPrivileged(new PrivilegedExceptionAction<Boolean>() {
        @Override
        public Boolean run() throws Exception {
            Method m;
            try {
                m = handlerType.getMethod(methodName, paramTypes);
            } catch (NoSuchMethodException e) {
                if (logger.isDebugEnabled()) {
                    logger.debug(
                        "Class {} missing method {}, assume we can not skip execution", handlerType, methodName, e);
                }
                return false;
            }
            return m != null && m.isAnnotationPresent(Skip.class);
        }
    });
}

// 省略部分代碼

以上代碼實現邏輯是這樣的:當創建一個 ChannelHandlerContext 時,會與一個 ChannelHandler 綁定,同時會將傳遞進來的 ChannelHandler 進行解析,解析當前 ChannelHandler 支持哪些回調方法,並通過位運算得到一個結果保存在 ChannelHandlerContext 的 executionMask 屬性里。注意 m.isAnnotationPresent(Skip.class)這裏,ChannelHandler 的基類 ChannelInboundHandlerAdapter 和 ChannelOutboundHandlerAdapter 里的回調方法上都有@Skip 註解,當繼承了這兩個類並重寫了某個回調方法后,這個方法上的註解就會被覆蓋掉,解析時就會被認為當前 ChannelHandler 支持這個回調方法。
下面是每個回調方法對應的掩碼

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

// ChannelHandlerMask.java

final class ChannelHandlerMask {
    // Using to mask which methods must be called for a ChannelHandler.
    static final int MASK_EXCEPTION_CAUGHT = 1;
    static final int MASK_CHANNEL_REGISTERED = 1 << 1;
    static final int MASK_CHANNEL_UNREGISTERED = 1 << 2;
    static final int MASK_CHANNEL_ACTIVE = 1 << 3;
    static final int MASK_CHANNEL_INACTIVE = 1 << 4;
    static final int MASK_CHANNEL_READ = 1 << 5;
    static final int MASK_CHANNEL_READ_COMPLETE = 1 << 6;
    static final int MASK_USER_EVENT_TRIGGERED = 1 << 7;
    static final int MASK_CHANNEL_WRITABILITY_CHANGED = 1 << 8;
    static final int MASK_BIND = 1 << 9;
    static final int MASK_CONNECT = 1 << 10;
    static final int MASK_DISCONNECT = 1 << 11;
    static final int MASK_CLOSE = 1 << 12;
    static final int MASK_DEREGISTER = 1 << 13;
    static final int MASK_READ = 1 << 14;
    static final int MASK_WRITE = 1 << 15;
    static final int MASK_FLUSH = 1 << 16;

    static final int MASK_ONLY_INBOUND =  MASK_CHANNEL_REGISTERED |
            MASK_CHANNEL_UNREGISTERED | MASK_CHANNEL_ACTIVE | MASK_CHANNEL_INACTIVE | MASK_CHANNEL_READ |
            MASK_CHANNEL_READ_COMPLETE | MASK_USER_EVENT_TRIGGERED | MASK_CHANNEL_WRITABILITY_CHANGED;
    private static final int MASK_ALL_INBOUND = MASK_EXCEPTION_CAUGHT | MASK_ONLY_INBOUND;
    static final int MASK_ONLY_OUTBOUND =  MASK_BIND | MASK_CONNECT | MASK_DISCONNECT |
            MASK_CLOSE | MASK_DEREGISTER | MASK_READ | MASK_WRITE | MASK_FLUSH;
    private static final int MASK_ALL_OUTBOUND = MASK_EXCEPTION_CAUGHT | MASK_ONLY_OUTBOUND;
}

二、ChannelHandler 處理消息

我們以消息讀取和寫入為例,來看看在 ChannelPipeline 里的各個 ChannelHandler 是如何按照順序處理消息和事件的。

讀取消息

當 Channel 讀取到消息后,會在以下地方調用 ChannelPipeline 的 fireChannelRead 方法:

// AbstractNioMessageClient.java

private final class NioMessageUnsafe extends AbstractNioUnsafe {

    // 省略代碼

    @Override
    public void read() {
        // ......

        for (int i = 0; i < size; i ++) {
            readPending = false;
            pipeline.fireChannelRead(readBuf.get(i));
        }

        // ......
    }

    // 省略代碼
}

// DefaultChannelPipeline.java

// 省略代碼

@Override
public final ChannelPipeline fireChannelRead(Object msg) {
    AbstractChannelHandlerContext.invokeChannelRead(head, msg);
    return this;
}

// 省略代碼

可以看到,通過 AbstractChannelHandlerContext 的 invokeChannelRead 方法,傳遞 head,從頭部開始觸發讀取事件。

// AbstractChannelHandlerContext.java

// 省略代碼

static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
    final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelRead(m);
    } else {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                next.invokeChannelRead(m);
            }
        });
    }
}

private void invokeChannelRead(Object msg) {
    if (invokeHandler()) {
        try {
            ((ChannelInboundHandler) handler()).channelRead(this, msg);
        } catch (Throwable t) {
            invokeExceptionCaught(t);
        }
    } else {
        fireChannelRead(msg);
    }
}

/**
    * Makes best possible effort to detect if {@link ChannelHandler#handlerAdded(ChannelHandlerContext)} was called
    * yet. If not return {@code false} and if called or could not detect return {@code true}.
    *
    * If this method returns {@code false} we will not invoke the {@link ChannelHandler} but just forward the event.
    * This is needed as {@link DefaultChannelPipeline} may already put the {@link ChannelHandler} in the linked-list
    * but not called {@link ChannelHandler#handlerAdded(ChannelHandlerContext)}.
    */
private boolean invokeHandler() {
    // Store in local variable to reduce volatile reads.
    int handlerState = this.handlerState;
    return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}

// 省略代碼

在這裏通過 invokeHandler 方法對當前 ChannelHandler 進行狀態檢查,通過了就將調用當前 ChannelHandler 的 channelRead 方法,沒有通過將調用 fireChannelRead 方法將事件傳遞到下一個 ChannelHandler 上。而 head 的類型是 HeadContext,本身也實現了 ChannelInBoundHandler 接口,所以這裏調用的是 HeadContext 的 channelRead 方法。

// DefaultChannelPipeline.java

final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ctx.fireChannelRead(msg);
    }
}

這裏對消息沒有做任何處理,直接將讀取消息傳遞下去。接下來看看 ChannelHandlerContext 的 fireChannelRead 做了什麼

// AbstractChannelHandlerContext.java

@Override
public ChannelHandlerContext fireChannelRead(final Object msg) {
    invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
    return this;
}

private AbstractChannelHandlerContext findContextInbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    EventExecutor currentExecutor = executor();
    do {
        ctx = ctx.next;
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_INBOUND));
    return ctx;
}

private static boolean skipContext(
        AbstractChannelHandlerContext ctx, EventExecutor currentExecutor, int mask, int onlyMask) {
    // Ensure we correctly handle MASK_EXCEPTION_CAUGHT which is not included in the MASK_EXCEPTION_CAUGHT
    return (ctx.executionMask & (onlyMask | mask)) == 0 ||
            // We can only skip if the EventExecutor is the same as otherwise we need to ensure we offload
            // everything to preserve ordering.
            //
            // See https://github.com/netty/netty/issues/10067
            (ctx.executor() == currentExecutor && (ctx.executionMask & mask) == 0);
}

這裏實現的邏輯是這樣的:在雙向鏈表中,從當前 ChannelHandlerContext 節點向後尋找,直到找到匹配 MASK_CHANNEL_READ 這個掩碼的 ChannelHandlerContext。從上面的章節里可以直到 ChannelHandlerContext 的屬性里保存了當前 ChannelHandler 支持(重寫)的所有方法掩碼的位運算值,通過位運算的結果來找到實現了對應方法的最近的 ChannelHandlerContext。
鏈表最後一個節點是 TailContext

// DefaultChannelPipeline.java

final class TailContext extends AbstractChannelHandlerContext implements ChannelInboundHandler {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        onUnhandledInboundMessage(ctx, msg);
    }

    /**
     * Called once a message hit the end of the {@link ChannelPipeline} without been handled by the user
     * in {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)}. This method is responsible
     * to call {@link ReferenceCountUtil#release(Object)} on the given msg at some point.
     */
    protected void onUnhandledInboundMessage(ChannelHandlerContext ctx, Object msg) {
        onUnhandledInboundMessage(msg);
        if (logger.isDebugEnabled()) {
            logger.debug("Discarded message pipeline : {}. Channel : {}.",
                         ctx.pipeline().names(), ctx.channel());
        }
    }

    /**
     * Called once a message hit the end of the {@link ChannelPipeline} without been handled by the user
     * in {@link ChannelInboundHandler#channelRead(ChannelHandlerContext, Object)}. This method is responsible
     * to call {@link ReferenceCountUtil#release(Object)} on the given msg at some point.
     */
    protected void onUnhandledInboundMessage(Object msg) {
        try {
            logger.debug(
                    "Discarded inbound message {} that reached at the tail of the pipeline. " +
                            "Please check your pipeline configuration.", msg);
        } finally {
            ReferenceCountUtil.release(msg);
        }
    }
}

可以看到,tail 節點的 channelRead 方法沒有將事件繼續傳遞下去,只是釋放了 msg。

寫入消息

我們通過 OneChannelInBoundHandler 的 channelReadComplete 方法里的 ctx.write 方法來看

// AbstractChannelHandlerContext.java

// 省略代碼

@Override
public ChannelFuture write(Object msg) {
    return write(msg, newPromise());
}

@Override
public ChannelFuture write(final Object msg, final ChannelPromise promise) {
    write(msg, false, promise);

    return promise;
}

private void write(Object msg, boolean flush, ChannelPromise promise) {
    ObjectUtil.checkNotNull(msg, "msg");
    try {
        if (isNotValidPromise(promise, true)) {
            ReferenceCountUtil.release(msg);
            // cancelled
            return;
        }
    } catch (RuntimeException e) {
        ReferenceCountUtil.release(msg);
        throw e;
    }

    final AbstractChannelHandlerContext next = findContextOutbound(flush ?
            (MASK_WRITE | MASK_FLUSH) : MASK_WRITE);
    final Object m = pipeline.touch(msg, next);
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        if (flush) {
            next.invokeWriteAndFlush(m, promise);
        } else {
            next.invokeWrite(m, promise);
        }
    } else {
        final WriteTask task = WriteTask.newInstance(next, m, promise, flush);
        if (!safeExecute(executor, task, promise, m, !flush)) {
            // We failed to submit the WriteTask. We need to cancel it so we decrement the pending bytes
            // and put it back in the Recycler for re-use later.
            //
            // See https://github.com/netty/netty/issues/8343.
            task.cancel();
        }
    }
}

private AbstractChannelHandlerContext findContextOutbound(int mask) {
    AbstractChannelHandlerContext ctx = this;
    EventExecutor currentExecutor = executor();
    do {
        ctx = ctx.prev;
    } while (skipContext(ctx, currentExecutor, mask, MASK_ONLY_OUTBOUND));
    return ctx;
}

// 省略代碼

通過調用一系列重載的 write 方法后,通過 findContextOutbound 方法在雙向鏈表裡向前尋找最近的實現了 write 或 writeAndFlush 方法的 ChannelHandlerContext,調用它的 invokeWrite 或 invokeWriteAndFlush 方法。

// AbstractChannelHandlerContext.java

// 省略代碼

void invokeWrite(Object msg, ChannelPromise promise) {
    if (invokeHandler()) {
        invokeWrite0(msg, promise);
    } else {
        write(msg, promise);
    }
}

private void invokeWrite0(Object msg, ChannelPromise promise) {
    try {
        ((ChannelOutboundHandler) handler()).write(this, msg, promise);
    } catch (Throwable t) {
        notifyOutboundHandlerException(t, promise);
    }
}

// 省略代碼

同理於讀取消息,這裏經過 invokeHandler 方法檢查通過後調用找到的 ChannelHandlerContext 的 ChannelHandler,沒有通過檢查,則繼續向前傳遞寫入事件。當寫入消息傳遞到頭部,調用 HeadContext 的 write 方法

// DefaultChannelPipeline.java

final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler {

    private final Unsafe unsafe;

    // 省略代碼

    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
        unsafe.write(msg, promise);
    }

    @Override
    public void flush(ChannelHandlerContext ctx) {
        unsafe.flush();
    }

    // 省略代碼
}

最終通過調用 unsafe 的 write 方法寫入消息。
最後,從上面的實現里可以發現,在將 ChannelHandler 加入到 ChannelPipeline 時,要把 ChannelOutBoundHandler 類型的 ChannelHandler 進來添加在前面,否則在 ChannelInBoundHandler 寫入消息時,在它後面的 ChannelOutBoundHandler 將無法獲取到事件。

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

电子郵件協議及GO發送QQ郵件_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

目錄

  • 一、电子郵件的工作機制
    • 1.1 SMTP
    • 1.2 POP3
    • 1.3 IMAP
  • 二、郵件地址
  • 三、MIME信息
  • 四、使用golang發送qq郵件

一、电子郵件的工作機制

提供电子郵件服務的協議叫做:SMTP(Simple Mail Transfer Protocol)為了能夠高效安全的進行數據的傳輸,SMTP協議底層使用的TCP實現兩端的連接。

早期的电子郵件收發的工作機制如上圖所示。發送端和接收端之間通過SMTP底層的TCP簡歷連接。通過網絡直接將郵件發送到對方的磁盤上。

但是問題也隨之而來:

如果接收方沒有開機,或者開機了但是沒有連接網絡,那麼就不能通過SMTP協議建立連接,這時發送端只能是個隔一段時間后重試,直到接收端開機了,聯網了,發送端才能成功的將郵件發送給接收方。問題很明顯,接收方只要不開機,發送方的郵件就不能發送出去,如果是東方國家和西方國家之間的兩個人各自在各自的白天才開機,那豈不是他們之間的郵件根本不可能發送出去了?

為了解決這個問題,郵件服務器出現了:

這時收發郵件的工作機制就演變成了上圖那樣。發送方 面向 郵件服務器發送郵件,而不管接收方是否開機,是否聯網,接收方通過上線后使用POP3(Post Office Proto-col)從郵件服務器接收郵件。

整個過程中,郵件服務器是不會斷電的。

1.1 SMTP

通過上圖可以看到,SMTP是發送电子郵件時使用的協議。 它底層使用tcp的25號端口。在這個tcp連接上進行控制,應答,以及數據的傳輸。

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

客戶端以文本的方式發送請求,郵件服務器每次回復3位数字作為應答。比如客戶端首次會發送 HELO<domain>表示請求建立連接。正常的話郵件服務器會回復250,表示完成請求命令。

SMTP協議中規定,以’.’最為郵件正文的結束符。當正文前面有一個’.’或者有兩個’.’ 都要進行特殊處理。

SMTP不會校驗發送者,所以我們經常會收到垃圾郵件,據說也會有“POP before SMTP” 和“SMTP認證”機制,來防止冒充發送人。從而減少垃圾郵件的數量。

1.2 POP3

POP服務器也是一台一直處於充電狀態的服務器。

客戶端通過pop3協議從pop服務器上接收發送方發過來的協議,但是在接收之前是需要進行用戶身份驗證的,也就是說,客戶端得將自己的賬號密碼發送到POP服務器,通過驗證后才能取回屬於自己的郵件

POP與SMTP一樣,都是基於TCP連接完成相應的操作的。

1.3 IMAP

IMAP和POP協議一樣都是接收电子郵件時使用的協議。

如果使用IMAP,即使不用將电子郵件下載到本地也可以閱讀。因為IMAP實現了字啊服務端處理MIME類型的數據,所以他能實現當一封电子郵件有10個附件時,它能直接打開其中的某一個。而且在服務端作出已讀/未讀,等狀體的修改。

二、郵件地址

郵件地址通常都是由兩部分組成: 名稱@地址

常見的像 123@qq.com 這種郵件的地址。

123就是名稱,qq.com就是地址。 电子郵件的地址和域名構造相同,後面的com是頂級域名。

現在的电子郵件地址由DNS統一管理。DNS裏面存儲着各個郵件地址,和這個郵件地址作為發送地址時所對應的郵件服務器的域名信息。我們把這種映射關係稱為MX記錄。因為方才說了,對現在的郵件發送機制來說,發送者是將郵件發送到郵件服務器上。那通過查詢DNS中的MX記錄,就能知道xxx@qq.com. xxx@163.com 這種不同的郵件後綴所對應的郵件服務器的域名,通過域名進一步找到這個機器。

三、MIME信息

最初的很長一段時間里,郵件只能發送文本信息。後台能發送的數據類型已經被拓展到了MIME。可以發送諸如gif, video,png,jpg,jpeg,text/plain 等等類型的數據。具體發送啥樣的信息,通過Content-Type定義。

四、使用golang發送qq郵件

實例代碼如下:

package mail

import (
	"strconv"
)
import "gopkg.in/gomail.v2"

func SendMail(mailTo []string, subject string, body string) error {
  
	mailConn := map[string]string{
		"user": "6464xxxx8@qq.com", // 郵件發送者的地址
		"pass": "trsxxxxxxxxxxcd",  // qq郵箱填授權碼,百度一下獲取方式。
		"host": "smtp.qq.com", // 發送將郵件發送給騰訊的smtp郵件服務器
		"port": "465",   // 發送郵件使用的端口
	}
	port, _ := strconv.Atoi(mailConn["port"])
  
	m := gomail.NewMessage()
	m.SetHeader("From", m.FormatAddress(mailConn["user"], "自動化成績查詢"))
	m.SetHeader("To", mailTo...)    //發送給多個用戶
	m.SetHeader("Subject", subject) //設置郵件主題
	 m.SetBody("text/html", body)    //設置郵件正文
  
	d := gomail.NewDialer(mailConn["host"], port, mailConn["user"], mailConn["pass"])
	err := d.DialAndSend(m)
	return err
}

	/*
	發送郵件
	stuEmail:學生的郵箱
	subject:標題
	body:發送的內容
  */
func DoSendMail(stuEmail , subject, body string) (e error) {
	mailTo := []string{stuEmail}
	err := SendMail(mailTo, subject, body)
	if err != nil {
		e = err
		return e
	}
	return nil
}

//func main() {
//	//定義收件人
//	mailTo := []string{
//		"2693xxxx8@qq.com",
//		"196xxxxx30@qq.com",
//	}
//	//郵件主題為"Hello"
//	subject := "Hi 出成績了"
//	// 郵件正文
//	body := "請查收您的新成績"
//
//	err := SendMail(mailTo, subject, body)
//	if err != nil {
//		log.Println(err)
//		fmt.Println("send fail")
//		return
//	}
//	fmt.Println("send successfully")
//}

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

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

拿20萬買車不容易!選對車能有豪華SUV的檔次感!_台中搬家公司

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

這並非是別克自嗨地定個高價錢,而是旗下的產品確實是有一定的豪華調性,譬如如今排在合資SUV銷量榜首上第一位的昂科威。打開昂科威的車門,第一時間定住眼睛的,除了那個包圍感十足的飛翼式中控台以外,就數那些面積巨大的棕色皮革面料。

如今的汽車早已經不是過去達官貴人們才消費得起的商品,對於許多的人來說,花個一年或者兩年的積蓄買一輛車是一件再平常不過的事情。所以,汽車對於彰顯車主身份的作用(通俗來說就是裝逼)已經大不如前。但即便如此,我依然認為同樣的價錢,買到一輛逼格更高的SUV是一件讓人相當愉快的事情,這跟裝逼沒關係(嘴上說說而已),僅僅是滿足了我們對生活品質的追求。今天,我們就來了解一些只需要十來二十萬售價,卻有着40、50萬逼格的SUV車型。

廠商指導價:9.98-18.68萬

作為上汽榮威憋了那麼多年的大招,榮威RX5確實是相當的不簡單。從造型設計上來看,榮威RX5有着各種高端德系車的影子。直觀的像前臉的展翼式中網,線條剛硬、簡潔,大氣如途觀。且LED大燈內部,有精緻的設計結構與中網相融,个中的工藝有着極高的難度。再比如車側上的刀鋒腰線,依靠3.5mm的鈑金工藝營造出了深層次的刀鋒效果,凸顯出豐富的力量感。縱觀這些巧妙但精心的設計,不僅關乎於高昂的成本,還考驗着設計手法的水準,若非高檔豪車,絕不會貿然使用。

在車廂的內部,其有着各種質感細膩的軟性皮革進行包裹。用上汽的話說,“榮威RX5是目前自主品牌SUV里,使用最多軟性材質進行內裝的SUV。”如果說摸着各種細膩的皮革還不能讓你興奮,那中控上那塊面積比Ipad還大的10.4寸全彩液晶觸控屏絕對能夠讓你瞬間高潮了。通過這塊屏幕,你可以把空調、天窗、安全系統等涉及車輛的功能調節全都玩個遍。這種高大上的酷炫體驗,一直是特斯拉的招牌。但如今想想榮威RX5的起售價,才9.98萬,我的天~~~

官方指導價:20.99-34.99萬

說起別克,大家總會說,“不就跟雪佛蘭差不多嘛,

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

有什麼牛逼的呀?!”但事實上,在別克的老家—美國,別克旗下車型的售價往往跟寶馬、奧迪、沃爾沃等豪華品牌看齊。這並非是別克自嗨地定個高價錢,而是旗下的產品確實是有一定的豪華調性,譬如如今排在合資SUV銷量榜首上第一位的昂科威。

打開昂科威的車門,第一時間定住眼睛的,除了那個包圍感十足的飛翼式中控台以外,就數那些面積巨大的棕色皮革面料。這些面料遍布在中控台上、門板上,無論用眼看還是用手摸,你都能夠充分感受到高貴的氣息。當然,設計師還把真皮的最佳搭檔—木紋裝飾面板也一併加入,如此一碰撞完全就是豪氣外露了。雖然如今許多車企也開始使用大量的真皮、木紋飾板,試圖使得自家的汽車更顯高大上。但這30萬以下的價位,也只有昂科威能夠把內飾造型、顏色搭配、面料材質三方面融合到了極致。雖然我不敢說每一個人都能認同這種張揚的美式豪華,但喜歡奔馳寶馬的人,十有八九也會被昂科威所吸引。其次,昂科威對於車廂靜音性的打造上也是極為牛逼。各種隔音玻璃、隔音海綿、隔音墊片等隔音材料,無所不用其極。從實測的情況來看,昂科威是要比不少寶馬、奧迪的隔音更加出色的。從視覺、觸覺、聽覺的表現上,昂科威都對得起豪華這個詞,只是……人家的售價只有寶馬X3的一半。

官方指導價:16.98-30.28萬

好吧,我承認寶沃完全就是個徹頭徹尾的自主品牌(福田汽車),那些德國三大汽車生產商之一、德國斯圖加特總部研發等等全都是忽悠吃瓜群眾的說辭。但我也不得不說,BX7是自主品牌少有的具有高大上特質的SUV。

首先是鑲嵌在BX7中網之上的雙色菱形徽標,讓人一看就產生“恩,這車看起來不簡單”的感覺,我能想的原因是因為這個徽標跟林肯實在是太像。而其整體的車身線條基本以簡煉為主,你說不上它有什麼特別耀眼的地方,但平淡之中仍能夠感受到它的精緻所在,與一眾的德系豪華車有一定的相似性。在鈑金工藝、漆面光澤等細節處理上,也表現出了較高的水平。不過真正讓人產生“這是一輛豪華品牌”的判斷的,還是要歸結於內飾的功勞。用料上,搪塑工藝、真皮皮革、金屬飾條毫不吝嗇地一次性全部用上,這種做法基本就是奔馳的最愛了。整體的中控布局和寶馬相像,但實體按鈕的排列上更加簡潔。可以說,寶沃BX7的這套內飾完美地整合了奔馳以及寶馬的精華。光好看還不夠,配置上同樣是讓人虎軀一震。低配的車型上,便已經擁有無匙啟動、自動駐車、真皮座椅、自動空調等配置。別指望一般人能看出你買的是低配車,這完全就是中高配的規格。

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

北京對發往河北公交線路採取暫停運營措施_台中搬家公司

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

中新網1月28日電 據北京公交集團官方微博消息,為配合河北省疫情防控需要,應河北省三河市、涿州市等15個屬地政府部門的要求,從1月28日起,北京市公交集團運營的28條跨河北省點到點直達線路,採取暫停運營措施。

9條在北京市域內設站點的線路,採取北京市域內區間運營措施。後續北京公交集團將與河北省相關市縣政府交通管理部門保持密切溝通,並做好隨時恢復開通運營的準備。

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

圖片來源:北京公交集團官方微博

本站聲明:網站內容來http://www.societynews.cn/html/wh/fq/,如有侵權,請聯繫我們,我們將及時處理

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

Netty源碼學習系列之2-NioEventLoopGroup的初始化_台中搬家公司

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

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

前言

    NioEventLoopGroup是netty對Reactor線程組這個抽象概念的具體實現,其內部維護了一個EventExecutor數組,而NioEventLoop就是EventExecutor的實現(看名字也可發現,一個是NioEventLoopGroup,一個是NioEventLoop,前者是集合,後者是集合中的元素)。一個NioEventLoop中運行着唯一的一個線程即Reactor線程,這個線程一直執行NioEventLoop的run方法。這個run方法就是netty的核心方法,其重要性可以類比於Spring中的refresh方法。

    下面是從百度上隨便找的一篇netty文章的線程模型圖(詳見文章https://www.cnblogs.com/luoxn28/p/11875340.html),此處引用是為方便在頭腦中產生一個整體印象,結合圖下面的代碼進行各個概念的歸位。圖中綠色的Reactor Thread就是上文說的NioEventLoopGroup,對應下面代碼中的boss變量,負責處理客戶端的連接事件,它其實也是一個池(因為內部維護的是一個數組);藍色的Reactor Thread Pool也是NioEventLoopGroup,對應下面代碼中的worker變量,負責處理客戶端的讀寫事件

 

 

     注:上圖是Reactor多線程模型,而下面的代碼示例是主從多線程模型,區別是只要將代碼boss中的參數2改成1,示例代碼就成了多線程模型,細細品味一下。

 1 public class NettyDemo1 {
 2     // netty服務端的一般性寫法
 3     public static void main(String[] args) {
 4         EventLoopGroup boss = new NioEventLoopGroup(2);
 5         EventLoopGroup worker = new NioEventLoopGroup();
 6         try {
 7             ServerBootstrap bootstrap = new ServerBootstrap();
 8             bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
 9                     .option(ChannelOption.SO_BACKLOG, 100)
10                     .childHandler(new ChannelInitializer<SocketChannel>() {
11                         @Override
12                         protected void initChannel(SocketChannel socketChannel) throws Exception {
13                             ChannelPipeline pipeline = socketChannel.pipeline();
14                             pipeline.addLast(new StringDecoder());
15                             pipeline.addLast(new StringEncoder());
16                             pipeline.addLast(new NettyServerHandler());
17                         }
18                     });
19             ChannelFuture channelFuture = bootstrap.bind(90);
20             channelFuture.channel().closeFuture().sync();
21         } catch (Exception e) {
22             e.printStackTrace();
23         } finally {
24             boss.shutdownGracefully();
25             worker.shutdownGracefully();
26         }
27     }
28 }

    以上部分是博主對netty的一個概括性總結,以將概念和其實現連接起來,方便建立一個初始的總體認識,下面進入EventLoopGroup的初始化。

一、EventLoopGroup初始化

1、NioEventLoopGroup構造器

    順着有參和無參的構造方法進去,發現無參的構造器將線程數賦值0繼續調了有參的構造器,而有參的構造器將線程池executor參數賦值null繼續調重載構造器

1 public NioEventLoopGroup() {
2         this(0);
3     }
1 public NioEventLoopGroup(int nThreads) {
2         this(nThreads, (Executor) null);
3     }
1 public NioEventLoopGroup(int nThreads, Executor executor) {
2         this(nThreads, executor, SelectorProvider.provider());
3     }

    因為博主是在筆記本電腦調試的,故此時的selectorProvider是WindowsSelectorProvider,然後又加了一個參數DefaultSelectStrategyFactory單例對象:

1 public NioEventLoopGroup(
2             int nThreads, Executor executor, final SelectorProvider selectorProvider) {
3         this(nThreads, executor, selectorProvider, DefaultSelectStrategyFactory.INSTANCE);
4     }

    然後調父類的構造器,在末尾增加一個參數RejectedExecutionHandler單例對象:

1 public NioEventLoopGroup(int nThreads, Executor executor, final SelectorProvider selectorProvider,
2                              final SelectStrategyFactory selectStrategyFactory) {
3         super(nThreads, executor, selectorProvider, selectStrategyFactory, RejectedExecutionHandlers.reject());
4     }

 

2、MultithreadEventLoopGroup構造器

    在該構造器中,對線程數參數進行了處理,如果是0(對應上面NioEventLoopGroup的無參構造器),則將線程數設置為默認值,默認值取的是CPU核數*2,8核處理器對應16個線程;如果不是0,則以指定的線程數為準。同時,將executor後面的參數變為數組的形式,對應上面可以知道args中有三個元素:WindowsSelectorProvider、DefaultSelectStrategyFactory、RejectedExecutionHandler。

1 protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
2         super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
3     }

 

3、MultithreadEventExecutorGroup構造器

    此構造器又在args數組前面加了一個單例對象DefaultEventExecutorChooserFactory,用於從NioEventLoopGroup的數組中選取一個NioEventLoop。

1 protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
2         this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
3     }

    下面才是最終的核心構造器方法,結合註釋應該比較好理解。其中最重要的是第3步和第4步,下面着重講解這兩步。

 1 protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
 2                                             EventExecutorChooserFactory chooserFactory, Object... args) {
 3         // 1.對線程數進行校驗
 4         if (nThreads <= 0) {
 5             throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
 6         }
 7         // 2.給線程池參數賦值,從前面追蹤可知,若未賦值,executor一直是null,後續用於創建NioEventLoop中的啟動線程,所以這玩意就是一個線程工廠
 8         if (executor == null) {
 9             executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
10         }
11         // 3.給children循環賦值,newChild方法是重點,後續會講解 ***
12         children = new EventExecutor[nThreads];
13         for (int i = 0; i < nThreads; i ++) {
14             boolean success = false;
15             try {
16                 children[i] = newChild(executor, args);
17                 success = true;
18             } catch (Exception e) {
19                 // TODO: Think about if this is a good exception type
20                 throw new IllegalStateException("failed to create a child event loop", e);
21             } finally {
22                 // 省略掉未創建成功后的資源釋放處理
23             }
24         }
25         // 4.完成chooser選擇器的賦值,此處是netty一個小的優化點,後續會講解 **
26         chooser = chooserFactory.newChooser(children);
27         // 5.給數組中每一個成員設置監聽器處理
28         final FutureListener<Object> terminationListener = new FutureListener<Object>() {
29             @Override
30             public void operationComplete(Future<Object> future) throws Exception {
31                 if (terminatedChildren.incrementAndGet() == children.length) {
32                     terminationFuture.setSuccess(null);
33                 }
34             }
35         };
36 
37         for (EventExecutor e: children) {
38             e.terminationFuture().addListener(terminationListener);
39         }
40         // 6.設置一個只讀的set集合
41         Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
42         Collections.addAll(childrenSet, children);
43         readonlyChildren = Collections.unmodifiableSet(childrenSet);
44     }

 

3.1)、第4步chooser的賦值

    由上面構造器調用過程可知,chooserFactory對應DefaultEventExecutorChooserFactory對象,該對象的newChooser方法如下:

1 public EventExecutorChooser newChooser(EventExecutor[] executors) {
2         if (isPowerOfTwo(executors.length)) {
3             return new PowerOfTwoEventExecutorChooser(executors);
4         } else {
5             return new GenericEventExecutorChooser(executors);
6         }
7     }

    邏輯比較簡單,判斷數組的長度是不是2的N次冪,如果是,返回PowerOfTwoEventExecutorChooser對象,如果不是則返回GenericEventExecutorChooser對象。這二者有什麼區別,netty設計者為什麼要這麼做呢?如果對HashMap的實現原理有深入了解的園友應該不難想到,如果一個數X是2的N次冪,那麼用任意一個數Y對X取模可以用Y&(X-1)來高效的完成,這樣做比直接%取模快了好幾倍,這也是HashMap用2次冪作為數組長度的主要原因。這裡是同樣的道理,如下代碼所示,這兩個chooser類都很簡單,內部維護了一個原子遞增對象,每次調用next方法都加1,然後用這個數與數組長度取模,得到要對應下標位置的元素。而如果數組長度剛好是2次冪,用PowerOfTwoEventExecutorChooser就會提高效率,如果不是那也沒辦法,走%取模就是了。netty這種對效率提升的處理,是否在平時的CRUD中也能套用一下呢?

 1 private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
 2         private final AtomicInteger idx = new AtomicInteger();
 3         private final EventExecutor[] executors;
 4 
 5         PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
 6             this.executors = executors;
 7         }
 8 
 9         @Override
10         public EventExecutor next() {
11             return executors[idx.getAndIncrement() & executors.length - 1];
12         }
13     }
14 
15     private static final class GenericEventExecutorChooser implements EventExecutorChooser {
16         private final AtomicInteger idx = new AtomicInteger();
17         private final EventExecutor[] executors;
18 
19         GenericEventExecutorChooser(EventExecutor[] executors) {
20             this.executors = executors;
21         }
22 
23         @Override
24         public EventExecutor next() {
25             return executors[Math.abs(idx.getAndIncrement() % executors.length)];
26         }
27     }

 

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

3.2)、第3步newChild方法的邏輯

    該方法的實現在NioEventLoopGroup中,由於args長度為3,所以queueFactory為null(暫時未發現哪裡的實現args參數長度會是4,或許只是為後續擴展用,如果園友對args長度為4的場景有了解的還請留言指教)。然後調用了NioEventLoop的構造器,下面進入NioEventLoop的初始化。

1 protected EventLoop newChild(Executor executor, Object... args) throws Exception {
2         EventLoopTaskQueueFactory queueFactory = args.length == 4 ? (EventLoopTaskQueueFactory) args[3] : null;
3         return new NioEventLoop(this, executor, (SelectorProvider) args[0],
4             ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2], queueFactory);
5     }

    執行完上述初始化方法后NioEventLoopGroup的快照圖如下,最重要的就兩個屬性:child和chooser。

 

 

 

二、NioEventLoop的初始化

1、NioEventLoop的構造器

    到這裏,有必要將此構造器的入參再梳理一遍。parent即上面的NioEventLoopGroup對象,executor是在MultithreadEventExecutorGroup中初始化的ThreadPerTaskExecutor,selectorProvider是WindowsSelectorProvider,strategy是DefaultSelectStrategyFactory,rejectedExecutionHandler是RejectedExecutionHandler,queueFactory是null。

 1 NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
 2                  SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler,
 3                  EventLoopTaskQueueFactory queueFactory) {
 4         super(parent, executor, false, newTaskQueue(queueFactory), newTaskQueue(queueFactory),
 5                 rejectedExecutionHandler);
 6         if (selectorProvider == null) {
 7             throw new NullPointerException("selectorProvider");
 8         }
 9         if (strategy == null) {
10             throw new NullPointerException("selectStrategy");
11         }
12         provider = selectorProvider;
13         final SelectorTuple selectorTuple = openSelector();
14         selector = selectorTuple.selector;// netty封裝的selector
15         unwrappedSelector = selectorTuple.unwrappedSelector;// java NIO原生的selector
16         selectStrategy = strategy;
17     }

    可以看到只是做了一些賦值,其中newTaskQueue方法創建的是MpscUnboundedArrayQueue隊列(多生產單消費無界隊列,mpsc是multi provider single consumer的首字母縮寫,即多個生產一個消費),繼續追查父類構造方法。

 

2、SingleThreadEventLoop構造器

    調用父類構造器,給tailTasks賦值。

1 protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
2                                     boolean addTaskWakesUp, Queue<Runnable> taskQueue, Queue<Runnable> tailTaskQueue,
3                                     RejectedExecutionHandler rejectedExecutionHandler) {
4         super(parent, executor, addTaskWakesUp, taskQueue, rejectedExecutionHandler);
5         tailTasks = ObjectUtil.checkNotNull(tailTaskQueue, "tailTaskQueue");
6     }

 

3、SingleThreadEventExecutor構造器

    在該構造方法中完成了剩餘變量的賦值,其中有兩個變量很重要:executor和taskQueue。前者負責創建Reactor線程,後者是實現串行無鎖化的任務隊列。

 1 protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
 2                                         boolean addTaskWakesUp, Queue<Runnable> taskQueue,
 3                                         RejectedExecutionHandler rejectedHandler) {
 4         super(parent);
 5         this.addTaskWakesUp = addTaskWakesUp;
 6         this.maxPendingTasks = DEFAULT_MAX_PENDING_EXECUTOR_TASKS;
 7         this.executor = ThreadExecutorMap.apply(executor, this);
 8         this.taskQueue = ObjectUtil.checkNotNull(taskQueue, "taskQueue");
 9         rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
10     }

    NioEventLoopGroup的對象引用最終記錄在了AbstractEventExecutor中:

1 protected AbstractEventExecutor(EventExecutorGroup parent) {
2         this.parent = parent;
3     }

    NioeventLoop初始化完成之後的對象快照如下,左邊是子類,右邊是父類:

 

 

小結

    本文詳細講述了netty中Reactor線程組概念模型的實現類 — NioEventLoopGroup的實例化過程。NioEventLoopGroup和其內部數組元素NioEventLoop是netty通信框架的基石,相信本文的內容對初學netty的園友有一點幫助。

    下篇將研究ServerBootstrap的初始化過程,敬請期待。

 

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

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」