老鼠斷糧生死鬥:美國疫情封鎖引爆的「鼠患警報」

摘錄自2020年5月26日聯合報報導

美國疾病管制中心(CDC)上星期對全美發出了「鼠患警報」,指在連續多月的防疫封鎖下,全國各地——特別是都會區的城中地段——紛紛出現了鼠患通報數量異常增加的情況。因為都會鼠群倚賴的餐飲業垃圾與商業區廚餘系統,已因防疫歇業而「斷糧多時」,因此除了鼠群大舉遷移、大膽深入民宅區外,部分鼠群更因生存壓力而出現互食、噬崽、以及鼠群格鬥的「暴力傾向」。

長期以來,以紐約為首的大型城市,都一直苦於城市鼠患的問題。但在2020年初,武漢肺炎疫情的封鎖之下,全國各地的衛生單位、防治機關,都紛紛接到了「大量新增」的居民鼠患通報。相關狀態,自3月中旬陸續爆發,但到了5月下旬,美國疾管中心才終於發出了「鼠患警報」,提醒美國民眾應該注意居家衛生與糧食囤積的安全,以避免養鼠為患、或遭遇那批從都心防疫區裡竄出求生的「侵略性鼠群」。

CDC表示,在近期的鼠患通報中,各級單位不僅發現了「鼠群侵門踏戶」不畏人類的機率大增;治鼠專家們也紛紛觀察到,都會區鼠群出現了「鼠吃鼠」、「鼠群相殺」與「嗜食幼鼠」的極端狀況。種種跡象皆顯示,被人類防疫行為嚴重影響的都會區老鼠,正因生存壓力而在劇烈地轉變集體行為。

生活環境
國際新聞
美國
老鼠
動物與大環境變遷

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

【其他文章推薦】

※回頭車貨運收費標準

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

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

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

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

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

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

這些5萬就能買到的超帥SUV!別告訴我你還沒關注…

對於年輕人來說,沒有比在某個周末的午後跟秋名山上的五菱宏光來一場速度與激情更讓人愉快了。雖然東南DX3的配置也很屌(全系標配ESp車身穩定系統),但光一副漂亮的臉蛋跟一副超運動的底盤就足夠讓各位年輕人買單了。一本真經的騷長安CS15廠商指導價:5。

作為祖國的花朵,作為人類的未來,年輕人自個買一輛又酷又炫的SUV作為代步的工具是一件天經地義的事情。然鵝,途觀、CX-5那些動輒十多萬的起步價實在是讓一眾騷年的錢包羞澀。難道年輕人擁有一輛“狂拽酷炫屌炸天”的SUV就這麼難?難道上天就這般刻薄我們祖國的花朵們?!當然不是!下面就讓我給大家推薦幾款年輕人都能夠輕鬆擁有的SUV車型!

狂拽酷炫屌的騷

比亞迪元

廠商指導價:5.99-24.98萬

犀利的開眼角大燈搭上展翼式的鍍鉻飾條,整個前臉頗有幾分梁朝偉式的帥氣。車身的側面富有心思地運用了三腰線的設計,動感、立體,彷彿是見人就懟的姿勢。更要命的是,大膽前衛的雙色車身設計,簡直是街上少女們的最愛。從設計的角度來說,這是一輛能夠幫你解決單身問題的SUV。

當然,年輕人用車也是很講究的。配置絕不能馬虎。你只需要付出6.99萬的代價,就能夠得到一鍵啟動、無匙進入、ESp車身穩定系統、陡坡緩降、自動大燈、大燈延時關閉、自動空調等一堆看起來極屌的配置。要是你是一個對裝逼孜孜不倦的人,那你還可以多加1萬,然後再得到前排側氣囊、胎壓監測、定速巡航、全景天窗、外后視鏡加熱等屌得不能再屌的配置。連廣告語我都幫比亞迪想好了:比亞迪元,決不在你裝逼的時候掉鏈子。

瑪莎拉蒂式的騷

東南DX3

廠商指導價:6.79-9.99萬

雖然比亞迪已經很努力地為中國年輕人的裝逼事業而奮鬥,但是另一廂的東南汽車也並沒有閑着,跟世界著名的汽車設計公司賓尼法利納一起倒騰出東南DX3。這個賓尼法利納得着重提及一下:作為世界最著名的汽車設計公司,瑪莎拉蒂總裁、法拉利599GTB等牛逼車型都出自它之手。

就跟賓尼法利納這個名字一樣,東南DX3也採用了一個相當個性的外觀設計。細長的進氣中網、精緻的大燈結構、超級上揚的動感腰線,再搭配上跟比亞迪元同款的雙色車身設計,想想就足夠地讓人雞凍。哪怕在多年以後,你依然能夠抬起頭驕傲地跟其他人說:我這車,賓尼法利納設計的。當然,東南還很逆天地給東南DX3配上了一副前麥弗遜后多連桿的底盤,完全是稱霸同級的節奏。對於年輕人來說,沒有比在某個周末的午後跟秋名山上的五菱宏光來一場速度與激情更讓人愉快了。雖然東南DX3的配置也很屌(全系標配ESp車身穩定系統),但光一副漂亮的臉蛋跟一副超運動的底盤就足夠讓各位年輕人買單了。

一本真經的騷

長安CS15

廠商指導價:5.79-7.79萬

CS75、CS35賣得那麼火,長安不搞個小型SUV怎麼能說得過去?!於是乎長安CS15就來了。和它的老大哥們一樣,CS15依舊很好地繼承了長安一流的造車品質。尤其在行車品質的表現上,行駛時的靜澀性完全不亞於一輛合資緊湊型轎車。內飾的做工、用料等等細節,它的精緻程度也大大領先於其他的同級車型。

造型上面,CS15套了一套長安最新的家族式設計,視覺比起CS35都要更顯動感、幹練一些。尤其前臉的擴散式中網,是同級車型里相當少見的設計。搭配上開眼角大燈,一下子把前臉的立體感增強不少。這種深邃又迷人的設計,同樣的價位,你只能在CS15上面看到了。此外,長安的設計師真的是骨子里的騷包。還給CS15配了4個花瓣式的雙色輪轂,效果完全等同於AJ限量版上腳,那種活潑、騷包的氣息撲面而來。如果你又想騷、又想靠譜,還想價格不高,我想CS15絕壁是一個極好的選擇。

總結

我就是我,是顏色不一樣的煙火!誰說年輕人買不起SUV的?!誰說年輕人就應該刻苦耐勞,走路上班的?!如今,你只需要5.79萬就能夠過上有車一族!去你想去的地方,泡你想泡的女神。看到這裏,難道你還在壓抑自己的內心?!趕緊買買買買買買買買買啊!本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

17萬起買個性SUV,這款美系SUV是不是真的比奇駿更值得買?

但對於指南者來說,2706mm的軸距絕對是碾壓它的2636mm軸距。日產逍客官方指導價:13。98-18。98萬此次指南者真正對手應該是逍客,但是1。4T的發動機註定是和2。0L發動機相對比的,動力方向相差無幾,不過在軸距上逍客以着10mm的距離小勝指南者。

前言

對於美系車,我們一向都有着這樣的認識,隔音好而且舒適,但價格水平卻一點都不接地氣。但是最近作為硬派SUV代表品牌的JEEp公布了最新款的指南者售價,價格竟然和一向走低價路線的日系車一樣,那麼這款jeep指南者究竟何方神聖?是否值得買呢?

Jeep指南者車身尺寸為4415*1819*1625,身材上要比起奇駿、CR-V這些對手要小上一圈。定位是一款緊湊型SUV,預售價為17到24萬之間。

外觀上非常硬朗霸氣,整體和大切諾基相似,採用了家族式的7孔進氣格柵,並且在視覺上和大燈融為一體,拉伸了整車的視覺寬度。

整車下部有着一圈塑料裝飾,可以避免小蹭刮后的補漆。車尾造型相對於以往車型來說要柔性不少,兩燈之間依然是大大的“jeep”的標識。

內飾方面則是一貫的jeep風格,比較的中庸沒有太大的特點,使用的是黑色以及米色相搭配的風格,更為適合家用以及溫馨。配置上可以看到有着自動空調、8.4英寸中控屏幕、电子手剎、一鍵啟動以及全景天窗。

在動力方面,指南者將採用的是1.4T渦輪增壓發動機或者2.4L自然吸氣發動機,前者將搭配7速雙離合變速箱,後者將搭載9AT變速箱,並且為四驅車型。

競爭對手

日產奇駿

官方指導價:18.18-26.78萬

日產奇駿在緊湊型SUV中是一個重量級选手,實力不容小覷,油耗以及空間表現優秀,不過隔音水平以及內飾用料稍顯一般。但對於指南者來說,2706mm的軸距絕對是碾壓它的2636mm軸距。

日產逍客

官方指導價:13.98-18.98萬

此次指南者真正對手應該是逍客,但是1.4T的發動機註定是和2.0L發動機相對比的,動力方向相差無幾,不過在軸距上逍客以着10mm的距離小勝指南者。

本田繽智

官方指導價:12.48-18.48萬

本田繽智是一款小型SUV,和指南者相比其實不公平,但是高配的1.8L車型價格已經和指南者相重合了,所以依然可以說是競爭對手。因為等級的關係,繽智的2610mm軸距,明顯不如指南者,而且1.8L發動機在動力上是不如1.4T發動機的,不過本田該發動機卻是更省油。

所以總的來說,這次指南者可以說是一款入門緊湊型SUV,是和着日產逍客這種定位相似。比起本田繽智以及昂科拉這類小型SUV要大上一圈,但是卻小於日產奇駿、本田C-RV這類緊湊型SUV。這次指南者上市,因為低配搭配的是1.4T發動機,售價難以下降,所以相當於是用着高配和別人競爭。這款指南者非常適合追求個性的年輕人使用,唯一就是價格應該再下降一點。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

要便宜還要檔次?8萬元白菜價的合資三廂車隨便挑

38-12。08萬元曾經的“老三樣”,是一代人的汽車情結。但如今,“老三樣”的輝煌成就已是茶餘飯後的談資。“老三樣”除了捷達和桑塔納繼續保持強大的產品號召力,接替富康的全新愛麗舍就顯得有點舉步維艱,銷量和影響力遠遠不及前面兩位老朋友的一半。

8萬元的預算買輛合資轎車,看似是一件非常不可思議的事情。但實際上,8-10萬這一細分市場早已不是合資車的天下,而是被日益強大的自主品牌所瓜分,競爭的壓力導致了入門級合資車售價區間不斷下探。8萬元,不僅能買到更有面子的合資品牌,關鍵它們還是一些實力不錯的三廂轎車。

新捷達

指導價:7.99-13.49萬元

不管大家信不信,反正的第一台車就是捷達,嗯!沒錯,就是隨處可見的捷達教練車,幾乎每一位有駕照的朋友都是被捷達車一路虐過來的。從最開始上市至今,捷達的累計銷量已經突破360萬台車。足以可見,捷達的實力派口碑可真不是蓋得的。即然如此,那就來推薦這款近期上新市的新捷達。作為中期改款后的新捷達,形象氣質完全煥然一新,相比以往刻板沉穩的印象更加年輕且運動化,但造型還是離不開你最熟悉的大眾風格。

隨之改變的還有新捷達的內飾,無論做工質感,還是時尚感均讓你懷疑這還是捷達嗎?與此同時,新捷達總算是良心發現了,配置水準大大高於現款捷達,中控台也不再是簡陋的不成樣,而是換裝了更有逼格的大尺寸显示屏,部分車型還增加了胎壓監測,ESp,倒車雷達等實用配置,空間還一如既往的出色。總體來說,新款捷達的性價比表現進步神速。真是厲害了,我的捷達哥!

科沃茲

指導價:7.99-10.99萬元

科沃茲和科魯茲,傻傻分不清。不僅取名方式非常雷同,設計方面也找到一些共同點。雖然科沃茲長得沒有科魯茲那麼有個性,但至少在很多年輕人看來,科沃茲的外觀設計還是比較成功的,以致於甚至你可能猜不到它的起步價只要7.99萬,頂配也不超過11萬,試問這樣有吸引力的價格,又能照顧你面子的合資轎車,國產轎車還怎麼活?

值得一提的是,儘管科沃茲是台中國特供車,但你別否認,其實打着特供車的名義殺入家用車市場,銷量真的差不到哪去,10月份科沃茲的銷量已經反超大哥科魯茲了,也說明科沃茲這款新車方方面面都有不俗的實力。其中,檔次感和配置是它最大的產品亮點,時尚有質感內飾可以輕易俘虜年輕人的心。配置方面,除了最低配感覺讓人稍微不值之外,其它款車型給出的配置已經非常厚道了,好嗎?綜合對比同級競品來說,科沃茲是少有的全能选手,不失為年輕人的入門家用車的首選。

愛麗舍

指導價:8.38-12.08萬元

曾經的“老三樣”,是一代人的汽車情結。但如今,“老三樣”的輝煌成就已是茶餘飯後的談資。“老三樣”除了捷達和桑塔納繼續保持強大的產品號召力,接替富康的全新愛麗舍就顯得有點舉步維艱,銷量和影響力遠遠不及前面兩位老朋友的一半。而從今天的審美觀來看,愛麗舍雖然不是走那種很驚艷的設計路線,在同級別中屬於很夠看的,辨識度也不錯。不過,讓人不爽的是,與時尚外觀形成巨大落差的內飾感實在太low了。

但總的來說,憑藉日積月累的口碑效應,過硬的可靠性,以及同級中一流的動力性和底盤調校,尤其是遭人詬病的4AT變速箱換成6AT變速箱之後,全新愛麗舍產品力得到進一步提升。同時,再結合一個不高不低的售價區間,愛麗舍還是一款非常靠譜的合資轎車。但也有一些不得不提的槽點,比如令人抓狂的風噪胎噪,以及不夠人性化的設計。當然,相比同級的德系車型,愛麗舍的性價比優勢更突出。

鋒范

指導價:7.98-11.98萬元

在8萬元的價位上,可能更多的人會考慮飛度。但如果介意飛度是台兩廂車的話,那麼退而求其次,購買三廂車鋒范無疑是最折中的選擇。當然,鋒范的銷量也並不差,精緻動感的車身造型,迎合了年輕人的審美觀念。但真正讓人佩服的是本田的地球夢發動機技術!無論是動力性還是燃油性,“黑科技”傍手的鋒范都有可圈可點的表現,其可靠性和經濟性足夠省心省力。

此外,要論空間的話,鋒范同樣不甘落後。繼承了本田MM理念的鋒范,軸距2600mm的車內空間,卻有十足的越級表現,即使滿載5位成年人,鋒范也是應付得游刃有餘。最後,再說說人家的性價比,一般是不建議入手本田車型的最低配版,怎麼說?簡直寒酸到不忍直視,鋒范也毫不例外,不僅外觀內飾有區別對待,居然連一些很基礎的配置如收音機、遙控鑰匙都沒有。拋開鋒范在最低配車的不厚道,其次低配、中配版都能讓人心滿意足。所以,鋒范作為不追求配置年輕人的代步車,也是完全稱職的。

手裡有8萬元的購車預算,還是會陷入比較尷尬的兩難,往往是高不成低不就。心裏想着買好點的合資車,但錢包不給力。退一步買性價比更高的國產車,又有些心不甘情不願。如果有類似這樣疑慮的朋友,不妨考慮推薦的這四款入門合資轎車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

※回頭車貨運收費標準

5-10萬的這些合資轎車男女老少開着都不丟面

3L 91馬力+5擋手動,1。5L 103馬力+5擋手動/4擋自動。這款來自鈴木的小車也很招男士的待見,因為鈴木的小排量發動機做的很不錯,不管是起步還是加速、超車,你踩多少油門,就會給你多少加速度,這種积極的動力響應讓人慾罷不能。

如果家裡只有一台車子,但是幾個人都有駕照,都會開車,那麼買台車子就不能僅僅只考慮一個人的感受了,而是需要考慮家裡所有人的考慮了。

有些車子雖然很不錯,但是可能不適合女人開,有些車子適合女人開但是看着太娘了,也不適合男士開,所以今天給大家推薦幾款男女老少都適合的車子,看看那款是你的菜。

長安福特-嘉年華

7.99-12.29萬

福特嘉年華的兩廂版和三廂的版的車身尺寸分別為3970*1722*1470mm、4320*1722*1470mm,軸距統一為2495mm。嘉年華屬於合資小型車。之所以說男女都適合開嘉年華,那是因為嘉年華屬於小巧的兩廂車,女士開着沒有絲毫的違和感,同時馬丁臉的前進氣格柵和炯炯有神的前大燈組使得嘉年華看起來多了幾分陽剛之氣,所以說就算你是一個威武的男人,這個看起來略顯霸氣的車型也很適合你。

嘉年華的動力系統為1.5L 110馬力+5擋手動/6擋雙離合,1.0T 125馬力+6擋雙離合。對於喜歡操控的消費者來說,嘉年華硬朗的底盤,精準的轉向不會讓你失望的。

最後,不管是男人還是女人,盡量買兩廂版的嘉年華吧。因為三廂版的嘉年華看起來不是那麼的協調。

長安鈴木-雨燕

5.98-8.28萬

雨燕的車身尺寸為3765*1690*1510mm,軸距為2390mm,雖然和嘉年華一樣也是定位為小型兩廂車,但是雨燕的“身板”確實夠較小的。雨燕看起來有點像“MINI”的感覺,造型比較可愛。但是可不能被他的外觀蒙蔽了,覺得它適合女士不適合男士。

雨燕確實很討女性消費者的喜歡,但是男士對他的愛一點也不少。雨燕的的動力系統為1.3L 91馬力+5擋手動,1.5L 103馬力+5擋手動/4擋自動。這款來自鈴木的小車也很招男士的待見,因為鈴木的小排量發動機做的很不錯,不管是起步還是加速、超車,你踩多少油門,就會給你多少加速度,這種积極的動力響應讓人慾罷不能。不過雨燕的缺點就是車型較老,配置太寒磣了,畢竟價格有限。

上汽大眾-pOLO

7.59-14.69萬

pOLO共有三種車身尺寸,1.4L和1.6L車型的車身尺寸為3970*1682*1462mm、軸距都為2470mm。比起時尚嘉年華和呆萌的雨燕,pOLO看起來則“正經”許多,外觀造型中規中矩,因為這是一台大眾車。

內飾也是大眾常見的風格,雖然中控的布局很實用,但是看久了也會看煩的。其實在這個級別,pOLO才是銷量王者。這個車子是正兒八經的男女老少通吃,畢竟這個造型很中庸的pOLO會符合更多人的審美。pOLO主銷的車型為1.4L和1.6L車型,尤其是1.4L車型,畢竟這款車型更經濟實惠,同時動力又可以滿足日常使用,關鍵是質量還很好。

總結:嘉年華的外觀更張揚,車子很靈活,操控也挺不錯的,不過油耗表現不是特別好。雨燕動力響應很好,價格實惠,油耗很低,但是配置一般。pOLO外觀更中庸,口碑也很好,省心耐用。總體來說,它們都挺適合家用的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

新款五菱之光和三折的奧迪該買誰好?

五菱之光V新車發布日前我們從上汽通用五菱官網獲悉,五菱之光V車型正式上市。不過這個V和凱迪拉克並沒有什麼關係,新車共推出3款車型,售價區間為3。38-3。68萬元。動力方面均搭載1。2L自然吸氣,最大功率為82馬力,峰值扭矩116牛·米,與之匹配的是5速手動變速器。

新款捷達,眾泰看到都不想拿出皮尺

就在本月7號,神車中的神車捷達再次發布新款,作為中期改款,外觀和內飾並沒有明顯改動,可能連眾泰看到了,都不屑於拿出自己的皮尺。

(車身尺寸4501*1704*1469,軸距2604mm,)而動力方面除了1.4TSI和1.4LMpI還增加了全新的1.5L MpI發動機,最大功率81Kkw,最大扭矩150牛米,匹配5速手動和6速自動變速箱,配置上稍有提升(中控台換裝了一塊尺寸更大的液晶显示屏,並配備有USB、SD卡以及AUX接口。

此外,新車還換裝了新的三輻式多功能方向盤),根據車型的不同還配備有自動空調、座椅加熱、胎壓監測、車身穩定控制以及全皮座椅等。

老捷達陪了國人二十幾年的情懷不是所有人都懂,但是現在的捷達(老款pOLO平台)每個月還2萬多的銷量,真的只能說這個車比較高級。

途觀L 15號首發 明年1月上市

目前,據最新消息稱,上汽大眾全新途觀L將於今年12月15日首發亮相,並有望於明年1月正式上市。從曝光的圖片來看,新車的前臉基本與海外版途觀保持一致,其延續了最新家族式的設計,車身側面海外版相比則有明顯的加長,後窗的三角窗面積也更大一些。

車身尺寸(長寬高4712/1839/1673mm,)相比海外版Tiguan的車身長度加長了226mm,同時軸距增長了110mm。達到了2791mm,儘管如此車內仍採用五座布局,動力方面,將搭載1.4TSI、1.8TSI、和2.0TSI的高低功率版3款發動機可選,最大功率分別為110kW(150ps)、132kW(179ps)、147kW(200ps)/162kW(220ps),與3款發動機匹配的均是7速DSG雙離合變速箱。

此外1.4TSI版途觀L則將保留手動擋車型。高配車型還將搭載四驅系統,而配置方面還有HUD抬頭显示、自動大燈、全景天窗、一鍵啟動、电子駐車、自動啟停、自動泊車等。這麼拽的配置還加長,到時候你不加價十幾萬我都不好意思提車啊。

五菱之光V新車發布

日前我們從上汽通用五菱官網獲悉,五菱之光V車型正式上市。不過這個V和凱迪拉克並沒有什麼關係,新車共推出3款車型,售價區間為3.38-3.68萬元。

動力方面均搭載1.2L自然吸氣,最大功率為82馬力,峰值扭矩116牛·米,與之匹配的是5速手動變速器。整車(新車的長寬高分別為4051/1575/1780毫米)軸距達到2600毫米。

其中最重要的是一改以往的中置后驅採用前置后驅的布局,除了發動機的噪音和散熱有所改善,新的布局也將令車頭有更長的緩衝區,這以後五菱宏光S的山路傳說估計要換主角的了。

天津港口奧迪事故車三折開賣

近日,據一汽-大眾內部員工爆料稱,公司內部發布了《天津港事件輕微受損進口奧迪車銷售公告》。內部員工只需3折的價格就可購買奧迪車。公告显示,一汽服貿汽車銷售中心將於12月6日至14日銷售一批國四排放進口奧迪車,此批車受2015年8月12日天津港事件輕微影響(在車庫中停放,未受到直接影響)。

車型包括(A1、A4allroad、A5、A7、S5、S6、S7、SQ5、Q5、Q7、TTS、R8)大部分進口車型。價格為市場指導價的3-5折,而且需要全款購車。此外,購車后需在2016年12月15日前落籍上牌,還需封存車籍四年。有網友表示奧迪真黑心,事故車還拿來賣,不過更多的網友則是更關心三折的奧迪哪裡有得賣?我先來一台R8。

奔馳C63被深圳交警認定為改裝車

一直以來深圳的各項交通法規都是走在全國最前線,像安全座椅和後排安全帶的強制性要求等,而深圳的交警更是妙招奇多,特別是治遠光狗的辦法都讓全國人民拍手稱快。

不過他們最近一條微博引起了車友的熱議,內容大致上整治改裝車行動,查獲非法改裝車7輛,最終全部查扣,而從照片和微信聊天記錄中我們可以看到其中有原裝的C63 AMG也被查扣,這TM就尷尬了,國內確實沒有合法改裝,那些非法改裝抓你的,但是連原廠車都不認識,只能對深圳交警說,要不拉你們進個猜車群?

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

曹工說JDK源碼(3)–ConcurrentHashMap,Hash算法優化、位運算揭秘

hashcode,有點講究

什麼是好的hashcode,一般來說,一個hashcode,一般用int來表示,32位。

下面兩個hashcode,大家覺得怎麼樣?

0111 1111 1111 1111 1111 1111 1111 1111  ------A
1111 1111 1111 1111 1111 1111 1111 1111  ------B

只有第32位(從右到左)不一樣,好像也沒有所謂的好壞吧?

那,我們再想想,hashcode一般怎麼使用呢?在hashmap中,由數組+鏈表+紅黑樹組成,其中,數組乃重中之重,假設數組長度為2的n次方,(hashmap的數組,強制要求長度為2的n次方),這裏假設為8.

大家又知道,hashcode 對 8 取模,效果等同於 hashcode & (8 – 1)。

那麼,前面的A 和 (8 – 1)相與的結果如何呢?

0111 1111 1111 1111 1111 1111 1111 1111  ------A
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
    相與
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7

結果為7,也就是,會放進array[7]。

大家再看B的計算過程:

1111 1111 1111 1111 1111 1111 1111 1111  ------B
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
    相與
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7

雖然B的第32位為1,但是,奈何和我們相與的隊友,7,是個垃圾。

前面的高位,全是0。

ok,你懂了嗎,數組長度太小了,才8,導致前面有29位都是0;你可能覺得一般容量不可能這麼小,那假設容量為2的16次方,容量為65536,這下不是很小了吧,但即使如此,前面的16位也是0.

所以,問題明白了嗎,我們計算出來的hashcode,低位相同,高位不同;但是,因為和我們進行計算的隊友太過垃圾,導致我們出現了hash衝突。

ok,我們怎麼來解決這個問題呢?

我們能不能把高位也參与計算呢?自然,是可以的。

hashmap中如何優化

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

這裏,其實分了3個步驟:

  1. 計算hashcode,作為操作數1

    h = key.hashCode()
    
  2. 將第一步的hashcode,右移16位,作為操作數2

    h >>> 16
    
  3. 操作數1 和 操作數2 進行異或操作,得到最終的hashcode

還是拿前面的來算,

0111 1111 1111 1111 1111 1111 1111 1111  ------A
0000 0000 0000 0000 0111 1111 1111 1111   ----- A >>> 16
          異或(相同則為0,否則為1)
0111 1111 1111 1111 1000 0000 0000 0000    --- 2147450880  

這裏算出來的結果是 2147450880,再去對 7 進行與運算:

0111 1111 1111 1111 1000 0000 0000 0000    --- 2147450880  
0000 0000 0000 0000 0000 0000 0000 0111  ------ 8 -1
          與運算
0000 0000 0000 0000 0000 0000 0000 0000  ------ 0    

這裏的A,算出來,依然在array[0]。

再拿B來算一下:

1111 1111 1111 1111 1111 1111 1111 1111  ------ B
0000 0000 0000 0000 1111 1111 1111 1111   ----- B >>> 16
          異或(相同則為0,否則為1)
1111 1111 1111 1111 0000 0000 0000 0000    --- -65536
0000 0000 0000 0000 0000 0000 0000 0111  ------ 7   
         與運算
0000 0000 0000 0000 0000 0000 0000 0000  ------- 0    

最終算出來為0,所以,應該放在array[0]。

恩?算出來兩個還是衝突了,我只能說,我挑的数字真的牛逼,是不是該去買彩票啊。。

總的來說,大家可以多試幾組數,下邊提供下源代碼:

public class BinaryTest {
    public static void main(String[] args) {
        int a = 0b00001111111111111111111111111011;
        int b = 0b10001101111111111111110111111011;

        int i = tabAt(32, a);
        System.out.println("index for a:" + i);

        i = tabAt(32, b);
        System.out.println("index for b:" + i);

    }

    static final int tabAt(int  arraySize, int hash) {

        int h = hash;
        int finalHashCode = h ^ (h >>> 16);
        int i = finalHashCode & (arraySize - 1);

        return i;
    }
}

雖然說,我測試了幾個数字,還是有些衝突,但是,你把高16位弄進來參与計算,總比你不弄進來計算要好吧。

大家也可以看看hashmap中,hash方法的註釋:

/**
 * Computes key.hashCode() and spreads (XORs) higher bits of hash
 * to lower.  Because the table uses power-of-two masking, sets of
 * hashes that vary only in bits above the current mask will
 * always collide. (Among known examples are sets of Float keys
 * holding consecutive whole numbers in small tables.)  So we
 * apply a transform that spreads the impact of higher bits
 * downward. There is a tradeoff between speed, utility, and
 * quality of bit-spreading. Because many common sets of hashes
 * are already reasonably distributed (so don't benefit from
 * spreading), and because we use trees to handle large sets of
 * collisions in bins, we just XOR some shifted bits in the
 * cheapest possible way to reduce systematic lossage, as well as
 * to incorporate impact of the highest bits that would otherwise
 * never be used in index calculations because of table bounds.
 */

裏面提到了2點:

So we apply a transform that spreads the impact of higher bits downward.

所以,我們進行了一個轉換,把高位的作用利用起來。

we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as

to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.

我們僅僅異或了從高位移動下來的二進制位,用最經濟的方式,削減系統性能損失,同樣,因為數組大小的限制,導致高位在索引計算中一直用不到,我們通過這種轉換將其利用起來。

ConcurrentHashMap如何優化

在concurrentHashMap中,其主要是:

    final V putVal(K key, V value, boolean onlyIfAbsent) {
        if (key == null || value == null) throw new NullPointerException();
        int hash = spread(key.hashCode());

這裏主要是使用spread方法來計算hash值:

    static final int spread(int h) {
        return (h ^ (h >>> 16)) & HASH_BITS;
    }

大家如果要仔細觀察每一步的二進制,可以使用下面的demo:


    static final int spread(int h) {
        	// 1
            String s = Integer.toBinaryString(h);
            System.out.println("h:" + s);
    
        	// 2
            String lower16Bits = Integer.toBinaryString(h >>> 16);
            System.out.println("lower16Bits:" + lower16Bits);
    
        	// 3
            int temp = h ^ (h >>> 16);
            System.out.println("h ^ (h >>> 16):" + Integer.toBinaryString(temp));
    
        	// 4
            int result = (temp) & HASH_BITS;
            System.out.println("final:" + Integer.toBinaryString(result));
    
    
            return result;
        }

這裏和HashMap相比,多了點東西,也就是多出來了:

& HASH_BITS;

這個有什麼用處呢?

因為(h ^ (h >>> 16))計算出來的hashcode,可能是負數。這裏,和 HASH_BITS進行了相與:

static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash
1111 1111 1111 1111 1111 1111 1111 1111   假設計算出來的hashcode為負數,因為第32位為1
0111 1111 1111 1111 1111 1111 1111 1111       0x7fffffff
    進行相與
0111 ..................................    

​ 這裏,第32位,因為0x7fffffff的第32位,總為0,所以相與后的結果,第32位也總為0 ,所以,這樣的話,hashcode就總是正數了,不會是負數。

concurrentHashMap中,node的hashcode,為啥不能是負數

當hashcode為正數時,表示該哈希桶為正常的鏈表結構。

當hashcode為負數時,有幾種情況:

ForwardingNode

此時,其hash值為:

    static final int MOVED     = -1; // hash for forwarding nodes

當節點為ForwardingNode類型時(表示哈希表在擴容進行中,該哈希桶已經被遷移到了新的臨時hash表,此時,要get的話,需要去臨時hash表查找;要put的話,是不行的,會幫助擴容)

TreeBin

    static final int TREEBIN   = -2; // hash for roots of trees

表示,該哈希桶,已經轉了紅黑樹。

擴容時的位運算

    /**
     * Returns the stamp bits for resizing a table of size n.
     * Must be negative when shifted left by RESIZE_STAMP_SHIFT.
     */
    static final int resizeStamp(int n) {
        return Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1));
    }

這裏,假設,n為4,即,hashmap中數組容量為4.

  • 下面這句,求4的二進製表示中,前面有多少個0.

    Integer.numberOfLeadingZeros(n)

    表示為32位后,如下

    0000 0000 0000 0000, 0000 0000 0000 0100

    所以,前面有29個0,即,這裏的結果為29.

  • (1 << (RESIZE_STAMP_BITS – 1)

    這一句呢,其中RESIZE_STAMP_BITS 是個常量,為16. 相當於,把1 向左移動15位。

    二進製為:

    1000 0000 0000 0000   -- 1 << 15
    

最終結果:

0000 0000 0000 0000 0000 0000 0001 1101   -- 29
0000 0000 0000 0000 1000 0000 0000 0000   -- 1 << 15
進行或運算

0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。

所以,最終結果是,

這個數,換算為10進制,為32972,是個正數。

這個數,有啥用呢?

在addCount函數中,當整個哈希表的鍵值對數量,超過sizeCtl時(一般為0.75 * 數組長度),就會觸發擴容。

java.util.concurrent.ConcurrentHashMap#addCount
    
int sc =  sizeCtl;
boolean bSumExteedSizeControl = newBaseCount >= (long) sc;
// 1
if (bContinue) {
    int rs = resizeStamp(n);
    // 2
    if (sc < 0) {
        if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
            sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
            transferIndex <= 0)
            break;
        if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
            transfer(tab, nt);
    }
    // 3
    else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                   (rs << RESIZE_STAMP_SHIFT) + 2))
        transfer(tab, null);
    newBaseCount = sumCount();
} else {
    break;
}
  • 1處,如果擴容條件滿足

  • 2處,如果sc小於0,這個sc是啥,就是前面說的sizeCtl,此時應該是等於:0.75 * 數組長度,不可能為負數

  • 3處,將sc(此時為正數),cas修改為:

    (rs << RESIZE_STAMP_SHIFT) + 2)
    

    這個數有點意思了,rs就是前面我們的resizeStamp得到的結果。

    按照前面的demo,我們拿到的結果為:

    0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。
        
    

    因為

    private static int RESIZE_STAMP_BITS = 16;
    private static final int RESIZE_STAMP_SHIFT = 32 - RESIZE_STAMP_BITS;
    

    所以,RESIZE_STAMP_SHIFT 為16.

    0000 0000 0000 0000 1000 0000 0001 1101   --  相當於把29的第一位,變成了1,其他都沒變。
    1000 0000 0001 1101 0000 0000 0000 0000 ---   左移16位,即   rs << RESIZE_STAMP_SHIFT
    1000 0000 0001 1101 0000 0000 0000 0010    -- (rs << RESIZE_STAMP_SHIFT) + 2)
    

    最終,這個數,第一位是 1,說明了,這個數,肯定是負數。

    大家如果看過其他人寫的資料,也就知道,當sizeCtl為負數時,表示正在擴容。

    所以,這裏

    if (U.compareAndSwapInt(this, SIZECTL, sc,
                                (rs << RESIZE_STAMP_SHIFT) + 2))
    

    這句話就是,如果當前線程成功地,利用cas,將sizeCtl從正數,變成負數,就可以進行擴容。

    擴容時,其他線程怎麼執行

    // 1
    if (bContinue) {
        int rs = resizeStamp(n);
        // 2
        if (sc < 0) {
            // 2.1
            if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
                sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
                transferIndex <= 0)
                break;
            // 2.2
            if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
                transfer(tab, nt);
        }
        // 3
        else if (U.compareAndSwapInt(this, SIZECTL, sc,
                                       (rs << RESIZE_STAMP_SHIFT) + 2))
            transfer(tab, null);
        newBaseCount = sumCount();
    } else {
        break;
    }
    

    此時,因為上面的線程觸發了擴容,sc已經變成了負數了,此時,新的線程進來,會判斷2處。

    2處是滿足的,會進入2.1處判斷,這裏的部分條件看不懂,大概是:擴容已經結束,就不再執行,直接break

    否則,進入2.2處,輔助擴容,同時,把sc變成sc + 1,增加擴容線程數。

總結

時間倉促,如有問題,歡迎指出。

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

【其他文章推薦】

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

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

※超省錢租車方案

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

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

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

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

python3 源碼閱讀-虛擬機運行原理

閱讀源碼版本python 3.8.3

參考書籍<<Python源碼剖析>>

參考書籍<<Python學習手冊 第4版>>

官網文檔目錄介紹

  1. Doc目錄主要是官方文檔的說明。
  2. Include:目錄主要包括了Python的運行的頭文件。
  3. Lib:目錄主要包括了用Python實現的標準庫。
  4. Modules: 該目錄中包含了所有用C語言編寫的模塊,比如random、cStringIO等。Modules中的模塊是那些對速度要求非常嚴格的模塊,而有一些對速度沒有太嚴格要求的模塊,比如os,就是用Python編寫,並且放在Lib目錄下的
  5. Objects:該目錄中包含了所有Python的內建對象,包括整數、list、dict等。同時,該目錄還包括了Python在運行時需要的所有的內部使用對象的實現。
  6. Parser:該目錄中包含了Python解釋器中的Scanner和Parser部分,即對Python源碼進行詞法分析和語法分析的部分。除了這些,Parser目錄下還包含了一些有用的工具,這些工具能夠根據Python語言的語法自動生成Python語言的詞法和語法分析器,將python文件編譯生成語法樹等相關工作。
  7. Programs目錄主要包括了python的入口函數。
  8. Python:目錄主要包括了Python動態運行時執行的代碼,裡面包括編譯、字節碼解釋器等工作。

1. 總體架構

  • Runtime Env:python運行時環境,初始化對象/類型系統(Object/Type structures),內存分配器(Memory Allocator) 和 運行時狀態信息 (Current state of Python)。運行時狀態維護了解釋器在執行字節碼時不同的狀態(如正常和異常)之間的切換動作,可以視為一個巨大而複雜的有窮狀態機。內存管理機制可參考另外一篇文章Python3 源碼閱讀 – 內存管理機制。

  • Python Core: 中間部分是python的核心—-解釋器(PyInterpreter), 也可以成為PVM。大致流程就是 先對.py程序進行此法分析,將文件輸入的源代碼或從命令行輸入的一行行python代碼切分一個個Token, 然後使用Parser進行語法分析,建立抽象語法樹(AST), Compiler根據AST生成字節碼指令集合,最後由Code Evaluator來執行這些字節碼。

  • File Groups: Python Lib庫和用戶自己的模塊包等源代碼文件

2. Run Python文件的啟動流程

Python啟動是由Programs下的python.c文件中的main函數開始執行

/* Minimal main program -- everything is loaded from the library */

#include "Python.h"
#include "pycore_pylifecycle.h"

#ifdef MS_WINDOWS
int
wmain(int argc, wchar_t **argv)
{
    return Py_Main(argc, argv);
}
#else
int
main(int argc, char **argv)
{
    return Py_BytesMain(argc, argv);
}
#endif
int
Py_Main(int argc, wchar_t **argv) {
    ...
    return pymian_main(&args);
}

static int
pymain_main(_PyArgv *args)
{
    PyStatus status = pymain_init(args);  // 初始化
    if (_PyStatus_IS_EXIT(status)) {
        pymain_free();
        return status.exitcode;
    }
    if (_PyStatus_EXCEPTION(status)) {
        pymain_exit_error(status);
    }

    return Py_RunMain();
}

2.1 初始化關鍵流程

  • 初始化一些與配置項 如:開啟utf-8模式,設置Python內存分配器
  • 初始化pyinit_core核心部分
    • 創建生命周期 pycore_init_runtime, 同時生成HashRandom
    • 初始化線程和解釋器並創建GIL鎖 pycore_create_interpreter
    • 初始化所有基礎類型,list, int, tuple等 pycore_init_types
    • 初始化sys模塊 _PySys_Create
    • 初始化內建函數或者對象,如map, None, True等 pycore_init_builtins
      • 其中包括內建的錯誤類型初始化 _PyBuiltins_AddExceptions

Python3.8 對Python解釋器的初始化做了重構PEP 587-Python初始化配置

2.2 run 相關源碼閱讀

int
Py_RunMain(void)
{
    int exitcode = 0;
	
    pymain_run_python(&exitcode);  //執行python腳本

	if (Py_FinalizeEx() < 0) {  // 釋放資源
        /* Value unlikely to be confused with a non-error exit status or
           other special meaning */
        exitcode = 120;
    }

    pymain_free();   // 釋放資源

    if (_Py_UnhandledKeyboardInterrupt) {
        exitcode = exit_sigint();
    }

    return exitcode;
}


static void
pymain_run_python(int *exitcode)
{   
    // 獲取一個持有GIL鎖的解釋器
    PyInterpreterState *interp = _PyInterpreterState_GET_UNSAFE();
    /* pymain_run_stdin() modify the config */
    ... // 添加sys_path等操作

    if (config->run_command) {
        // 命令行模式
        *exitcode = pymain_run_command(config->run_command, &cf); 
    }
    else if (config->run_module) {
        // 模塊名
        *exitcode = pymain_run_module(config->run_module, 1);
    }
    else if (main_importer_path != NULL) {
        *exitcode = pymain_run_module(L"__main__", 0);
    }
    else if (config->run_filename != NULL) {
        // 文件名
        *exitcode = pymain_run_file(config, &cf);
    }
    else {
        *exitcode = pymain_run_stdin(config, &cf);
    }

	...
}

/* Parse input from a file and execute it */ //Python/pythonrun.c
int
PyRun_AnyFileExFlags(FILE *fp, const char *filename, int closeit,
                     PyCompilerFlags *flags)
{
    if (filename == NULL)
        filename = "???";
    if (Py_FdIsInteractive(fp, filename)) {
        int err = PyRun_InteractiveLoopFlags(fp, filename, flags);  // 是否是交互模式
        if (closeit)
            fclose(fp);
        return err;
    }
    else
        return PyRun_SimpleFileExFlags(fp, filename, closeit, flags);   // 執行腳本
}

// 執行python .py文件
int
PyRun_SimpleFileExFlags(FILE *fp, const char *filename, int closeit,
                        PyCompilerFlags *flags)
{
    ...
    if (maybe_pyc_file(fp, filename, ext, closeit)) {
        FILE *pyc_fp;
        /* Try to run a pyc file. First, re-open in binary */
        ...
        v = run_pyc_file(pyc_fp, filename, d, d, flags);
    } else {
        /* When running from stdin, leave __main__.__loader__ alone */
        ...
        v = PyRun_FileExFlags(fp, filename, Py_file_input, d, d,
                              closeit, flags);
    }
    ...
}

PyObject *
PyRun_FileExFlags(FILE *fp, const char *filename_str, int start, PyObject *globals,
                  PyObject *locals, int closeit, PyCompilerFlags *flags)
{
    ...
    // // 解析傳入的腳本,解析成AST
    mod = PyParser_ASTFromFileObject(fp, filename, NULL, start, 0, 0,
                                     flags, NULL, arena); 
    ...
    // 將AST編譯成字節碼然後啟動字節碼解釋器執行編譯結果
    ret = run_mod(mod, filename, globals, locals, flags, arena);
    ...
}

// 查看run_mode
static PyObject *
run_mod(mod_ty mod, PyObject *filename, PyObject *globals, PyObject *locals,
            PyCompilerFlags *flags, PyArena *arena)
{
    ...
    // 將AST編譯成字節碼
    co = PyAST_CompileObject(mod, filename, flags, -1, arena);  
    ...

    // 解釋執行編譯的字節碼
    v = run_eval_code_obj(co, globals, locals);
    Py_DECREF(co);
    return v;
}

2.3 字節碼查看案例

新建test.py

def show(a):
    return  a


if __name__ == "__main__":
    print(show(10))

執行命令: python3 -m dis test.py

λ ppython3 -m dis test.py
  3           0 LOAD_CONST               0 (<code object show at 0x000000E7FC89E270, file "test.py", line 3>)
              2 LOAD_CONST               1 ('show')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (show)

  7           8 LOAD_NAME                1 (__name__)
             10 LOAD_CONST               2 ('__main__')
             12 COMPARE_OP               2 (==)
             14 POP_JUMP_IF_FALSE       28

  8          16 LOAD_NAME                2 (print)
             18 LOAD_NAME                0 (show)
             20 LOAD_CONST               3 (10)
             22 CALL_FUNCTION            1
             24 CALL_FUNCTION            1
             26 POP_TOP
        >>   28 LOAD_CONST               4 (None)

左邊3, 7, 8表示 test.py中的第一行和第二行,右邊表示python byte code

Include/opcode.h 發現總共有 163 個 opcode, 所有的 python 源文件(Lib庫中的文件)都會被編譯器翻譯成由 opcode 組成的 pyx 文件,並緩存在執行目錄,下次啟動程序如果源代碼沒有修改過,則直接加載這個pyx文件,這個文件的存在可以加快 python 的加載速度。普通.py文件如我們的test.py 是直接進行編譯解釋執行的,不會生成.pyc文件,想生成test.pyc 需要使用python內置的py_compile模塊來編譯該文件,或者執行命令python3 -m test.py python生成.pyc文件

嚴格意義上來說: 只有文件導入import 的情況下字節碼.pyc文件才會保存下來,__pycache__ — 《python學習手冊(第四版) Page40》

2.4 python中的code對象

字節碼在python虛擬機中對應的是PyCodeObject對象, .pyc文件是字節碼在磁盤上的表現形式。python編譯的過程中,一個代碼塊就對應一個code對象,那麼如何確定多少代碼算是一個Code Block呢? 編譯過程中遇到一個新的命名空間或者作用域時就生成一個code對象,即類或函數都是一個代碼塊,一個code的類型結構就是PyCodeObject, 參考Junnplus

/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */     // 位置參數的個數,
    int co_posonlyargcount;     /* #positional only arguments */  
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
} PyCodeObject;
Field Content Type
co_argcount Code Block 的參數個數 PyIntObject
co_posonlyargcount Code Block 的位置參數個數 PyIntObject
co_kwonlyargcount Code Block 的關鍵字參數個數 PyIntObject
co_nlocals Code Block 中局部變量的個數 PyIntObject
co_stacksize Code Block 的棧大小 PyIntObject
co_flags N/A PyIntObject
co_firstlineno Code Block 對應的 .py 文件中的起始行號 PyIntObject
co_code Code Block 編譯所得的字節碼 PyBytesObject
co_consts Code Block 中的常量集合 PyTupleObject
co_names Code Block 中的符號集合 PyTupleObject
co_varnames Code Block 中的局部變量名集合 PyTupleObject
co_freevars Code Block 中的自由變量名集合 PyTupleObject
co_cellvars Code Block 中嵌套函數所引用的局部變量名集合 PyTupleObject
co_cell2arg N/A PyTupleObject
co_filename Code Block 對應的 .py 文件名 PyUnicodeObject
co_name Code Block 的名字,通常是函數名/類名/模塊名 PyUnicodeObject
co_lnotab Code Block 的字節碼指令於 .py 文件中 source code 行號對應關係 PyBytesObject
co_opcache_map python3.8新增字段,存儲字節碼索引與CodeBlock對象的映射關係 PyDictObject

2.4.1 LOAD_CONST

// Python\ceval.c
PREDICTED(LOAD_CONST);     -> line 943: #define PREDICTED(op)           PRED_##op:
FAST_DISPATCH();           -> line 876 #define FAST_DISPATCH() goto fast_next_opcode

額外收穫: c 語言中 ##和# 號 在marco 里的作用可以參考 這篇

在宏定義里, ## 被稱為連接符(concatenator) , a##b 表示將ab連接起來

a 表示把a轉換成字符串,即加雙引號,

所以LONAD_CONST這個指領根據宏定義展開如下:

case TARGET(LOAD_CONST): {
    PRED_LOAD_CONST:
    PyObject *value = GETITEM(consts, oparg); // 獲取一個PyObject* 指針對象
    Py_INCREF(value);  // 引用計數加1
    PUSH(value);     // 把剛剛創建的PyObject* push到當前的frame的stack上, 以便下一個指令從這個 stack 上面獲取
    goto fast_next_opcode;

2.5 main_loop

// Python\ceval.c
main_loop:
    for (;;) {
        ...
            
        switch (opcode) {
 
        /* BEWARE!
           It is essential that any operation that fails must goto error
           and that all operation that succeed call [FAST_]DISPATCH() ! */
 
        case TARGET(NOP): {
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_FAST): {
            PyObject *value = GETLOCAL(oparg);
            if (value == NULL) {
                format_exc_check_arg(PyExc_UnboundLocalError,
                                     UNBOUNDLOCAL_ERROR_MSG,
                                     PyTuple_GetItem(co->co_varnames, oparg));
                goto error;
            }
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
 
        case TARGET(LOAD_CONST): {
            PREDICTED(LOAD_CONST);
            PyObject *value = GETITEM(consts, oparg);
            Py_INCREF(value);
            PUSH(value);
            FAST_DISPATCH();
        }
        ...
    }
}

在 python 虛擬機中,解釋器主要在一個很大的循環中,不停地讀入 opcode, 並根據 opcode 執行對應的指令,當執行完所有指令虛擬機退出,程序也就結束了

2.6 總結

過程描述:

  1. python先把代碼(.py文件)編譯成字節碼,交給字節碼虛擬機,然後虛擬機會從編譯得到的PyCodeObject對象中一條一條執行字節碼指令,並在當前的上下文環境中執行這條字節碼指令,從而完成程序的執行。Python虛擬機實際上是在模擬操作中執行文件的過程。PyCodeObject對象中包含了字節碼指令以及程序的所有靜態信息,但沒有包含程序運行時的動態信息——執行環境(PyFrameObject),後面會繼續記錄執行環境的閱讀。
  2. 從整體上看:OS中執行程序離不開兩個概念:進程和線程。python中模擬了這兩個概念,模擬進程和線程的分別是PyInterpreterStatePyTreadState。即:每個PyThreadState都對應着一個幀棧,python虛擬機在多個線程上切換(靠GIL實現線程之間的同步)。當python虛擬機開始執行時,它會先進行一些初始化操作,最後進入PyEval_EvalFramEx函數,內部實現了一個main_loop它的作用是不斷讀取編譯好的字節碼,並一條一條執行,類似CPU執行指令的過程。函數內部主要是一個switch結構,根據字節碼的不同執行不同的代碼

3. Python中的Frame

如上所說,PyCodeObject對象只是包含了字節碼指令集以及程序的相關靜態信息,虛擬機的執行還需要一個執行環境,即PyFrameObject,也就是對系統棧幀的模擬。

3.1 堆和棧的認識

堆中存的是對象。棧中存的是基本數據類型和堆中對象的引用。一個對象的大小是不可估計的,或者說是可以動態變化的,但是在棧中,一個對象只對應了一個4btye的引用(堆棧分離的好處)

內存中的堆棧和數據結構堆棧不是一個概念,可以說內存中的堆棧是真實存在的物理區,數據結構中的堆棧是抽象的數據存儲結構。

內存空間在邏輯上分為三部分:代碼區,靜態數據區和動態數據區,動態數據區有分為堆區和棧區

  • 代碼區:存儲的二進制代碼塊,高級調度(作業調度)、中級調度(內存調度)、低級調度(進程調度)控制代碼區執行代碼的切換
  • 靜態數據區:存儲全局變量,靜態變量,常量,系統自動分配和回收。
  • 動態數據區:
    • 棧區(stack):存儲運行方法的形參,局部變量,返回值,有編譯器自動分配和回收,操作類似數據結構中的棧
    • 堆區(heap):new一個對象的引用或者地址存儲在棧區,該地址指向指向對象存儲在堆區中的真實數據。如c中的malloc函數,python中的Pymalloc

3.2 PyFrameObject對象

typedef struct _frame{  
    PyObject_VAR_HEAD //"運行時棧"的大小是不確定的, 所以用可變長的對象
    struct _frame *f_back; //執行環境鏈上的前一個frame,很多個PyFrameObject連接起來形成執行環境鏈表  
    PyCodeObject *f_code; //PyCodeObject 對象,這個frame就是這個PyCodeObject對象的上下文環境  
    PyObject *f_builtins; //builtin名字空間  
    PyObject *f_globals;  //global名字空間  
    PyObject *f_locals;   //local名字空間  
    PyObject **f_valuestack; //"運行時棧"的棧底位置  
    PyObject **f_stacktop;   //"運行時棧"的棧頂位置  
    //...  
    int f_lasti;  //上一條字節碼指令在f_code中的偏移位置  
    int f_lineno; //當前字節碼對應的源代碼行  
    //...  
      
    //動態內存,維護(局部變量+cell對象集合+free對象集合+運行時棧)所需要的空間  
    PyObject *f_localsplus[1];    
} PyFrameObject; 

如果你想知道 PyFrameObject 中每個字段的意義, 請參考 Junnplus’ blog 或者直接閱讀源代碼,了解frame的執行過程可以參考zpoint’blog.

名字空間實際上是維護着變量名和變量值之間關係的PyDictObject對象。
f_builtins, f_globals, f_locals名字空間分別維護了builtin, global, local的name與對應值之間的映射關係。

每一個 PyFrameObject對象都維護了一個 PyCodeObject對象,這表明每一個 PyFrameObject中的動態內存空間對象都和源代碼中的一段Code相對應。

3.2.1 棧幀的獲取,工作中會用到

可以通過sys._getframe([depth]), 獲取指定深度的PyFrameObject對象

>>> import sys
>>> frame = sys._getframe()
>>> frame
<frame object at 0x103ab2d48>

3.2.2 python中變量名的解析規則 LEGB

Local -> Enclosed -> Global -> Built-In

  • Local 表示局部變量

  • Enclosed 表示嵌套的變量

  • Global 表示全局變量

  • Built-In 表示內建變量

如果這幾個順序都取不到,就會拋出 ValueError

可以在這個網站python執行可視化網站,觀察代碼執行流程,以及變量的轉換賦值情況。

4. 額外收穫

意外收穫: 之前知道pythonGIL , 遇到I/O阻塞時會釋放gil,現在從源碼中看到了對應的流程

if (_Py_atomic_load_relaxed(&ceval->gil_drop_request)) {
    /* Give another thread a chance */
    if (_PyThreadState_Swap(&runtime->gilstate, NULL) != tstate) {
        Py_FatalError("ceval: tstate mix-up");
    }
    drop_gil(ceval, tstate);

    /* Other threads may run now */

    take_gil(ceval, tstate);

    /* Check if we should make a quick exit. */
    exit_thread_if_finalizing(runtime, tstate);

    if (_PyThreadState_Swap(&runtime->gilstate, tstate) != NULL) {
        Py_FatalError("ceval: orphan tstate");
    }
}
/* Check for asynchronous exceptions. */

參考:

python 源碼分析 基本篇

python虛擬機運行原理

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

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

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

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

SpringSceurity(4)—短信驗證碼功能實現

SpringSceurity(4)—短信驗證碼功能實現

有關SpringSceurity系列之前有寫文章

1、SpringSecurity(1)—認證+授權代碼實現

2、SpringSecurity(2)—記住我功能實現

3、SpringSceurity(3)—圖形驗證碼功能實現

一、思考

1、設計思路

在獲取短信驗證碼功能和圖形驗證碼還是有很多相似的地方,所以這裡在設計獲取短信驗證的時候,將之前開發好的的圖形驗證碼進一步整合、抽象與重構。

在獲取驗證碼的時候,它們最大的不同在於: 圖形驗證碼是通過接口返回獲取給前端。而短信驗證碼而言是通過第三方API向我們手機推送

但是它們在登陸的時候就有很大的不同了,對於圖形驗證碼而言驗證通過之前就走 UsernamePasswordAuthenticationFilter 過濾器了開始校驗用戶名密碼了。

但對於短信登陸而言,確實也需要先現在短信驗證碼是否通過,但是一旦通過他是不走 UsernamePasswordAuthenticationFilter,而是通過其它方式查詢用戶信息來校驗

認證已經通過了。

這篇博客只寫獲取獲取短信驗證碼的功能,不寫通過短信驗證碼登陸的邏輯。

2、重構設計

這裏才是最重要的,如何去設計和整合短信驗證碼和圖形驗證碼的代碼,是我們最應該思考的。如何將相似部分抽離出來,然後去實現不相同的部分。

整理后發現不同點主要在於

 1、獲取驗證碼。因為對於圖形驗證碼需要有個畫布,而短信驗證碼並不需要,所以它們可以實現同一個接口,來完成不同的邏輯。
 2、發送驗證碼。對於圖形驗證碼來講只要把驗證碼返給前端就可以,而短信驗證碼而言是通過第三方API將驗證碼發到我們的手機上。
    所以這裏也可以通過實現統一接口來具體實現不同的方法。

相同部分我可以通過抽象類來完成實現,不同部分可以通過具體的實現類來實現。

AbstractValidateCodeProcessorService 抽象類是用來實現兩種驗證碼可以抽離的部分。ImageCodeProcessorServiceImpl

SmsCodeProcessorServiceImpl方法是來實現兩種驗證碼不同的發送方式。

在簡單看下時序圖可能會更加明白點。

一個接口只有一個方法(processor)就是處理驗證碼,它其實需要做三件事。

 1、獲取驗證碼。2、將驗證碼存入session。3、將驗證碼信息通過短信或者圖形驗證碼發送出去。

首先講生成獲取驗證碼,這裡有一個公共接口和兩個實現類

對於保存驗證碼信息而言,可以在直接在 AbstractValidateCodeProcessorService抽象類來完成,都不需要去實現。

對發送驗證碼信息而言,只需要實現AbstractValidateCodeProcessorService抽象類的send發送驗證碼接口即可。

整個大致接口設計就是這樣,具體的可以通過代碼來展示。

二、代碼實現

1、驗證碼屬性

短信驗證碼和圖形驗證后包含屬性有 codeexpireTime,短信驗證碼只有這兩個屬性,而圖形驗證碼還多一個BufferedImage實例對象屬性,所以將共同屬性進行抽取

,抽取為ValidateCode類,代碼如下:

ValidateCode實體

@Data
@AllArgsConstructor
@NoArgsConstructor
public class ValidateCode {

    private String code;

    private LocalDateTime expireTime;

    public boolean isExpired() {
        return LocalDateTime.now().isAfter(expireTime);
    }

}

對於圖形驗證碼而言,除了需要code和過期時間還需要圖片的畫布,所以繼承ValidateCode之後再寫自己屬性

ImageCode實體

@Data
public class ImageCode extends ValidateCode {

    private BufferedImage image;

    public ImageCode(BufferedImage image, String code, LocalDateTime expireTime) {
        super(code, expireTime);
        this.image = image;
    }

    public ImageCode(BufferedImage image, String code, int expireIn) {
        super(code, LocalDateTime.now().plusSeconds(expireIn));
        this.image = image;
    }
}

對於短信驗證碼而言,暫時不需要添加自己的屬性字段了。

SmsCode實體

public class SmsCode extends ValidateCode {

    public SmsCode(String code, LocalDateTime expireTime) {
        super(code, expireTime);
    }

    public SmsCode(String code, int expireIn) {
        super(code, LocalDateTime.now().plusSeconds(expireIn));
    }
}

2、ValidateCodeProcessor接口

ValidateCodeProcessor接口主要是完成 驗證碼的生成、保存與發送的完整流程,接口的主要設計如下所示:

ValidateCodeProcessorService接口

public interface ValidateCodeProcessorService {

    /**
     * 因為現在有兩種驗證碼,所以存放到seesion的key不能一樣,所以前綴+具體type
     */
    String SESSION_KEY_PREFIX = "SESSION_KEY_FOR_CODE_";
    /**
     * 通過也是 type+CODE_PROCESSOR獲取對於的bean
     */
    String CODE_PROCESSOR = "CodeProcessorService";

    /**
     * 這個接口要做三件事
     * 1、獲取驗證碼。
     * 2、將驗證碼存入session
     * 3、將驗證碼信息通過短信或者圖形驗證碼發送出去。
     * (將spring-boot-security-study-03接口裡的那三步進行里封裝)
     *
     */
    void processor(ServletWebRequest request) throws Exception;

由於圖片驗證碼和短信驗證碼的 生成和保存、發送等流程是固定的。所以這裏寫一個抽象類來實現ValidateCodeProcessor接口,來實現相似部分。

AbstractValidateCodeProcessorService抽象類

@Component
public abstract class AbstractValidateCodeProcessorService<C> implements ValidateCodeProcessorService {

    private static final String SEPARATOR = "/code/";

    /**
     * 操作session的工具集
     */
    private SessionStrategy sessionStrategy = new HttpSessionSessionStrategy();

    /**
     * 這是Spring的一個特性,就是在項目啟動的時候會自動收集系統中 {@link ValidateCodeGeneratorService} 接口的實現類對象
     *
     * key為bean name
     */
    @Autowired
    private Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap;

    @Override
    public void processor(ServletWebRequest request) throws Exception {
        //第一件事
        C validateCode = generate(request);
        //第二件事
        save(request, validateCode);
        //第三件事
        send(request, validateCode);
    }

    /**
     * 生成驗證碼
     *
     */
    private C generate(ServletWebRequest request) {
        String type = getProcessorType(request);
        //這裏 image+CodeGenerator = imgCodeGenerator 對應的就是ImageCodeGeneratorServiceService
        ValidateCodeGeneratorService validateCodeGenerator = validateCodeGeneratorMap.get(type.concat(ValidateCodeGeneratorService.CODE_GENERATOR));
        return (C) validateCodeGenerator.generate(request);
    }

    /**
     * 保存驗證碼到session中
     */
    private void save(ServletWebRequest request, C validateCode) {
        //這裏也是封裝了一下
        sessionStrategy.setAttribute(request, SESSION_KEY_PREFIX.concat(getProcessorType(request).toUpperCase()), validateCode);
    }

    /**
     * 發送驗證碼 (只有發送驗證碼是需要自己去實現的。)
     */
    protected abstract void send(ServletWebRequest request, C validateCode) throws Exception;

    /**
     * 獲取請求URL中具體請求的驗證碼類型
     *
     */
    private String getProcessorType(ServletWebRequest request) {
        // 獲取URI分割后的第二個片段 (/code/image 通過/code/ 切割后就只剩下 image
        return StringUtils.substringAfter(request.getRequest().getRequestURI(), SEPARATOR);
    }
}

簡單說明

1、這裏用到了Spring一個特性就是Map<String, ValidateCodeGeneratorService> validateCodeGeneratorMap 可以把ValidateCodeGeneratorService所以的實現類都放
到這個map中,key為bean的名稱。

2、抽象類中實現了 ValidateCodeProcessor接口的processor方法,它主要是完成了驗證碼的創建、保存和發送的功能。

3、generate 方法根據傳入的不同泛型而生成了特定的驗證碼。

4、save 方法是將生成的驗證碼實例對象存入到session中,兩種驗證碼的存儲方式一致,只是有個key不一致,所以代碼也是通用的。

5、send 方法一個抽象方法,分別由ImageCodeProcessorService和SmsCodeProcessorService來具體實現,也是根據泛型來判斷具體調用哪一個具體的實現類的send方法。

3、驗證碼的生成接口

上面說過驗證的生成應該也是通過實現類

ValidateCodeGeneratorService

public interface ValidateCodeGeneratorService {

    /**
     * 這個常量也是用來 type+CodeGeneratorService獲取對於bean對象
     */
    String CODE_GENERATOR = "CodeGeneratorService";

    /**
     * 生成驗證碼
     * 具體是圖片驗證碼 還是短信驗證碼就需要對應的實現類
     */
    ValidateCode generate(ServletWebRequest request);
}

圖形驗證碼具體實現類

mageCodeGeneratorServiceImpl

@Data
@Component("imageCodeGeneratorService")
public class ImageCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {

    private static final String IMAGE_WIDTH_NAME = "width";
    private static final String IMAGE_HEIGHT_NAME = "height";
    private static final Integer MAX_COLOR_VALUE = 255;

    @Autowired
    private ValidateCodeProperties validateCodeProperties;

    @Override
    public ImageCode generate(ServletWebRequest request) {
        int width = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_WIDTH_NAME, validateCodeProperties.getImage().getWidth());
        int height = ServletRequestUtils.getIntParameter(request.getRequest(), IMAGE_HEIGHT_NAME,validateCodeProperties.getImage().getHeight());
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics g = image.getGraphics();

        Random random = new Random();

        // 生成畫布
        g.setColor(getRandColor(200, 250));
        g.fillRect(0, 0, width, height);
        g.setFont(new Font("Times New Roman", Font.ITALIC, 20));
        g.setColor(getRandColor(160, 200));
        for (int i = 0; i < 155; i++) {
            int x = random.nextInt(width);
            int y = random.nextInt(height);
            int xl = random.nextInt(12);
            int yl = random.nextInt(12);
            g.drawLine(x, y, x + xl, y + yl);
        }

        // 生成数字驗證碼
        StringBuilder sRand = new StringBuilder();
        for (int i = 0; i < validateCodeProperties.getImage().getLength(); i++) {
            String rand = String.valueOf(random.nextInt(10));
            sRand.append(rand);
            g.setColor(new Color(20 + random.nextInt(110), 20 + random.nextInt(110), 20 + random.nextInt(110)));
            g.drawString(rand, 13 * i + 6, 16);
        }

        g.dispose();

        return new ImageCode(image, sRand.toString(), validateCodeProperties.getImage().getExpireIn());
    }

    /**
     * 生成隨機背景條紋
     *
     * @param fc 前景色
     * @param bc 背景色
     * @return RGB顏色
     */
    private Color getRandColor(int fc, int bc) {
        Random random = new Random();
        if (fc > MAX_COLOR_VALUE) {
            fc = MAX_COLOR_VALUE;
        }
        if (bc > MAX_COLOR_VALUE) {
            bc = MAX_COLOR_VALUE;
        }
        int r = fc + random.nextInt(bc - fc);
        int g = fc + random.nextInt(bc - fc);
        int b = fc + random.nextInt(bc - fc);
        return new Color(r, g, b);
    }
}

短信驗證碼具體實現類

SmsCodeGeneratorServiceImpl

@Data
@Component("smsCodeGeneratorService")
public class SmsCodeGeneratorServiceImpl implements ValidateCodeGeneratorService {

    @Autowired
    private ValidateCodeProperties validateCodeProperties;


    @Override
    public SmsCode generate(ServletWebRequest request) {
        //生成隨機數
        String code = RandomStringUtils.randomNumeric(validateCodeProperties.getSms().getLength());
        return new SmsCode(code, validateCodeProperties.getSms().getExpireIn());
    }
}

4、驗證碼的發送邏輯類

獲取的實現類寫好了,我們在寫發送具體的發送實現類,發送類的實現類是實現AbstractValidateCodeProcessorService抽象類的。

圖片發送實現類

ImageCodeProcessorServiceImpl

@Component("imageCodeProcessorService")
public class ImageCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<ImageCode> {

    private static final String FORMAT_NAME = "JPEG";

    /**
     * 發送圖形驗證碼,將其寫到相應中
     *
     * @param request   ServletWebRequest實例對象
     * @param imageCode 驗證碼
     */
    @Override
    protected void send(ServletWebRequest request, ImageCode imageCode) throws Exception {
        ImageIO.write(imageCode.getImage(), FORMAT_NAME, request.getResponse().getOutputStream());
    }
}

短信發送具體實現類。這裏只是後台輸出就好了,實際中只要接入對於的SDK就可以了。

SmsCodeProcessorServiceImpl

@Component("smsCodeProcessorService")
public class SmsCodeProcessorServiceImpl extends AbstractValidateCodeProcessorService<SmsCode> {
    private static final String SMS_CODE_PARAM_NAME = "mobile";

    @Override
    protected void send(ServletWebRequest request, SmsCode smsCode) throws Exception {
        //這裡有一個參數也是前端需要傳來的 就是用戶的手機號
        String mobile = ServletRequestUtils.getRequiredStringParameter(request.getRequest(), SMS_CODE_PARAM_NAME);
        // 這裏僅僅寫個打印,具體邏輯一般都是調用第三方接口發送短信
        System.out.println("向手機號為:" + mobile + "的用戶發送驗證碼:" + smsCode.getCode());
    }

整個大致就是這樣,我們再來測試一下。

三、測試

1、ValidateCodeController接口

獲取驗證碼接口

@RestController
@Slf4j
public class ValidateCodeController {


    @Autowired
    private  Map<String, ValidateCodeProcessorService> validateCodeProcessorMap;


    @RequestMapping("/code/{type}")
    public void createCode(HttpServletRequest request, HttpServletResponse response, @PathVariable String type) throws Exception {
        if(!StringUtils.equalsAny(type, "image", "sms")){
            log.info("type類型錯誤 type={}",type);
            return;
        };

        //根據type獲取具體的實現類
        ValidateCodeProcessorService validateCodeProcessorService = validateCodeProcessorMap.get(type.concat(ValidateCodeProcessorService.CODE_PROCESSOR));
        validateCodeProcessorService.processor(new ServletWebRequest(request, response));

    }

}

2、獲得圖形驗證碼

獲取成功

3、獲取短信驗證碼

獲取短信驗證碼需要多傳一個參數就是mobile 手機號碼

因為這裏發送短信沒有接第三方SDK,而是直接在控制台輸出

參考

1、Spring Security技術棧開發企業級認證與授權(JoJo)

這一套架構設計真的非常的好,代碼可讀性和可用性都非常高,以後如果要接第三個驗證碼只要實現發送和獲取的接口來自定義實a現就好了。很受啟發!

別人罵我胖,我會生氣,因為我心裏承認了我胖。別人說我矮,我就會覺得好笑,因為我心裏知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。
攻我盾者,乃我內心之矛(20)

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

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

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

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

架構C02:商業模式與架構設計

商業模式與架構設計:A段架構與B段架構

《思考軟件創新設計:A段架構師思考技術》 A段架構師必須具備鮮活的創新思維,睿智的策略思考,犀利的洞察力和靈活的戰術才能把握稍縱即逝的商機                                                       

 

 

 

 

 

 

 

 

 

 

 

  

  段架構師 B段架構師
關注點 產品策略規劃 實踐策略,執行能力,技術變遷
協作對象 協助產品經理 協助研發經理生產經理
思維的差異 獲利思維,知彼才能在複雜商業環境生存 成本思維,知己才能在成本和收益做出合適選擇

 

                目前我們所接觸的大多是B段技術的架構,更關注“知己”,我們做研發改進,敏捷管理,技術重構,就是為了更好的平衡技術的成本和業務的收益  

 A段架構與商業模式:以不變應萬變

 

架構師需要考慮的商業要素

決策前(A段設計)—->決策點—>決策后(B段設計)  

商業思維三要素:商業模式,架構模式,創新產品

 

軟件是現實世界的映射和抽象

現實世界是複雜多變的,所以由需求就是複雜多變的,軟件也是複雜多變的, 所以現實中組織要發展就要面對變化的適合的變化,反應到軟件上也會隨需求的變化而變化,所以軟件本質上是一個演化的系統,是一個複雜的系統  

商業與技術的關係

商業維度,現實世界是複雜多變的組織需要不停的適應市場的變化, 從產品維度需要不停的創新滿足客戶和市場的需求, 而從技術和架構的維度來看,架構則希望更少的信息熵,用更少的技術元素來表述更多的業務結構,這也正是為什麼我們追求模型,模式,結構與算法  

 商業和產品做加法,架構設計做減法 

在複雜的現實中,用簡單的抽象來支撐商業的變化,用靈活的設計支持業務的創新   《深奧的簡潔》是一本科普讀物,裏面講述了碎行,自我組織,自我類似等等自然界好些美妙的規律 https://zh.wikipedia.org/wiki/%E5%88%86%E5%BD%A2#%E7%A4%BA%E4%BE%8B    

大樹的隱喻描述商業,架構,研發技術生產管理

大樹的上層是枝恭弘=叶 恭弘,要吸收陽光雨露,要開花結果,是對外界展示的活躍和生機的一面,這裏用來表述商業模式和創新產品 這些都是要變化的部分,而且收外部影響較明顯   再次是樹榦是中層A段架構,中層要求穩既要約束和輔助枝恭弘=叶 恭弘發展和繁榮又要保護下層樹根承受壓力   下層部分的話就是B段架構,生產,技術,管理,這些是看不見但是很重要的元素,是整個樹木生命繁榮的根本    

從複雜中抽象出簡單,用簡單和較少信息熵,應對複雜多變的商業和產品

簡單的有序的產品和架構設計,通過一定的約束組合可以形成一個富有活力的系統,底層元素的簡單又保證了它可以包容現實中的複雜變化,應對紛繁複雜的現實情況,支持商業的變革和產品的創新    

B段架構技術和業務的矛盾:用成本收益作為衡量標準

 

變的是需求和技術,不變的是成本與收益評估,是要創造價值的目標

  “你這個功能啥時候能上? ” “這個有難度目前不行,需要做重構,技術細節blablabla…” “提這麼多需求沒幾個有用的,根本不懂技術實現,你要覺的能行為啥你不上”   產品和技術的矛盾點: 1. 資源的搶佔 2. 成本的評估 3.內外部目標的差異 4.內部目標設定不合理  

解決問題:業務知識+成本核算

技術要了解業務背景,業務收益,要解決的問題是什麼?只有這樣才能解決問題,做出架構設計,做出模型設計,解決業務問題,幫助客戶解決現實場景的問題  

優秀的架構要融和技術與業務的平衡和成本收益的評估

  1. 清晰服務業務短期目標,明確技術定位,輔助實現當前階段業務訴求 2. 協調技術資源投入和分配 3. 進行成本與收益的評估,確定做哪些,不做那些,先做那些,怎麼做收益更大 4. 預留長期技術規劃和儲備  

我們是解決昨日之債務,還是準備迎接今日之挑戰?

衡量的標準就是做這件事的收益?   產品和業務做哪些收益更大:產品的願景和價值觀 本年度看做哪些收益更大(OKR) 本季度本月做哪些收益最大(月度發版路標規劃) 當天本周做哪些收益最大(周計劃)  

舊系統的改造 OR 新技術的引進?

技術儲備和技術棧規劃方面: 中小型創業型公司,非技術驅動的公司 關注中長期發展的技術與趨勢,不要太超前,不必做小白鼠   舊系統改造方面: 假如不能明顯的產生業務價值,單純的把報表生成把半小時優化到5分鐘,不如做一些其他更有業務價值的任務 假如沒有其他高附件值任務可以去做,假如報表生成佔用研發時間減少了質量保證時間,影響了交付質量也可以去做  

 

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

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

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