豪雨引發土石流洪災 印度尼泊爾過去1週至少41死

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

尼泊爾和印度官員今(31日)表示,過去一週豪雨導致洪水及土石流,兩國共有至少41人喪命。南亞年度雨季進入最後階段,在各國已奪走數百人命。

尼泊爾內政部官員表示,西部偏遠地區昨天暴雨導致土石流,埋沒5戶住家,並造成10人死亡,死者包括4名孩童。路透社報導,多山的尼泊爾今年至少已269人死於土石流與洪水,另有76人失蹤。

印度國家緊急應變中心也表示,西部古茶拉底省(Gujarat)過去2天內已有14人因與大雨和洪水相關事故喪命。在東部的奧里薩省(Odisha),過去1週洪水也奪走至少17條人命,造成數以千計民眾流離失所,影響逾50萬人。

氣候變遷
國際新聞
印度
尼泊爾
豪雨
土石流
洪災

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

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

阿根廷環團拒建中資養豬場 合作協議延期簽署

摘錄自2020年09月05日中央社報導

中國計劃投資逾37億美元在阿根廷蓋養豬場供應中國,以解決非洲豬瘟造成的豬肉短缺,但遭阿國環團反對。阿國政府表示將納入環保條款,兩國合作協議延至11月簽署。

阿根廷網路媒體iProfesional報導,據阿根廷外交部消息來源指出,艾柏托政府將利用11月參加中國國際進口博覽會(中國進博會)的機會,簽下這項養豬產業協議。

iProfessional報導,根據取得的文件,這項合作案預計建造25座養豬場,每座養豬場飼養約1.2萬頭豬。每座養豬場還需配合建造壓榨基因改造大豆和玉米的工廠,以提供牲畜飼料。此外,建造的地點除是過去50年從未淹水的農地,每天也須供應多達150萬公升的水流量。

此項協議簽署後,預計每年可供應中國近90萬噸的豬肉。中國因爆發非洲豬瘟,撲殺國內40%的豬隻,造成豬肉短缺,因此開始向其他國家尋求養豬場。

但養豬場投資案曝光後,遭阿根廷環保團體、動物權益者和許多民眾強力反對,擔憂將使阿根廷的環境、水質和土壤遭受前所未有污染,迫使政府暫時延後簽署備忘錄。

根據綠色和平組織的報告,在墨西哥等養豬產業密集國家,或阿根廷政府打算建造的養豬場,每天排放二氧化碳和甲烷等致命氣體,相當於數百萬輛汽車排放的廢氣。

污染治理
國際新聞
阿根廷
養豬場

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

【其他文章推薦】

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

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

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

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

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

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

海神挾風雨今晨登陸 南韓一核電廠2機組突停止運轉

摘錄自2020年9月7日自由時報報導

中度颱風「海神」已於台灣時間今(7日)上午8點登陸南韓蔚山沿海,海神颱風威力強大,傳出位於慶尚北道慶州市的月城核電廠有兩個核電機組暫停運轉。

據《韓聯社》報導,韓國水電與核電公司總部宣布,擁有四個核子反應爐發電的月城核電廠的2號機組在當地時間8點38分停止運作,而3號機組則在上午9點18分停止運轉,這兩機組設備容量都達70萬千瓦(kW),另外兩機組則正常運作,維持60%發電量,總部強調,沒有輻射外洩問題。

韓國水電與核電公司總部一名官員表示,目前正在檢查停機原因,研判可能是由於颱風造成電線短路問題;月城核電廠供應約5%的南韓用電量。

能源轉型
國際新聞
南韓
核電廠

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

【其他文章推薦】

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

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

※台北網頁設計公司全省服務真心推薦

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

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

燃燒的西伯利亞:北極野火碳排量創紀錄 已較2019全年高出35%

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

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

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

說到“新能源”就只有電動車嗎?

木炭車行駛速度慢,載重能力差,主要承擔客運。當時,由市區發往百多公里以外鄰縣的班線,每天只能行使一個單程。由於車輛老舊,汽車中途拋錨現象時常發生。特別是遇上坡路段,汽車還經常熄火,需旅客幫助推車。有時木炭燒完,還要發動旅客下車揀柴禾、樹枝充做燃料。

現在說到新能源,一般會想到電動車,如果了解得多的可能還知道燃料電池或者氫動力之類,但是再早一點,國內還有另外一種不用新能源的“新能源”汽車—木炭車。

當你在想木炭車怎麼驅動車的時候是不是以為大力出奇迹?其實這裏的木炭不是直接當做發動機的燃料來產生動力,整個工作原理大致如下圖。

利用木炭在一個特殊的發生爐經過一系列程序後生成煤氣,然後讓發動機燃燒煤氣驅動車輛。聽着就很蛋疼啊,除了讓車看起來更丑,開起來更慢,可能還有更多的pM2.5(那會應該還沒有這個概念)為什麼要搞這種事情呢?

上世紀五十年代初,國家經濟處在恢復時期又遇抗美援朝戰爭,各西方國家對我國實施禁運政策,國家汽油短缺,燒汽油的車就紛紛改裝成燒木炭的車。(所以木炭車的本質還是活塞發動機的原理,只是燃料從石油變成了煤氣。)

由於木炭車的結構,每天發車前,駕駛員需要用油棉引燃木炭,先燒上10分鐘左右,然後搖動鼓風機才能啟動。上部的木炭淋有少量的水分,由於頂部的鍋爐蓋在正常工作時是密封着的,高溫密封加少量的氧,反應成一氧化碳氣體,經罐內生鐵造的爐膽管道,進入儲氣罐再送到發動機里和適量的空氣混合,形成可燃混合氣(也就是煤氣),然後導入發動機工作。(讓我們看下木炭車的動力系統是怎麼工作的)

木炭車一般從點爐到啟動需40-50分鐘。行駛中,還要添料、捅爐、掌握木炭燃燒程度,司機和助手每天工作相當辛苦。木炭車行駛速度慢,載重能力差,主要承擔客運。當時,由市區發往百多公里以外鄰縣的班線,每天只能行使一個單程。由於車輛老舊,汽車中途拋錨現象時常發生。

特別是遇上坡路段,汽車還經常熄火,需旅客幫助推車。有時木炭燒完,還要發動旅客下車揀柴禾、樹枝充做燃料。

一趟行程下來司機乘客都是灰頭土臉,但是在當時的情況下,有車就已經很滿足了,顯然這裏的“新能源”並不是所謂的綠色新能源,所以木炭車隨着我國的油田發展也就漸漸消失了。

看到上面的工程師以為木炭車中國才有嗎?其實二戰的時候有些國家也因為石油問題,甚至期間的坦克也採用“木炭車”的技術,屁股上背着一個大罐。

而在我們的鄰國朝鮮現在還有機會能看到。

雖然我們國家現在有了自己的油田,現在卻是世界最大石油進口國。不管是因為真的消耗如此巨大還是戰略上的貯備,木炭車的歷史都讓我們明白了一個道理,一定要有自己的核心技術。如果當時我們連木炭車都搗鼓不出來就真的只能幹瞪眼。現在的自主品牌中有些確實腳踏實地在潛心研發自己的技術,但是也有些品牌像寄生蟲一樣,總是想着鑽空子,鬧個大新聞,希望自主品牌越來越好的贊一個!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

主動安全很重要 8萬元以下帶ESP家用轎車選這些!

最初的遙控泊車着實讓我們驚艷的一把。速銳在7萬多的車型上就已經配備了ESp車身穩定系統。除了ESp此外,速銳身上還有許多實用配置,表現可圈可點。動力上速銳採用了1。5升的自然吸氣發動機。搭配5擋手動或者6擋雙離合變速箱,最大功率109馬力最大扭矩145牛米,這樣的動力輸出只能說夠用,但是這也為速銳帶來了非常親民的價格。

昨天小編美美走路一不小心,摔了個狗吃屎;這時候二貨同事說,你這種十八年的老車沒有ESp,肯定容易摔跤啊,主動安全性不行嘛。

的確,對於十八歲的小編美美來說,如果我媽生我的時候附帶了ESp技能,興許就不會摔跤了。

看↓↓↓

帶ESp的狗狗穩定性多好。

當然這都是玩笑,不過ESp對於汽車來說還真有這麼神奇的功效。它能夠通過對單獨車輪進行制動、切斷動力等方法來控制車身動態,減少車輛的失控機率。憑藉優異的主動安全表現,ESp榮獲了NCAp全球獎項,這也足以證明ESp在安全性上的巨大作用。

我們看看在時速40km的情況下汽車在冰面上做一個變線的情況

可以看到帶ESp的車輛很好的保持了正常行駛,而沒帶ESp的車,呵呵~

那麼多少錢的車會搭載ESp呢?一般來說10萬以上的自主車輛都配備了ESp,12萬以上的合資車都配備了ESp,如果你買了個合資車花了十幾萬還沒有ESp那你來找我,我來幫你罵它。那麼10萬內有沒有帶ESp的車呢?當然有,這幾台車不到8萬裸車的配置就帶有ESp了,而且綜合表現也不錯哦。

長安悅翔V7

指導價:5.99-8.79萬

長安悅翔V7是我們非常熟悉的一位車型。而最近它又增加了1.0T的動力系統。除了低配車型之外,中高配開始就搭載了ESp車身穩定系統,在安全性方面還是十分厚道的。

悅翔V7的外觀設計在同級別中數一數二,而在內飾上設計的也十分恰到好處。簡約的內飾清晰的功能分區以及不錯的人機設計。都值得豎個大拇指,但是對於悅翔V7來說最大的問題是我為什麼不加一點錢買逸動呢?

比亞迪速銳

指導價:6.99-9.59萬

比亞迪速銳是我們非常熟悉的一款車型,它以超高的配置和大空間作為主要的賣點。最初的遙控泊車着實讓我們驚艷的一把。速銳在7萬多的車型上就已經配備了ESp車身穩定系統。除了ESp此外,速銳身上還有許多實用配置,表現可圈可點。

動力上速銳採用了1.5升的自然吸氣發動機。搭配5擋手動或者6擋雙離合變速箱,最大功率109馬力最大扭矩145牛米,這樣的動力輸出只能說夠用,但是這也為速銳帶來了非常親民的價格。

雪佛蘭樂風RV

指導價:7.49-9.99萬

樂風RV雖然定位為小型車,但是由於兩廂的造型設計以及不錯的空間設計理念,樂風RV在內部空間表現上還是十分值得表揚的,樂風RV使用1.5升自然吸氣發動機匹配5擋手動或者4擋自動變速箱。

樂風RV在同價位的車型中十分罕見的全系標配ESp車身穩定系統,這也显示了通用對於安全性的重視以及嚴謹的造車態度,樂風RV可以說是一位不折不扣的實力选手。不過沒那麼主流的外觀可能會讓不少人拒絕它。

東風風行景逸S50

指導價:6.99-10.29萬

風行景逸S50外觀上就比較的平庸了,在內飾設計上則是東風風行家族的標準化設計,沒什麼亮點但是風行景逸S50在低配車型上也已經配備了ESp,而且加上較大的尺寸和完善的動力系統。風行景逸還是值得推薦的。

風行景逸S50採用1.5L和1.6L/2.0L 3款自然吸氣發動機自動車型匹配CVT變速箱。動力方面風行景逸S50表現平平,但是它的車身尺寸比較大,軸距長達兩米七,行李箱容積也達到了500升,對於家用車來說這樣的空間表現十分寬裕了。而我唯一需要考慮的是我要不要等即將上市的外觀更大氣內飾更加精緻的新款呢?

總結:

對於家用車來說,安全舒適是十分重要的,有ESp能夠大大提升你在下雨天、環島行駛等多種情況下的安全性,因此對於ESp小編美美是十分推薦的,當然更重要的還在於謹慎駕駛安全開車,如果你100km/h打死方向,神仙也救不了你。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

※台北網頁設計公司全省服務真心推薦

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

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

小編辣評6-8萬熱門SUV!說完會不會被炒魷魚?

前置后驅載貨也是一流的,但1。6L動力剛好夠用而已。想動力強加錢上1。5T吧”東風風光580指導價:7。29-9。99萬“又一款7座中型SUV,價格也與長安CX70相似,硬抗的節奏。不過衝著配置以及顏值,我選風光580”東風風神AX3指導價:6。

在留言中,小編總是會遇到很多很多的留言,都是詢問某某車型是怎樣的。那麼今天小編就來一個超大的集合,簡單但客觀地評論6-8萬區間的SUV熱門車型。這樣的評論小編真擔心自己的飯碗吶~

寶駿560

指導價:6.98-10.58萬

“寶駿神車物美價廉,空間大油耗低配置高,但就是底盤表現沒有那麼的SUV。”

北汽幻速S2

指導價:5.18-6.08萬

“價格非常低的SUV車型滿足你的SUV夢,空間還是可以的,但底盤以及操控還是別想着太SUV”

北汽幻速S3

指導價:5.38-6.68萬

“就問你,五萬多的7座緊湊型SUV還想着怎樣?不過小編覺得要是出五座版本的話,空間表現絕對是逆天的”

紳寶X35

指導價:6.58-8.88萬

“總體中規中矩沒有太多亮點,但是來自北汽紳寶讓它可靠性更高,內飾也挺對年輕人口味”

比速T3

指導價:7.49-8.69萬

“配置水平極高,使用的還是1.3T渦輪增壓發動機,就是比速的4S店在哪裡?”

比亞迪元

指導價:7.49-8.69萬

“國產中最愛堆配置的品牌,顏值頗高,背着小書包非常個性,但就看合不合你胃口”

長安CX70

指導價:6.89-10.99萬

“不到7萬買到中型7座SUV,還能有誰?前置后驅載貨也是一流的,但1.6L動力剛好夠用而已。想動力強加錢上1.5T吧”

東風風光580

指導價:7.29-9.99萬

“又一款7座中型SUV,價格也與長安CX70相似,硬抗的節奏?不過衝著配置以及顏值,我選風光580”

東風風神AX3

指導價:6.97-8.77萬

“啊?原來這貨是SUV,我還以為是旅行車呢?顏值不錯,但性價比比較一般”

東南DX3

指導價:6.79-10.09萬

“要顏值有顏值,要操控有操控,年輕人的最愛,後輪還是多連桿式獨立懸架。”

哈弗H1

指導價:5.49-7.89萬

“別以為長城M2換了一個馬甲我就不認得你,ESp配置率較高值得推薦,但其餘都是中規中矩”。

大邁X5

指導價:6.99-12.19萬

“你覺得它外觀像誰呢?配置外觀表現優秀,但就是做工方面以及質感方面需要加強”

小編總結:

對這個價位的車型而言,成本的原因它們是無法做到盡善盡美的,要配置還是要動力?兩者都要做到的話,駕駛品質以及做工精細水平能保證嗎?所以小編覺得的是選擇滿足自己最大需求的車型就好了。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

別再寫一摞if-else了!再寫開除!兩種設計模式帶你消滅它!

題外話:本來不想解釋、可是看完評論,有點服氣。沒想到居然這麼多人能曲解題意。這篇文章明顯是在說,不要寫一大堆if-else,一大堆是啥意思很難懂嗎?我沒有一句話說了不要寫if-else。開頭也給出了具體需求,在這種需求的前提下不要寫if-else,沒毛病吧??

代碼潔癖狂們!看到一個類中有幾十個if-else是不是很抓狂?
設計模式學了用不上嗎?面試的時候問你,你只能回答最簡單的單例模式,問你有沒有用過反射之類的高級特性,回答也是否嗎?
這次就讓設計模式(模板方法模式+工廠模式)和反射助你消滅if-else!
真的是開發中超超超超超超有用的乾貨啊!

那個坑貨

某日,碼農胖滾豬接到上級一個需求,這個需求牛逼了,一站式智能報表查詢平台,支持mysql、pgxl、tidb、hive、presto、mongo等眾多數據源,想要啥數據都能通通給你查出來展示,對於業務人員數據分析有重大意義!

雖然各個數據源的參數校驗、查詢引擎和查詢邏輯都不一樣,但是胖滾豬對這些框架都很熟悉,這個難不倒她,她只花了一天時間就都寫完了。

領導胖滾熊也對胖滾豬的效率表示了肯定。可是好景不長,第三天,領導閑着沒事,準備做一下code review,可把胖滾熊驚呆了,一個類裏面有近30個if-else代碼,我滴個媽呀,這可讓代碼潔癖狂崩潰了。

// 檢驗入參合法性
Boolean check = false;
if(DataSourceEnum.hive.equals(dataSource)){
    check = checkHiveParams(params);
} else if(DataSourceEnum.tidb.equals(dataSource)){
    check = checkTidbParams(params);
} else if(DataSourceEnum.mysql.equals(dataSource)){
    check = checkMysqlParams(params);
} // else if ....... 省略pgxl、presto等
if(check){
    if(DataSourceEnum.hive.equals(dataSource)){
        list = queryHive(params);
    } else if(DataSourceEnum.tidb.equals(dataSource)){
        list = queryTidb(params);
    } else if(DataSourceEnum.mysql.equals(dataSource)){
        list = queryMysql(params);
    } // else if ....... 省略pgxl、presto等
}
//記錄日誌
log.info("用戶={} 查詢數據源={} 結果size={}",params.getUserName(),params.getDataSource(),list.size());

模板模式來救場

首先我們來分析下,不管是什麼數據源,算法結構(流程)都是一樣的,1、校驗參數合法性 2、查詢 3、記錄日誌。這不就是說模板一樣、只不過具體細節不一樣,沒錯吧?

讓我們來看看設計模式中模板方法模式的定義吧:

模板方法模式:定義一個操作中的算法的框架,而將一些步驟延遲到子類中. 使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。通俗的講,就是將子類相同的方法, 都放到其抽象父類中。

我們這需求不就和模板方法模式差不多嗎?因此我們可以把模板抽到父類(抽象類)中。至於特定的步驟實現不一樣,這些特殊步驟,由子類去重寫就好了。

廢話不多說了,我們先把父類模板寫好吧,完全一樣的邏輯是記錄日誌,這步在模板寫死就好。至於檢驗參數和查詢,這兩個方法各不相同,因此需要置為抽象方法,由子類去重寫。

public abstract class AbstractDataSourceProcesser <T extends QueryInputDomain> {
    public List<HashMap> query(T params){
        List<HashMap> list = new ArrayList<>();
        //檢驗參數合法性 不同的引擎sql校驗邏輯不一樣
        Boolean b = checkParam(params);
        if(b){
            //查詢
            list = queryData(params);
        }
        //記錄日誌
        log.info("用戶={} 查詢數據源={} 結果size={}",params.getUserName(),params.getDataSource(),list.size());
        return list;
    }
    //抽象方法 由子類來實現特定邏輯
    abstract Boolean checkParam(T params);
    abstract List<HashMap> queryData(T params);
}

這段代碼非常簡單。但是為了照顧新手,還是想解釋一個東西:

T這個玩意。叫泛型,因為不同數據源的入參不一樣,所以我們使用泛型。但是他們也有公共的參數,比如用戶名。因此為了不重複冗餘,更好的利用公共資源,在泛型的設計上,我們可以有一個泛型上限,<T extends QueryInputDomain>

public class QueryInputDomain<T> {
    public String userName;//查詢用戶名
    public String dataSource;//查詢數據源 比如mysql\tidb等
    public T params;//特定的參數 不同的數據源參數一般不一樣
}
public class MysqlQueryInput extends QueryInputDomain{
    private String database;//數據庫
    public String sql;//sql
}

接下來就輪到子類出場了,通過上面的分析,其實也很簡單了,不過是繼承父類,重寫checkParam()和queryData()方法,下面以mysql數據源為例,其他數據源也都一樣的套路:

@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
    @Override
    public Boolean checkParam(MysqlQueryInput params) {
        System.out.println("檢驗mysql參數是否準確");
        return true;
    }

    @Override
    public List<HashMap> queryData(MysqlQueryInput params) {
        List<HashMap> list = new ArrayList<>();
        System.out.println("開始查詢mysql數據");
        return list;
    }
}

這樣一來,所有的數據源,都自成一體,擁有一個只屬於自己的類,後續要擴展數據源、或者要修改某個數據源的邏輯,都非常方便和清晰了。

說實話,模板方法模式太簡單了,抽象類這東西也太基礎普遍了,一般應屆生都會知道的。但是對於初入職場的新人來說,還真不太能果斷應用在實際生產中。因此提醒各位:一定要有一個抽象思維,避免代碼冗餘重複。

另外,要再啰嗦幾句,即使工作有幾年的工程師也很容易犯一個錯誤。就是把思維局限在今天的需求,比如老闆一開始只給你一個mysql數據源查詢的需求,壓根沒有if-else,可能你就不會放在心上,直接在一個類中寫死,不會考慮到後續的擴展。直到後面越來越多的新需求,你才恍然大悟,要全部重構一番,這樣浪費自己的時間了。因此提醒各位:做需求不要局限於今天,要考慮到未來。 從一開始就做到高擴展性,後續需求變更和維護就非常爽了。

原創聲明:本文為【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

工廠模式來救場

但是模板模式還是沒有完全解決胖滾豬的if-else,因為需要根據傳進來的dataSource參數,判斷由哪個service來實現查詢邏輯,現在是這麼寫的:

  if(DataSourceEnum.hive.equals(dataSource)){
        list = queryHive(params);
    } else if(DataSourceEnum.tidb.equals(dataSource)){
        list = queryTidb(params);
    }

那麼這種if-else應該怎麼去幹掉呢?我想先跟你講講工廠模式的那些故事。

工廠模式:工廠方法模式是一種創建對象的模式,它被廣泛應用在jdk中以及Spring和Struts框架中。它將創建對象的工作轉移到了工廠類。

為了呼應一下工廠兩字,我特意舉一個代工廠的例子讓你理解,這樣你應該會有更深刻的印象。

以手機製造業為例。我們知道有蘋果手機、小米手機等等,每種品牌的手機製造方法必然不相同,我們可以先定義好一個手機標準接口,這個接口有make()方法,然後不同型號的手機都繼承這個接口:

#Phone類:手機標準規範類(AbstractProduct)
public interface Phone {
    void make();
}
#MiPhone類:製造小米手機(Product1)
public class MiPhone implements Phone {
    public MiPhone() {
        this.make();
    }
    @Override
    public void make() {
        System.out.println("make xiaomi phone!");
    }
}
#IPhone類:製造蘋果手機(Product2)
public class IPhone implements Phone {
    public IPhone() {
        this.make();
    }
    @Override
    public void make() {
        System.out.println("make iphone!");
    }
}

現在有某手機代工廠:【天霸手機代工廠】。客戶只會告訴該工廠手機型號,就要匹配到不同型號的製作方案,那麼代工廠是怎麼實現的呢?其實也很簡單,簡單工廠模式(還有抽象工廠模式和工廠方法模式,有興趣可以了解下)是這麼實現的:

#PhoneFactory類:手機代工廠(Factory)
public class PhoneFactory {
    public Phone makePhone(String phoneType) {
        if(phoneType.equalsIgnoreCase("MiPhone")){
            return new MiPhone();
        }
        else if(phoneType.equalsIgnoreCase("iPhone")) {
            return new IPhone();
        }
    }
}

這樣客戶告訴你手機型號,你就可以調用代工廠類的方法去獲取到對應的手機製造類。你會發現其實也不過是if-else,但是把if-else抽到一個工廠類,由工廠類統一創建對象,對我們的業務代碼無入侵,不管是維護還是美觀上都會好很多。

首先,我們應該在每個特定的dataSourceProcessor(數據源執行器),比如MysqlProcesser、TidbProcesser中添加spring容器註解@Component。該註解我想應該不用多解釋了吧~重點是:我們可以把不同數據源都搞成類似的bean name,形如dataSourceProcessor#數據源名稱,如下兩段代碼:

@Component("dataSourceProcessor#mysql")
public class MysqlProcesser extends AbstractDataSourceProcesser<MysqlQueryInput>{
@Component("dataSourceProcessor#tidb")
public class TidbProcesser extends AbstractDataSourceProcesser<TidbQueryInput>{

這樣有什麼好處呢?我可以利用Spring幫我們一次性加載出所有繼承於AbstractDataSourceProcesser的Bean ,形如Map<String, AbstractDataSourceProcesser>,Key是Bean的名稱、而Value則是對應的Bean:

@Service
public class QueryDataServiceImpl implements QueryDataService {
    @Resource
    public Map<String, AbstractDataSourceProcesser> dataSourceProcesserMap;
    public static String beanPrefix = "dataSourceProcessor#";
    @Override
    public List<HashMap> queryData(QueryInputDomain domain) {
        AbstractDataSourceProcesser dataSourceProcesser = dataSourceProcesserMap.get(beanPrefix + domain.getDataSource());
        //省略query代碼
    }
}

可能你還是不太理解,我們直接看一下運行效果:

1、dataSourceProcesserMap內容如下所示,存儲了所有數據源Bean,Key是Bean的名稱、而Value則是對應的Bean:

2、我只需要通過key(即前綴+數據源名稱=beanName),就能匹配到對應的執行器了。比如當參數dataSource為tidb的時候,key為dataSourceProcessor#tidb,根據key可以直接從dataSourceProcesserMap中獲取到TidbProcesser

public static String classPrefix = "com.lyl.java.advance.service.";

AbstractDataSourceProcesser sourceGenerator = 
(AbstractDataSourceProcesser) Class.forName
(classPrefix+DataSourceEnum.getClasszByCode(domain.getDataSource()))
.newInstance();

需要注意的是,該種方法是通過className來獲取到類的實例,而前端傳參肯定是不會傳className過來的。因此可以用到枚舉類,去定義好不同數據源的類名:

public enum DataSourceEnum {
    mysql("mysql", "MysqlProcesser"),
    tidb("tidb", "TidbProcesser");
    private String code;
    private String classz;

原創聲明:本文為【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

總結

有些童鞋總覺得設計模式用不上,因為平時寫代碼除了CRUD還是CRUD,面試的時候問你設計模式,你只能回答最簡單的單例模式,問你有沒有用過反射之類的高級特性,回答也是否。

其實不然,JAVA這23種設計模式,每一個都是經典。今天我們就用模板方法模式+工廠模式(或者反射)解決了讓人崩潰的if-else。後續對於設計模式的學習,也應該多去實踐,從真實的項目中找到用武之地,你才算真正把知識佔為己有了。

本篇文章的內容和技術點雖然很簡單,但旨在告訴大家應該要有一個很好的代碼抽象思維。杜絕在代碼中出現一大摞if-else或者其他爛代碼。

即使你有很好的代碼抽象思維,做需求開發的時候,也不要局限於當下,只考慮現在,要多想想未來的擴展性。

就像你談戀愛一樣,只考慮當下的是渣男,考慮到未來的,才算是一個負責任的人

“願世界沒有渣男”

原創聲明:本文為【胖滾豬學編程】原創博文,轉載請註明出處。以漫畫形式讓編程生動有趣!原創不易,求關注!

本文來源於公眾號:【胖滾豬學編程】。一枚集顏值與才華於一身,不算聰明卻足夠努力的女程序媛。用漫畫形式讓編程so easy and interesting!求關注!

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

【其他文章推薦】

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

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

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

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

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

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

【實戰】基於OpenCV的水表字符識別(OCR)

目錄

  • 1. USB攝像頭取圖
  • 2. 圖像預處理:獲取屏幕ROI
    • 2.1. 分離提取屏幕區域
    • 2.2. 計算屏幕區域的旋轉角度
    • 2.3. 裁剪屏幕區域
    • 2.4. 旋轉圖像至正向視角
    • 2.5. 提取文字圖像
    • 2.6. 封裝上述過程
  • 3. 字符分割,獲取單個字符的圖像
  • 4. 模板匹配:確定字符內容
    • 4.1. make_template
    • 4.2. 模板修復
    • 4.3. 重新加載模板數據
    • 4.4. 模板匹配

1. USB攝像頭取圖

由於分辨率越高,處理的像素就越多,導致分析圖像的時間變長,這裏,我們設定攝像頭的取圖像素為(240,320):

cap = cv2.VideoCapture(0)  # 根據電腦連接的情況填入攝像頭序號
assert cap.isOpened()

# 以下設置显示屏的寬高
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter.fourcc('M', 'J', 'P', 'G'))

這裏提幾個常用的標準分辨率:

  • VGA (Video Graphics Array): 640×480
  • QVGA (QuarterVGA): 240×320
  • QQVGA: 120×160

接下來可以捕獲一幀數據看一下狀態:

# %% 捕獲一幀清晰的圖像
def try_frame():
    while True:
        ret, im_frame = cap.read()
        cv2.imshow("frame", im_frame)  # 显示圖像

        # im_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)  # 可選擇轉換為灰度圖
        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    cv2.destroyAllWindows()
    return im_frame

im_frame = try_frame()
env.imshow(im_frame)

ps: 鏡頭角度會存在一定的歪斜,沒有關係,我們後面會進行處理。

2. 圖像預處理:獲取屏幕ROI

利用屏幕的亮度,通過簡單的閾值操作和輪廓操作,獲取屏幕輪廓,然後將圖像角度校正,最後獲得正向的文字內容。

2.1. 分離提取屏幕區域

通過OTSU的閾值化操作,將圖像處理為二值狀態。這個很重要,因為如果直接使用彩圖或灰度圖,會由於外部光線的變化,導致後期字符匹配時整體灰度值與模板的差別而降低置信度,導致較大的誤差。而二值圖可以避免這個問題。

然後利用開運算(白底黑字,如果黑底白字則為閉運算),消除噪點。

im_latest = try_frame()
im_gray = mvlib.color.rgb2gray(image)
im_bin = mvlib.filters.threshold(im_gray, invert=False)
# im_erosion = mvlib.morphology.erosion(im_bin, (11, 11))
# im_dilation = mvlib.morphology.dilation(im_erosion, (5, 5))
im_opening = mvlib.morphology.opening(im_bin, (11, 11))
env.imshow(im_opening)

2.2. 計算屏幕區域的旋轉角度

提取圖像的最大輪廓,然後獲取其包絡矩形。

list_cnts = mvlib.contours.find_cnts(im_opening)
if len(list_cnts) != 1:
    print(f"非唯一輪廓,請通過面積篩選過濾")
    # assert 0
    cnts_sorted = mvlib.contours.cnts_sort(list_cnts, mvlib.contours.cnt_area)
    list_cnts = [cnts_sorted[0]]

box, results = mvlib.contours.approx_rect(list_cnts[0], True)
angle = results[2]  # 此處的角度是向逆時針傾斜,記作:-4
if abs(angle) > 45:
    angle = (angle + 45) % 90 - 45
print(angle, box)

上述過程輸出:

1.432098388671875
[[282 173]
 [ 29 167]
 [ 32  41]
 [285  47]]

2.3. 裁剪屏幕區域

至此可以丟棄im_opening以及im_bin的圖像了。我們重新回到im_gray上進行操作(需要重新進行閾值化以獲取文字的二值圖)。

list_width = box[:,0]
list_height= box[:,1]
w_min, w_max = min(list_width), max(list_width)
h_min, h_max = min(list_height), max(list_height)

im_screen = im_gray[h_min:h_max, w_min:w_max]
env.imshow(im_screen)

2.4. 旋轉圖像至正向視角

im_screen_orthogonal = mvlib.transform.rotate(im_screen, angle, False)
# env.imshow(im_screen_orthogonal)
im_screen_core = im_screen_orthogonal[20:-20, 20:-20]
env.imshow(im_screen_core)

2.5. 提取文字圖像

第二次執行閾值化操作,但這一次是在屏幕內部,排除了屏幕外複雜的背景后,可以很容易的獲取到文字的內容。由於我們只關心数字,所以通過閉運算將細體字過濾掉。

im_core_bin = mvlib.filters.threshold(im_screen_core, invert=False)
im_closing = mvlib.morphology.closing(im_core_bin, (3,3))
env.imshow(im_closing)

2.6. 封裝上述過程

瑣碎的預處理過程就告一段落了,我們可以將上述的內容封裝成一個簡單的函數:

def preprocess():
    # 獲取屏幕區域
    im_latest = try_frame()
    ...
    im_closing = mvlib.morphology.closing(im_core_bin, (3,3))
    return im_closing

3. 字符分割,獲取單個字符的圖像

字符分割,一方面是製作模板的需要(當然,你也可以直接用畫圖工具裁剪出一張模板圖像);另一方面是為了加速模板匹配的效率。當然,你完全可以在整張圖像上利用 match_template() 查找模板,但如果進行多模板匹配,重複的掃描整張圖像,效率就大打折扣了。

先提供完整的代碼

char_width_min = 7
gap_height_max = 5

def segment_chars(im_core):
    list_char_img = []
    # 字符區域
    raw_bkg = np.all(im_core, axis=0)
    col_bkg = np.all(im_core, axis=1)

    # 計算字高
    ndarr_char_height = np.where(False == col_bkg)[0]
    char_height_start = ndarr_char_height[0]
    item_last = ndarr_char_height[0]
    for item in ndarr_char_height:
        if item - item_last > gap_height_max:
            char_height_start = item
        item_last = item
    char_height_end = ndarr_char_height[-1] +1
    print(f"字高【{char_height_end - char_height_start}】")

    ndarr_chars_pos = np.where(False == raw_bkg)[0]
    ndarr_chars_pos = np.append(ndarr_chars_pos,
                                im_core.shape[1] + char_width_min)

    last_idx = ndarr_chars_pos[0]
    curr_char_width = 1
    for curr_idx in ndarr_chars_pos:
        idx_diff = curr_idx - last_idx
        # 這裏應該限制最小寬度>=2,否則認為是一個粘連字
        if idx_diff <= 2:
            curr_char_width += idx_diff
        else:  # 新的字符
            char_width_end = last_idx +1
            char_width_start = char_width_end - curr_char_width
            im_char_last = im_core[char_height_start:char_height_end,
                                char_width_start:char_width_end]
            list_char_img.append(im_char_last)
            curr_char_width = 0
        last_idx = curr_idx
    return list_char_img

按照行列,獲取圖像中的文字像素點集:

raw_bkg = np.all(im_core, axis=0)
col_bkg = np.all(im_core, axis=1)

由此,可以知道255(黑色)的區域從大約 39 到 75,那麼 75 - 29 = 36 就是字高。

另外,圖像中有可能存在噪點,去掉就是了(我這裏只是簡單粗暴的處理下,請見諒)。

行的處理同樣。如果發現間隔,那麼就可以分離字符。最後,輸出每個字符的圖像。

檢驗下效果:

list_char_imgs = segment_chars(im_core)
env.imshow(list_char_imgs[1])

4. 模板匹配:確定字符內容

利用模板匹配,實現字符識別的過程。這裏不再細說OpenCV的 cv2.matchTemplate() 函數,只描述應用過程。

4.1. make_template

首先,有必要把字符先作為模板存儲下來。

def make_tpls(list_tpl_imgs, dir_save, dict_tpl=None):
    if not dict_tpl:
        dict_tpl = {}

    str_items = input("請輸入模板上的文本內容,用於校對(例如215801): ")

    assert len(str_items) == len(list_tpl_imgs)
    for i, v in enumerate(str_items):
        filename = v
        if v in dict_tpl:
            filename = v + "_" + str(random.random())
        else:
            dict_tpl[v] = list_tpl_imgs[i]
        path_save = os.path.join(dir_save, filename + ".jpg")
        mvlib.io.imsave(path_save, list_tpl_imgs[i])

    return dict_tpl

這裏,同一字符有必要多存儲幾張,最後擇優(或者一個字符通過多個模板匹配的結果來確定)。

4.2. 模板修復

這個過程,雖然沒啥子技術含量,但卻對結果影響很大。在前一步驟中,我們每一個字符都收集了多張模板圖像。現在,從中擇優錄取。還有,可以手動編輯模板的圖片,去除模板多餘的白邊(邊並不是文字內容的一部分,而且會降低字符的匹配度)。

4.3. 重新加載模板數據

def load_saved_tpls(dir_tpl):
    saved_tpls = os.listdir(dir_tpl)

    dict_tpl = {}  # {"1": imread("mvdev/tmp/tpl/1.jpg"), ...}
    for i in saved_tpls:
        filename = os.path.splitext(i)[0]
        path_tpl = os.path.join(dir_tpl, i)

        im_rgb = cv2.imread(path_tpl)
        im_gray = mvlib.color.rgb2gray(im_rgb)
        dict_tpl[filename] = im_gray
    return dict_tpl

dir_tpl = "tpl/"
dict_tpls = load_saved_tpls(dir_tpl)

4.4. 模板匹配

def number_ocr_matching(im_char):
    most_likely = [1, ""]
    for key, im_tpl in dict_tpls.items():
        try:
            pos, similarity = mvlib.feature.match_template(im_char, im_tpl, way="most")
            if similarity < most_likely[0]:
                most_likely = [similarity, key]
        except:
            im_char_old = im_char.copy()
            h = max(im_char.shape[0], im_tpl.shape[0])
            w = max(im_char.shape[1], im_tpl.shape[1])
            im_char = np.ones((h,w), dtype="uint8") * 255
            # im_char2 = mvlib.pixel.bitwise_and(z, im_char)
            im_char[:im_char_old.shape[0], :im_char_old.shape[1]] = im_char_old

            pos, similarity = mvlib.feature.match_template(im_char, im_tpl, way="most")
            if similarity < most_likely[0]:
                most_likely = [similarity, key]

    print(f"字符識別為【{most_likely[1]}】相似度【{most_likely[0]}】")
    return most_likely[1]

def application(list_char_imgs):
    str_ocr = ""
    for im_char in list_char_imgs:
        width_img = im_char.shape[1]
        # 判斷字符
        match_char = number_ocr_matching(im_char)
        str_ocr += match_char
    return str_ocr

str_ocr2 = application(list_char_imgs)
print(str_ocr2)

過程中,opencv出現了報錯,是由於模板的shape大於當前分割字符的shape。這個很正常,採集圖像時由於距離的微調(注意,距離變化不能太大,OpenCV的默認算子不支持模板縮放)可能導致字符尺寸更小。解決方案也很簡單,直接把字符圖像拓展到大於模板的狀態就OK了。

額,忘了刪除debug信息了……再來一次~

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

【其他文章推薦】

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

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

※台北網頁設計公司全省服務真心推薦

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

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

最近學習了限流相關的算法

最近測試team在測試過程中反饋部分接口需要做一定的限流措施,剛好我也回顧了下限流相關的算法。常見限流相關的算法有四種:計數器算法, 滑動窗口算法, 漏桶算法, 令牌桶算法

1.計數器算法(固定窗口)

 計數器算法是使用計數器在周期內累加訪問次數,當達到設定的閾值時就會觸發限流策略。下一個周期開始時,清零重新開始計數。此算法在單機和分佈式環境下實現都非常簡單,可以使用Redis的incr原子自增和線程安全即可以實現

 這個算法常用於QPS限流和統計訪問總量,對於秒級以上周期來說會存在非常嚴重的問題,那就是臨界問題,如下圖:

 假設我們設置的限流策略時1分鐘限制計數100,在第一個周期最後5秒和第二個周期的開始5秒,分別計數都是88,即在10秒時間內計數達到了176次,已經遠遠超過之前設置的閾值,由此可見,計數器算法(固定窗口)限流方式對於周期比較長的限流存在很大弊端。

 Java 實現計數器(固定窗口):

package com.brian.limit;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

import lombok.extern.slf4j.Slf4j;

/**
 * 固定窗口
 */
@Slf4j
public class FixWindow {

    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

    private final int limit = 100;

    private AtomicInteger currentCircleRequestCount = new AtomicInteger(0);

    private AtomicInteger timeCircle = new AtomicInteger(0);

    private void doFixWindow() {
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            log.info(" 當前時間窗口,第 {} 秒 ", timeCircle.get());
            if(timeCircle.get() >= 60) {
                timeCircle.set(0);
                currentCircleRequestCount.set(0);
                log.info(" =====進入新的時間窗口===== ");
            }
            if(currentCircleRequestCount.get() > limit) {
                log.info("觸發限流策略,當前窗口累計請求數 : {}", currentCircleRequestCount);
            } else {
                final int requestCount = (int) ((Math.random() * 5) + 1);
                log.info("當前發出的 ==requestCount== : {}", requestCount);
                currentCircleRequestCount.addAndGet(requestCount);
            }
           timeCircle.incrementAndGet();
        }, 0, 1, TimeUnit.SECONDS);
    }

    public static void main(String[] args) {
        new FixWindow().doFixWindow();
    }
    
}

2.滑動窗口算法

 滑動窗口算法是將時間周期拆分成N個小的時間周期,分別記錄小周期裏面的訪問次數,並且根據時間的滑動刪除過期的小周期。如下圖,假設時間周期為1分鐘,將1分鐘再分為2個小周期,統計每個小周期的訪問數量,則可以看到,第一個時間周期內,訪問數量為92,第二個時間周期內,訪問數量為104,超過100的訪問則被限流掉了。

 

 由此可見,當滑動窗口的格子劃分的越多,那麼滑動窗口的滾動就越平滑,限流的統計就會越精確。此算法可以很好的解決固定窗口算法的臨界問題。

  Java實現滑動窗口:

package com.brian.limit;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import lombok.extern.slf4j.Slf4j;

/**
 * 滑動窗口
 * 
 * 60s限流100次請求
 */
@Slf4j
public class RollingWindow {

    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

    // 窗口跨度時間60s
    private int timeWindow = 60;

    // 限流100個請求
    private final int limit = 100;

    // 當前窗口請求數
    private AtomicInteger currentWindowRequestCount = new AtomicInteger(0);

    // 時間片段滾動次數
    private AtomicInteger timeCircle = new AtomicInteger(0);

    // 觸發了限流策略后等待的時間
    private AtomicInteger waitTime = new AtomicInteger(0);

    // 在下一個窗口時,需要減去的請求數
    private int expiredRequest = 0;

    // 時間片段為5秒,每5秒統計下過去60秒的請求次數
    private final int slidingTime = 5;

    private ArrayBlockingQueue<Integer> slidingTimeValues = new ArrayBlockingQueue<>(11);

    public void rollingWindow() {
        scheduledExecutorService.scheduleWithFixedDelay(() -> {

            if (waitTime.get() > 0) {
                waitTime.compareAndExchange(waitTime.get(), waitTime.get() - slidingTime);
                log.info("=====當前滑動窗口===== 限流等待下一個時間窗口倒計時: {}s", waitTime.get());
                if (currentWindowRequestCount.get() > 0) {
                    currentWindowRequestCount.set(0);
                }
            } else {
                final int requestCount = (int) ((Math.random() * 10) + 7);
                if (timeCircle.get() < 12) {
                    timeCircle.incrementAndGet();
                }
                
            log.info("當前時間片段5秒內的請求數: {} ", requestCount);
            currentWindowRequestCount.addAndGet(requestCount);
            log.info("=====當前滑動窗口===== {}s 內請求數: {} ", timeCircle.get()*slidingTime , currentWindowRequestCount.get());

            if(!slidingTimeValues.offer(requestCount)){
                expiredRequest =  slidingTimeValues.poll();
                slidingTimeValues.offer(requestCount);
            } 

            if(currentWindowRequestCount.get() > limit) {
                // 觸發限流
                log.info("=====當前滑動窗口===== 請求數超過100, 觸發限流,等待下一個時間窗口 ");
                waitTime.set(timeWindow);
                timeCircle.set(0);
                slidingTimeValues.clear();
            } else {
                // 沒有觸發限流,滑動下一個窗口需要,移除相應的:在下一個窗口時,需要減去的請求數
                log.info("=====當前滑動窗口===== 請求數 <100, 未觸發限流,當前窗口請求總數: {},即將過期的請求數:{}"
                        ,currentWindowRequestCount.get(), expiredRequest);
                currentWindowRequestCount.compareAndExchange(currentWindowRequestCount.get(), currentWindowRequestCount.get() - expiredRequest);
            }
        }   
        }, 5, 5, TimeUnit.SECONDS);
    }

    public static void main(String[] args) {
        new RollingWindow().rollingWindow();
    }
    

}

計數器(固定窗口)和滑動窗口區別:

計數器算法是最簡單的算法,可以看成是滑動窗口的低精度實現。滑動窗口由於需要存儲多份的計數器(每一個格子存一份),所以滑動窗口在實現上需要更多的存儲空間。也就是說,如果滑動窗口的精度越高,需要的存儲空間就越大。

3.漏桶算法

 漏桶算法是訪問請求到達時直接放入漏桶,如當前容量已達到上限(限流值),則進行丟棄(觸發限流策略)。漏桶以固定的速率進行釋放訪問請求(即請求通過),直到漏桶為空。

 Java實現漏桶:

package com.brian.limit;

import java.util.concurrent.*;

import lombok.extern.slf4j.Slf4j;

/**
 * 漏桶算法
 */
@Slf4j
public class LeakyBucket {
    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

    // 桶容量
    public  int capacity = 1000;
    
    // 當前桶中請求數
    public int curretRequest = 0;

    // 每秒恆定處理的請求數
    private final int handleRequest = 100;

    public void doLimit() {
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            final int requestCount = (int) ((Math.random() * 200) + 50);
            if(capacity > requestCount){
                capacity -= requestCount;
                log.info("<><>當前1秒內的請求數:{}, 桶的容量:{}", requestCount, capacity);
                if(capacity <=0) {
                    log.info(" =====觸發限流策略===== ");
                } else {
                    capacity += handleRequest;
                    log.info("<><><><>當前1秒內處理請求數:{}, 桶的容量:{}", handleRequest, capacity);
                }
            } else {
                log.info("<><><><>當前請求數:{}, 桶的容量:{},丟棄的請求數:{}", requestCount, capacity,requestCount-capacity);
                if(capacity <= requestCount) {
                    capacity = 0;
                }
                capacity += handleRequest;
                log.info("<><><><>當前1秒內處理請求數:{}, 桶的容量:{}", handleRequest, capacity);
            }
        }, 0, 1, TimeUnit.SECONDS);
    }

    public static void main(String[] args) {
        new LeakyBucket().doLimit();
    }
}

 漏桶算法有個缺點:如果桶的容量過大,突發請求時也會對後面請求的接口造成很大的壓力。

4.令牌桶算法

 令牌桶算法是程序以恆定的速度向令牌桶中增加令牌,令牌桶滿了之後會丟棄新進入的令牌,當請求到達時向令牌桶請求令牌,如獲取到令牌則通過請求,否則觸發限流策略。

 

 Java實現令牌桶:

package com.brian.limit;

import java.util.concurrent.*;

import lombok.extern.slf4j.Slf4j;
/**
 * 令牌桶算法
 */
@Slf4j
public class TokenBucket {
    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);

    // 桶容量
    public  int capacity = 1000;
    
    // 當前桶中請求數
    public int curretToken = 0;

    // 恆定的速率放入令牌
    private final int tokenCount = 200;

    public void doLimit() {
        scheduledExecutorService.scheduleWithFixedDelay(() -> {
            
            new Thread( () -> {
                if(curretToken >= capacity) {
                    log.info(" =====桶中的令牌已經滿了===== ");
                    curretToken = capacity;
                } else {
                    if((curretToken+tokenCount) >= capacity){
                      log.info(" 當前桶中的令牌數:{},新進入的令牌將被丟棄的數: {}",curretToken,(curretToken+tokenCount-capacity));
                      curretToken = capacity;
                  } else {
                      curretToken += tokenCount;
                  }
                }
            }).start();

            new Thread( () -> {
                final int requestCount = (int) ((Math.random() * 200) + 50);
                if(requestCount >= curretToken){
                    log.info(" 當前請求數:{},桶中令牌數: {},將被丟棄的請求數:{}",requestCount,curretToken,(requestCount - curretToken));
                    curretToken = 0;
                } else {
                    log.info(" 當前請求數:{},桶中令牌數: {}",requestCount,curretToken);
                    curretToken -= requestCount;
                }
            }).start();
        }, 0, 500, TimeUnit.MILLISECONDS);
    }

    public static void main(String[] args) {
        new TokenBucket().doLimit();
    }
    
}

漏桶算法和令牌桶算法區別:

令牌桶可以用來保護自己,主要用來對調用者頻率進行限流,為的是讓自己不被打垮。所以如果自己本身有處理能力的時候,如果流量突發(實際消費能力強於配置的流量限制),那麼實際處理速率可以超過配置的限制。而漏桶算法,這是用來保護他人,也就是保護他所調用的系統。主要場景是,當調用的第三方系統本身沒有保護機制,或者有流量限制的時候,我們的調用速度不能超過他的限制,由於我們不能更改第三方系統,所以只有在主調方控制。這個時候,即使流量突發,也必須捨棄。因為消費能力是第三方決定的。
總結起來:如果要讓自己的系統不被打垮,用令牌桶。如果保證被別人的系統不被打垮,用漏桶算法

 

參考博客:https://blog.csdn.net/weixin_41846320/article/details/95941361

     https://www.cnblogs.com/xuwc/p/9123078.html

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

【其他文章推薦】

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

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準