環境資訊中心綜合外電;姜唯 編譯;林大利 審校
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※超省錢租車方案
※教你寫出一流的銷售文案?
※網頁設計最專業,超強功能平台可客製化
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※台中搬家遵守搬運三大原則,讓您的家具不再被破壞!
摘錄自2020年10月16日自由時報報導
福島核災發生至今已屆滿9年,年中傳出日本政府計畫將123萬噸輻射污水排入太平洋,引發鄰近國家反彈,我國環團也表態反對。日媒昨日(15日)報導稱,當局現已決定排放入海的處理方式,最快月內敲定細節。
根據《共同社》報導,對於淨化後核污水後續處理機制的討論始於2013年,至今7年仍未拍板,《共同社》近日採訪相關人士獲悉,日本政府已決定維持排放入海的計畫,至於形象受損部分如何修補,將召開新一輪討論會議。
據了解,截至今年9月,福島核電廠為冷卻核燃料碎片注入的污水總量已高達123萬噸,較年中的120萬噸增加不少,目前污水全數被收置於1044座大型儲罐中,但由於儲罐將於2022年夏天用罄,廠區用地也因堆放污水罐而擁擠不堪,故需盡速制定後續處理計畫。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※教你寫出一流的銷售文案?
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
※超省錢租車方案
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※推薦台中搬家公司優質服務,可到府估價
摘錄自2020年10月15日自由時報報導
墨西哥日前開始爭議性的「馬雅鐵路」計畫,環繞猶加敦半島(Yucatan peninsula),可望串聯多個馬雅文明遺址。墨西哥專家14日則宣布,在鐵路計畫沿線發現2000多個遺跡和手工製品。
《美聯社》報導,馬雅鐵路的建設計畫自宣布以來,引發少數民族及環保團體的抗議,專家最新的發現可能將馬雅鐵路興建計畫減速,根據專家探測,在長達277英里(366公里)的鐵路,共發現2187個「考古遺跡」,遺跡從房屋建築到石雕不等,範圍約佔總鐵路計畫的1/4。
國家人類學暨歷史研究院表示,數十年前的鐵路建設就曾威脅文物,沿伸的路線更將穿過脆弱的叢林地區。反對派人士批評,總統歐布拉多(Andres Manuel Lopez Obrador)強行推動計畫,並沒有充分研究鐵路對當地環境、天然井(cenote)和遺址的影響。
部分馬雅社區也擔心鐵路計畫會破壞環境,已向法院提出申訴。馬雅鐵路今年7月開工,將加勒比海灘勝地連接猶加敦半島內陸,經過多個原住民社區和遺址。
本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※超省錢租車方案
※別再煩惱如何寫文案,掌握八大原則!
※回頭車貨運收費標準
※教你寫出一流的銷售文案?
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※網頁設計最專業,超強功能平台可客製化
不不不,早在 2010 年就有成熟的產品了:Cloud9 IDE
時至如今,雲 IDE 已經相當常見了,比如:
Cloud9:亞馬遜為其雲計算服務提供的 IDE
Eclipse Che及Eclipse Theia:老牌 IDE 的雲化版本
Coder:以及前不久開源的code-server
Expo Snack:React Native 的雲端開發環境
Coding:國內的雲 IDE 產品
codesandbox:面向 Web 項目的雲 IDE
一般的開發工作流中,我們會建立一套本地環境,包括順手的 IDE 和整套本地工具,但這種本地開發模式存在一些問題:
開發機性能要求高:冷編譯一次 40 分鐘
開發環境配置複雜:工具環境能夠通過容器技術或一系列版本管理工具(如 nvm)解決,但網絡、安全等環境就不那麼容易配置了
依賴特定設備:休假可以,但是帶上電腦,24 小時 On Call,10 分鐘無響應記大過一次
巨型代碼庫的管理難題:巨型代碼庫切換個 Git 分支,動輒半小時
於是,遠程開發的理念應運而生,連接遠程測試服務器,直接在服務器環境完成日常開發工作,免去本地重建並維護一套測試環境的成本
現有的遠程開發模式下,工程師大多通過終端交互工具連接遠程機器,並通過 vim、naro 等文本編輯器來開發。而這些編輯器通常對項目文件管理、運行任務、調試器、智能提示/補全等基礎功能的支持不那麼友好,並不能像本地 IDE 一樣提供舒適的開發環境。開發體驗下降的同時,也限制了開發效率
那麼,有沒有兩全其美的辦法?
有,把 IDE 也搬到遠程,即雲 IDE
P.S.或者把雲拽下來,即,本地 IDE 提供遠程開發能力,但理念上與雲 IDE 並無二致(本地 IDE 相當於瀏覽器),具體見VSCode 遠程開發套件
綜上,IDE 上雲能解決兩方面問題:
本地開發模式難以解決的問題:不再要求本地機器十分強大,不必擔心環境,不依賴特定辦公設備,硬盤也不用再瘋狂旋轉
遠程開發模式的體驗問題:不再是 Web Editor 玩具,而與本地 IDE 一樣順手的開發環境
雲 IDE 也是遠程開發模式的一種實現形式,自然能夠解決本地開發模式所存在的一些難題
同時,作為 Web Editor 的升級形態,雲 IDE 能夠提供更好的遠程開發體驗,補足遠程開發模式的體驗短板,解決工具不稱手限制開發效率的問題
無論本地 IDE 還是雲 IDE,都具有兩個基本作用:
提升開發效率:整合零碎的開發工具/服務,實現工具鏈的平台化
升級開發體驗:無縫連接開發工作流,提供一站式體驗
從開發者角度來看,IDE 的關鍵在於對工具的整合與連接,不只是簡單的工具集,而是讓這些工具能以最自然的方式配合工作,組成高效的工作流。即工作台/工作助理 >> 工具集:
IDE >> 項目文件管理 + 文本編輯器 + 交互式終端 + 項目腳手架 + 運行任務 + 調試器 + 工具插件 + ...工具
對雲服務供應商而言,能夠實現從 Cloud Shell、Cloud Editor 到 Cloud IDE 的產品形態升級,將一系列產品(雲服務)與用戶的工作流緊密結合起來,不僅能更好地表達產品功能,還能通過 IDE 更高效率地觸達用戶:
^ FaaS、BaaS
/
雲服務用戶 ---> 雲IDE ---> 數據存儲服務
\
v 計算資源
在肉眼可見的未來,雲 IDE 有這樣幾個應用場景:
FaaS:函數即服務,那麼,函數在哪裡寫?
獨立的技術生態:如 React Native、小程序、可視化搭建系統等
雲計算產品:從提供離散的產品/服務(如 FaaS),轉向提供定製開發環境和工作流
源碼管理平台:試想,GitHub/GitLab 即開發環境
研發工作台:雲計算時代的全雲研發模式下,需求-開發-測試-運維的完整鏈路
以雲 IDE 為中心的高效研發模式,可能是這樣的:
統一的開發環境:藉助容器技術,開發環境也能作為項目的一部分,像源碼一樣管理起來(基礎設施即代碼,Infrastructure as Code),代碼風格約束也能更好地落實
專用 IDE:通過定製開源 IDE,提供更貼合產品/業務的專用 IDE
完整的工程化鏈路:編輯-構建-運行-調試-測試-運維
飛快的構建速度:得益於雲計算的彈性調配能力,編譯時長能被大幅縮短
Code anywhere:開發環境也能像雲計算服務一樣觸手可得,隨時隨地,想碼就碼
實時協作:在線 Review,手把手教學,共享工作空間、一鍵分享代碼
AI 助力開發:基於全源碼的智能提示、甚至代碼生成、質量分析等
在技術走向 techless 的同時,研發模式或將迎來 tool-less 時代
The Future of Cloud IDEs
IDEs are Moving to the Cloud
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?
※別再煩惱如何寫文案,掌握八大原則!
阿里CBU前端團隊招人,不管是serverless、跨端、WEB IDE、工程化、智能化、搭建 還是 直播,在CBU 前端團隊 都能夠找到適合你的技術方向。 感興趣的小夥伴可私信留言,或者加群交流,QQ群(1126560208)
首先簡單自我介紹一下,我叫陳映平,花名叫做小卡,2011年校招進入騰訊,是騰訊課堂B側的前端技術負責人。2015年響應總理的號召,跟朋友一起出來創業,跟前面一位講師的經歷有點像,然後2018年回到騰訊,主要負責NOW直播相關的業務,包括NOW商業化、NOW獨立版等。
今年的三月底,因為家庭的原因來到了杭州,加入了阿里巴巴CBU技術部-前端體驗技術團隊,職級是P8,目前負責的主要是內容跟營銷方向,包括采源寶、淘賣,以及現在風口上的業務,1688電商直播。
接下來分享一下我是如何拿到阿里P8的offer,以及面試過程中的一些思考。分享主要會分為幾個部分。
首先我們來看一下面試流程,以及面試過程中可能問到的一些問題。
這裏大概列了一下我當時去面試阿里巴巴的流程,一共分為五面。一面是我的直屬主管,二面是前端團隊的負責人,三面是部門的總經理,四面是跨部門交叉面試,最後一面是部門的HR負責人。
整體的面試流程其實跟前面幾位講師講到的差不多,裏面有一點小小的區別。
第一,就是面試官的層級必須要高於候選人的預估層級,因此從一面開始,所有的面試官都必須是P8或以上。
第二,終面面試官的層級必須大於候選人兩級以上,也就是說終面的面試官需要是P10以上,在阿里內部叫研究員。
最後,就是P7及以上的候選人需要跨BU的交叉面試,來保證面試的公平跟客觀。
接下來我們來看一下面試的內容,具體的面試題目屬於敏感信息,這裏不是很方便透露,就簡單的列舉一下每一輪大概考察了哪一些內容。
首先我們來看一下主管面,他一般會考察你的項目經歷、技術深度,來確認你的基本能力是過關的。
接下來是前端團隊的負責人,一般會看你過往的作品以及個人的一些影響力。比如說有沒有比較成功的項目,對外的一些技術分享,以及一些開源作品等。
然後第三個是部門的總經理跟HRG,一般會看你的項目經驗,你對所從事的業務以及該業務所在行業的一些思考,以及你的人才調性跟匹配度。簡單的說,就是看你是否是部門所需要的人才。
跨BU交叉面試,面試官是隨機的,面的問題跟前面的面試官可能會有一些差異,但整體上是類似的。
可以看到,五輪面試下來,考察的內容其實是各有側重的,當然也有不少重合的地方,這裏簡單概括為四個方面。
第一個是項目經驗與成果,第二個是方案設計與實施細節,第三個是對業務以及行業的一些思考,第四個是人才的調性以及匹配度。我們逐個來拆解一下。
首先是項目的經驗與成果。這個比較簡單,主要看的就是你在過去的幾年裡做了什麼項目,取得了什麼成績,有沒有取得一些突破。
不管是技術上的還是業務上的,在取得突破的同時,是否有對外進行沉澱輸出。比如說比如說輸出一套標準化的解決方案,是否在公司組織以及業界具有一定的影響力。
第二個是方案設計與實施細節。作為一個P8,對架構能力以及技術視野也是有一定的要求的。
這裏同樣是針對項目經驗進行考察,因為架構本身是為場景服務的,不存在脫離場景的架構。你在項目當中是如何進行選型與決策的?我們的簡歷裏面通常呈現的是一個具體的方案,但面試官他更關心的是你在這個方案背後的思考以及決策的過程。
比如說方案主要關心哪些點,是優先保證上線,還是重點保證後續的可維護性。方案是否閉門造車,有沒有跟集團或者業界的方案做對比等。
方案很難做到完美,最後總會存在這樣那樣的問題。這些問題都是面試官他可能挑戰到的。
我們的方案裏面存在的問題,其實很多時候是在有限的資源以及理想的設計之間取得的一些權衡。面試官他其實也有豐富的項目經驗,一般都能夠理解,你只要能夠自圓其說就可以了。最重要的是讓面試官看到你在具體細節背後的一些深入的思考。
第三個是行業以及業務的一些思考。到了P8,除了在技術上帶頭衝鋒之外,也需要在業務上有更多深入的思考,體現出業務上的決策力跟影響力。
比如說你當前所處的行業現在是什麼情況,是處於上升期呢?還是穩定期?還是已經進入了衰退期?
第二個,團隊業務的現狀。在行業中處於什麼位置?他是領頭羊,還是一個挑戰者?當前有哪些競爭對手?主要面臨哪一些挑戰?要如何破局?
也就是說,你要有更多就是業務以及全局的視角。
最後是人才的調性跟匹配度。簡單的說,就是看你是不是崗位所需要的人才,你過往的經驗能力跟當前的崗位是否匹配,最後,是否符合阿里的人才觀跟價值觀。
其中有一個非常重要的一點,也是大家經常忽略的一個點,就是個人的預期跟團隊的預期是否匹配。比如說你只是想專心做技術架構,但是崗位想要的其實是一個偏管理的人才,就很有可能會產生預期上的衝突。
這個時候你的落地就會比較困難。哪怕部門最終給你開出來一個比較高的職級,其實也是價值不大的。
前面講了面試的整體流程、面試的題目,以及面試過程中主要考查的一些點。接下來講一下我在面試之前做了哪一些準備。主要包含三個部分,其實跟大家差不多。
第一個是了解崗位,第二個是準備簡歷,第三個是準備面試過程中可能問到的一些問題。
首先,了解崗位,主要就是了解這個崗位是做什麼的,他對應聘者有什麼要求,比如說年限,比如說技術棧,比如說項目經驗、部門的大致情況。
第二個準備簡歷,這個大家基本上都有經驗了,主要就是介紹自己的個人專長以及項目經驗,同時針對項目專長跟經驗進行能力的舉證。也就是說,證明你所你說的是真的。
第三個是面試,準備面試的問題。
根據崗位職級的不同,面試的問題跟側重點也不同。通常來說會圍繞技術能力、業務理解、綜合能力三個方面進行展開。
接下來我會簡單講一下這三個步驟,以及大概有哪一些注意事項。
首先是了解崗位的職責要求。很多時候我們看崗位的描述就可以了,一般用人單位會發布一個招聘的JD。如果是部門的情況,可以通過社區或者朋友來進行了解。
比如說,我去面試的時候,對應聘的職級有要求的話,需要重點了解什麼呢?
主要就是了解一下,對應職級對人才的要求,以及能力模型大概是什麼樣子的。
這裏貼一下阿里巴巴對於P8的能力要求。有兩個主要的關鍵特徵點,第一個是領域突破,第二個是業務增值。
怎麼理解這兩個關鍵詞?
首先是領域突破,也就是說你需要在自己所擅長的領域裏面,通常是技術,要有所突破。
比如之前在做直播的時候,在技術上通過webassembly+ffmpeg的方式,實現了在web端播放H.265、降低帶寬,並支持更多的視頻編碼。
業務增值怎麼理解呢?簡單的說,你需要通過技術突破、系統化數據體系建設等,給業務帶來增量價值。
舉個例子,在做1688直播的時候,項目組當時通過數據分析,發現早上開播的商家比較多,但買家比較少,晚上開播的商家較少,但是買家比較多。
開播的商家跟買家的數量明顯是衝突的,這個時候就可以調整一些運營策略,引導商家在晚上人流多的時候進行開播,提高業務的DAU以及轉化率。
這就是領域突破跟業務增值兩點的簡單闡釋。
在崗位特徵之外,下面是三點具體的能力要求。比如說業務理解與實現,技術的沉澱與傳承,團隊的建設與發展等,字比較多,這裏就不一一念出來。
歸結起來其實主要就是三點。
接下來講一下怎麼準備簡歷。
首先最重要的一點,就是要充分展示自己的能力,以及自己能力跟崗位的匹配度。也就是說告訴面試官,你是他們想要的那一個人。
第二個就是要注意能力舉證的邏輯,最好是数字說話。比如說介紹首屏性能優化,你必須要告訴面試官說你通過哪一些關鍵的手段提升了哪一些性能。在過往的面試中經常會遇到,有些候選人,他在簡歷裏面可能寫了上千字,說他做了什麼事情,但是他最終通過哪一些關鍵的步驟,取得了什麼樣的成果,沒有體現出來。這對看簡歷的面試官來說,也是一個比較頭大的事情。
最後一點,就是要謹慎挑選你的簡歷素材。簡而言之就是不要給自己挖坑,寫進簡歷裏面的每一句話,每一個数字都要經得起面試官的挑戰。被問倒的話是其次,千萬不要弄虛作假。因為弄虛作假,不管是在哪個企業裏面,都是紅線,不能觸碰。
最後一個就是準備面試的問題。我自己也做了挺長時間的面試官,也經常面試別人。所以在面試之前,我通常會進行自我模擬面試,主要問自己以下幾個問題:
模擬面試的話,需要對崗位的能力模型、崗位的要求、自我的能力特長 以及 產出有比較清晰的認識。如果沒有什麼底的話,建議可以找比較有經驗的朋友幫自己把把關。也可以藉助搜索引擎去網上看一下,需要哪些知識和能力。
接下來就是具體的面試過程中的建議。這裡是長者說過的一段話,我自己挺喜歡的,跟大家分享一下。
一個人的命運當然要靠自我的奮鬥,同時也要考慮到歷史的進程。
為什麼這麼說呢?因為面試本身是一個充滿了不確定性的事情。七分看實力,三分看運氣。
拋開個人的能力,以及面試的臨場表現,不確定性它主要體現在哪裡呢?
首先是面試官的個人偏好。比如你自己比較擅長vue,面試官它是angular的鐵粉,並且是排他性的鐵粉,大概率你們兩個人是聊不到一塊去的。大概在去年底,社區也發生過這麼個事情。這種情況雖然比較極端,但的確的存在。
其次就是崗位人才的稀缺性。比如說2010年時,隨着iOS以及安卓的流行興起的終端開發浪潮,以及2015年興起的人工智能浪潮。在這個時間點里,這兩個崗位的人才,他是供不應求的,做這塊開發的同學比較容易拿到自己想要的offer以及職級。
最後一個就是面試的時機。比如說現在你去面的這個團隊,他只招P6,但是你自己心目中的理想職級是P7,那就是時機不對,跟你個人能力其實並沒有太大的關係。
最前面提到了,面試三分靠運氣,聽上去有點像撞大運的感覺。但事實上,通過自身硬實力的增加,是可以讓面試的結果變得更加確定的,這裡有幾點小建議。
第一點,就是功夫在日常,提前規劃、做好積累。如果你事先已經有了換工作的打算,你可以提前了解一下你要面試的崗位,你所要的職級有哪一些硬性要求,提前做好準備。
因為有一些東西是沒辦法臨時抱佛腳的,比如說團隊管理經驗。在過去的幾年時間里,我一直在做團隊管理,還包括一些項目管理的事情。因此在這一方面,我簡歷里是有東西可以寫的,也不怕被面試官問到。
第二個,是個人品牌的建設以及影響力的提升。
面試的時間非常有限,通常會在四十五分鐘到一個小時之間。坦白的講,作為一個面試官,他很難在這麼短的時間里對你做出非常全面以及準確的判斷。因為我做過面試官,我也知道這個。因此你需要花非常多的精力,來證明理你的能力能到能夠達到崗位的要求。
個人品牌的建立,以及影響力的建立有什麼好處呢?他會讓你面試的過程變得順利很多。比如說過去的這些年,我基本上每一年都會在社區做一些技術分享,同時也會持續更新我的技術博客。通過這一些,面試官在面試之前就可以對你的個人能力有一個基本的判斷。加上我本身從大公司里出來的buff,以及阿里朋友推薦的背書,在能力舉證這一塊可以省掉非常多的力氣。
最後的一點,就是面試是一個雙向選擇的過程。前面堂主在分享的時候其實也提到了,這個過程,很看緣分跟運氣,要保持平常心。
面試不通過,有可能是你的能力達不到,也有可能是沒有發揮好,需要認真的總結跟反思,制定規劃,進一步提高自己。
但反過來,如果面試通過,當然是一件非常值得開心的事情,但你也要懷着感恩的心,感謝這一路過來幫過自己的朋友,包括自己的面試官。能夠成為面試官,通常他的能力上都有過人之處。這種1v1將近一個小時的面試,是一個非常好的學習探討的過程。你能夠在這個過程中學到非常多的東西。因此要懷着感恩的心。
接下來,是scott讓我加入的一個技術修鍊的章節。很多同學都覺得,到了P8之後,基本上就只是做做管理的事情,不做編碼了。但事實上不是這樣的。
我們先來看一下,到了P8之後,日常工作是做哪些事情呢?其實主要就三個方面,第一個就是技術管理,第二個項目管理,第三個是團隊管理。
技術管理這一塊,因為P8屬於集團的技術中堅力量,他負責的技術域跟業務域會越來越大,很多時候其實並沒有太多精力參与具體的編碼。日常主要做的工作是參与方案的評審,技術架構的設計以及特定領域技術難點的攻堅。
不是說不寫代碼,而是說要站在更高的視角,深入業務輸出合理的方案和設計。簡單的說,就是從原先的自己編碼,變成帶着別人去合理地編碼。
第二個是項目管理,做好項目管理主要要做好三個事情。
第一個就是目標管控。當前的項目處於什麼狀況,想要在什麼時候達成什麼目標。
第二個是資源協調。資源總是稀缺的,不管是人力、時間還是經費。按照既定的目標將資源協調到位,也有可能會根據資源的限制,調整實際的項目目標。
最後一個是過程的管控。確保項目按照既定規劃的路線實施,並達到目標,包括進度管理、風險管控以及質量管控等。
最後一個是團隊管理。前面的講師也提到了,主要做四個事情。
第一個是帶人做事。這一個是最基本的要求,就是帶着小夥伴高效、高質量地完成部門、團隊安排的工作。
第二個是看方向。比如說最新的技術趨勢,以及未來的業務走向等。
第三個是定規劃。比如說團隊未來半年、一年的技術規劃,團隊小夥伴的成長規劃等。
第四個是團隊建設。比如說未來團隊的人才梯度劃分、培養計劃、溝通等工作。
團隊管理是一個非常重要的事情,哪怕你現在沒有管理者的頭銜,因為它是在為業務的未來打基礎。
這裏就簡單的三句話:扎得更深、看得更遠、始終編碼。
首先是扎得更深。也就是說,你在技術領域要有就是更深、更透徹的理解,而不是片面的隨着社區去追星追潮流。
第二個看的更遠。你在技術上、業務上必須有更遠的視角。你要能夠看到技術、業務的遠景,做好規劃。
第三個是始終編碼。現在其實有非常多的技術專家,已經脫離了一線的編碼,他已經沒辦法理解現在越來越複雜的前端研發環境,以及我們的業務變化。阿里內部現在也在推一個親碼活動,所有的P8,甚至P9,每個月都需要提交代碼。也就是要保持對代碼這塊的敏感度。
接下來推薦一本書,這本書叫做影響力。從職場的角度來看,影響力能夠讓自己的工作開展、目標達成更加順利。其實包括求職也是一樣的,在前面的分享的各個章節裏面,已經反覆反覆提到了影響力這個詞。
影響力的重要性,基本上不用怎麼強調了,如何提高自己的影響力呢?包括技術影響力、團隊影響力、組織影響力,甚至是業界影響力。在這一本書中你都可以找到你想要的答案。
比如說互惠、承諾與一致、權威、短缺、喜好等,整本書的內容還是是比較通俗易懂的,尤其建議剛剛工作一兩年的同學看一下,會有非常大的幫助。
接下來是關於 CBU技術部-前端體驗團隊 的一點簡單介紹,我是今年三月份加入的體驗技術團隊。
整個團隊現在有六七十人,在集團內部也有比較大的技術影響力。我們致力於鏈接商業與設計,給客戶提供專業的人機交互解決方案。作為B端的業務,我們通常比較低調,但是我們的技術團隊在集團內其實也擁有多個領先的技術產品。比如說像奇美拉搭建系統、JUST研發平台、lighthouse數據分析平台、柯南前端錄製反饋平台等。
我們的團隊技術棧以react、nodejs為主,此外,在serverless、webide、跨端、工程化、智能化、搭建等領域,都有比較多的工程實踐與沉澱。
下面是團隊今年的重點技術方向,主要有下面幾個,下面伐總會對這一塊做進一步的闡釋,這裏就簡單提一下。
曬一下我們團隊的照片,這也是我加入CBU體驗技術團隊的一個非常重要的原因。最右邊的就是我們腿長一米八的美女主持人,霸天小姐姐,她也是我們團隊的,現在就坐在我的隔壁,我們每天都有面對面交流的機會。
大家不用太羡慕,只要加入我們團隊,你也可以獲得同樣的機會。這裏的話是幾個二維碼,最右邊的二維碼是我們的團隊跟崗位的介紹,大家感興趣的話可以了解一下。
最左邊是剛剛建的阿里巴巴CBU的技術交流群,大家可以用釘釘掃碼加入。我們已經把我們團隊的技術專家、高級技術專家都拉了進去,大家有什麼技術問題,有什麼困惑都可以掃碼進去交流。
最後,中間是我自己的個人二維碼,現在我們團隊招聘大量的P6跟P7,簡稱海量HC,歡迎加我微信,然後一起交流。加之前最好把自己的博客跟github的地址附錄一下
謝謝大家。就是今天的分享主要就到這裏。然後大家看一下大家有什麼問題。
Q:好,小卡。接下來進入提問環節,請問從公司層面來看,騰訊與阿里哪一個更適合上升期的前端工程師發展。
A:這一點的話其實比較難回答,就是片面的比如說究竟是騰訊適合,還是阿里適合。剛好前陣子我跟阿里、騰訊、拼多多的幾位專家還有大佬一起面基,然後我們也探討過同樣的問題,該去什麼樣的公司或團隊,是技術好的,還是說錢多的。
其實答案都不對,重要的是加入成長性比較強的團隊。比如說,我們部門做的是B側的電商,然後風口上的1688電商直播,這個就是屬於公司、部門今年重點布局與規劃的業務,屬於成長性,爆發性比較強的業務。
我們在看應該加入哪個公司、團隊的時候,主要看的是這個部門、業務它的成長性,而不是說是阿里巴巴或者說騰訊哪個更適合。不管是騰訊還是阿里巴巴,在眾多的領域都有它的布局和規劃,也有很多具備成長性的一些業務,大家如果經常在社區逛,其實都會看到。
也就是說,主要看的是業務的成長性以及行業的成長性,公司倒是其次,基本上在大公司你都可以找到一些成長性強的業務和團隊。
比如說我們團隊,歡迎掃碼加入CBU技術交流群,然後一起來探討這個問題。
Q:好的,打下卡。第二個問題,大場對候選人的要求,或者說候選人面試準備,閱讀源碼是否是重要的一項。
A:怎麼說呢?對於有源碼閱讀經驗的同學,其實我們是非常歡迎的,比如說讀過react源碼、vue的源碼、webpack源碼等,我們基本可以判斷,就是這個小夥伴他對這一塊的技術有比較濃厚的興趣,以及比較深入的鑽研。
可能在不同的階段,對人才的要求不是特別一樣。舉個例子,比如說在14年,我當時去面阿里的時候,當時我主要講的是我在工程化以及模塊化的一些積累。當時我的面試官石破當場問了我這個問題,就是有沒有看過某某某的源碼這一塊。
為什麼呢?因為對於P6的同學來說,他更重要的是執行、對方案研究的深入程度,以及標準化方案的輸出。如果你閱讀過源碼,說明你對這塊有比較深入的研究。對於比如說像P7、P8的話,可能對於你的比如說團隊管理能力,你的技術視野等會有更高的要求。
不同的階段它的要求不一樣。但如果你讀過源碼這一塊,我們都是非常歡迎的。
Q:好的,好,還有第三個問題請教小凱,你在前端中是否有遇到過瓶頸期,然後你是怎麼突破的呢?
A:怎麼說呢,就瓶頸期,我分享一下當時我在創業時候的一些經歷跟歷程。我是在15年跟着朋友一起出去創業,然後當時做的是期權做市交易系統。整一個團隊其實做的是金融類的業務,並且是以後台技術為主導的。我當時作為一個前端,其實就孤零零的帶着可能兩三個前端同學,然後就比較孤獨。第二個就是技術上,其實在前端那塊要求並不是特別的高,也沒有太太多人跟我一起交流。
我在創業公司一共待了三年,如果按照當時的那個現狀下去的話,可能我就廢了,也不可能說今年來到阿里拿到P8。我當時是怎麼做的呢?第一個就是始終要關注業界的動態,知道當時最流行的技術趨勢是什麼,我們的業務可能會需要什麼。比如說,我之前提到的Nodejs學習筆記,那一本書就是在創業的那個期間寫出來的。
當時創業其實是非常累、非常辛苦的,可能創過業的同學都知道,我當時寫這本書的時候,基本上都是在晚上,可能十一二點下了班,回家之後,然後在那裡翻源碼,看博客,自己總結輸出。
第二個的話,就是當時團隊的重心是在後台的金融交易系統,前端的東西比較薄。這個時候,其實就是按照像阿里對人才的要求一樣,不要給自己設限,大膽突破自己的邊界。
比如說像當時,我的本職工作是前端,但我還做了非常多的事情,比如說像項目管理、團隊管理的事情。在技術上當時就是在node的那一塊做的相對比較深,我們金融系統裏面的一塊,我把它用nodejs 接過來。
團隊需要我做什麼,我基本就去學什麼。比如說當時做的最雜的時候,前端、php、nodej s,甚至最後連C++、iOS都會去接觸。
遇到瓶頸的時候,可能就是問自己,第一,我想要往哪一方面去發展。第二,當前業務團隊部門、甚至集團需要你去做什麼。
思考這兩個問題之後,基本上你就會得到你想要的答案。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※台北網頁設計公司全省服務真心推薦
※想知道最厲害的網頁設計公司"嚨底家"!
※推薦評價好的iphone維修中心
※網頁設計最專業,超強功能平台可客製化
※別再煩惱如何寫文案,掌握八大原則!
閱讀本文大概需要 1.5 分鐘。
數組本身相對來說比較簡單,能想到的可寫的東西不多。但還是有一些知識點值得總結和知曉一 下。有的知識點,知不知道不重要,工作中用的時候搜索一下就可以了,畢竟實現一個功能代碼的寫法有很多種,再牛的人也不可能完全熟悉一門語言的每個細節。當然了,偶然地知道了一些小知識或小技巧也是一種小小的收穫。在你看這篇小文時,或許這種偶然的事情就正在發生。我們先從數組的初始化開始吧。
定義和初始化一個數組有好幾種方式,隨着 C# 版本升級,方式也越來越簡單:
int[] arr = new int[3]; // 定義一個長度為 3 的數組
int[] arr = new int[3] {1, 2, 3}; // 定義一個長度為 3 的數組並初始化
int[] arr = new [] {1, 2, 3}; // 上面的簡寫
int[] arr = {1, 2, 3}; // 上面的進一步簡寫
我們知道當定義一個固定長度的數組沒有初始化時,比如:
int[] arr = new int[3];
雖然沒有初始化,但它是有默認值的,這個數組中包含了 3 個值為 0 的整數。如何使用非默認值創建一個數組呢?可以使用 System.Linq 命名空間下的 Enumerable.Repeat 方法:
// 創建長度為 5 的整形數組,並用 100 來填充
int[] arr = Enumerable.Repeat(100, 5).ToArray();
// 創建長度為 5 的字符串數組,並用“C#”來填充
string[] arr = Enumerable.Repeat("C#", 5).ToArray();
把一個數組的元素複製到另一個數組,可以使用 Array.Copy() 方法,複製時源數組取值和目標數組賦值都是從索引 0 開始的:
var sourceArray = new int[] { 11, 12, 3, 5, 2, 9};
var destinationArray = new int[3];
Array.Copy(sourceArray, destinationArray, 3);
// destinationArray = [11, 12, 3]
克隆一個數組很簡單:
var sourceArray = new int[] { 11, 12, 3 };
var destinationArray = (int[])sourceArray.Clone();
// destinationArray = [11, 12, 3]
比較兩個數組是否一樣,即兩個數組包含的元素及元素的順序是否都一樣,可以使用數組的 SequenceEqual 方法:
int[] arr1 = { 3, 5, 7 };
int[] arr2 = { 3, 5, 7 };
bool result = arr1.SequenceEqual(arr2); // true
以上只是羅列幾個有代表性的比較快捷簡單的數組操作,在 C# 中藉助 Linq 可以實現更複雜的數組操作,這裏先不作總結。
實際的 C#開發中很少會直接用到指針,在需要進行底層操作時可能會用到。在 C# 中使用指針時需要在 unsafe 上下文中操作:
int[] arr = new int[] {1, 6, 3, 3, 9};
// 使用 foreach
foreach (int element in arr)
{
Console.WriteLine(element);
}
// 使用指針
unsafe
{
int length = arr.Length;
fixed (int* p = arr)
{
int* pInt = p;
while (length-- > 0)
{
Console.WriteLine(*pInt);
pInt++; // 將指針移到下一個元素
}
}
}
// 依次輸出:1 6 3 3 9
這裏只是讓大家知道有這麼一回事,指針不是本篇的話題,更多關於指針的操作可以訪問:
https://bit.ly/2MmIsNl
我就見過有人使用 for 循環生成一個從 n 到 m 的有序數組,其實 C# 提供了 Enumerable.Range 方法可以很容易地創建一個有序的整型數組。示例:
// 創建数字從 1 到 100 的數組
int[] sequence = Enumerable.Range(1, 100).ToArray();
// 結合 Linq 還可以實現更複雜的數組創建邏輯
int[] squares = Enumerable.Range(2, 10).Select(x => x * x).ToArray(); // 4, 9, 16...
簡單來說,多維數組每一行長度都是固定的,比如二維數組是一個 m 行 n 列的矩陣:
int[,] arr = {
{1, 2, 3, 4},
{4, 2, 1, 3},
{2, 1, 3, 4},
}
而交錯數組(又叫鋸齒數組)的每一行可以有不同的大小,表示的是數組的數組。比如:
int[][] arr = {
new [] {1, 2, 3, 4},
new [] {1, 2},
new [] {1, 2, 3},
}
它們的定義、初始化、取值、賦值等都有明顯的區別。
先看多維數組:
// 多維數組的定義
int[,] arr = new int[10, 10]; // 二維數組
int[,,] arr = new int[10, 10, 10]; // 三維數組
// 多維數組的初始化 (new int[3, 2] 可以省略)
int[,] arr = new int[3, 2] { {1, 1}, {2, 2}, {3, 3} };
// 多維數組的取值
Console.WriteLine(arr[2, 1]); // 3
// 多維數組的賦值
arr[2, 1] = 10;
再對比交錯數組:
// 交錯數組的定義
int[][] arr = new int[10][]; // 二層:數組的數組
int[][][] arr = new int[10][][]; // 三層:數組的數組的數組
// 交錯數組的初始化 (new int[3][] 可以省略)
int[][] arr = new int[3][] { new [] {1}, new [] {2, 2}, new [] {3, 3, 3} };
// 交錯數組的取值
Console.WriteLine(arr[2][1]); // 3
// 多維數組的賦值
arr[2][1] = 10;
注意:多維數組每行長度必須一致;交錯數組第二個 [] 是不能有数字的。兩者的 Length 屬性意義也是不一樣的,多維數組的 Length 屬性取的是數組所有元素的總數,而交錯數組取的是第一層的數組的個數。例如:
int[,] arr1 = new int[3, 2] { { 1, 1 }, { 2, 2 }, { 3, 3 } };
Console.WriteLine(arr1.Length); // 輸出:6
int[][] arr2 = new int[3][] { new[] { 1 }, new[] { 2, 2 }, new[] { 3, 3, 3 } };
Console.WriteLine(arr2.Length); // 輸出:3
建議:除了某些像矩陣這樣的操作場景可能更適合使用多維數組,大多數場景應盡量選擇使用交錯數組。功能上多維數組可以實現的,交錯數組也都能實現,反過來不一定可以。另外,根據 stackoverflow 網友的回答(下面參考鏈接),在 .NET 中,交錯數組性能上要好於多維數組。
參考:https://bit.ly/2Mqn6P1
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※台北網頁設計公司這麼多該如何選擇?
※智慧手機時代的來臨,RWD網頁設計為架站首選
※評比南投搬家公司費用收費行情懶人包大公開
※回頭車貨運收費標準
※網頁設計最專業,超強功能平台可客製化
※別再煩惱如何寫文案,掌握八大原則!
前文傳送門:
「Python 圖像處理 OpenCV (1):入門」
「Python 圖像處理 OpenCV (2):像素處理與 Numpy 操作以及 Matplotlib 显示圖像」
「Python 圖像處理 OpenCV (3):圖像屬性、圖像感興趣 ROI 區域及通道處理」
「Python 圖像處理 OpenCV (4):圖像算數運算以及修改顏色空間」
「Python 圖像處理 OpenCV (5):圖像的幾何變換」
看到這個詞可能大家都很懵,為啥在圖像處理裏面還會有閾值。
圖像的閾值處理用大白話講就是將圖像轉化為二值圖像(黑白圖),目的是用來提取圖像中的目標物體,將背景和噪聲區分開(可以近似的認為除了目標全是噪聲)。
通常會設定一個閾值 T ,通過 T 將圖像的像素劃分為兩類:大於 T 的像素群和小於 T 的像素群。
首先可以先將圖像轉化為灰度圖像,因為在灰度圖像中,每個像素都只有一個灰度值用來表示當前像素的亮度。
接下來二值化處理可以將圖像中的像素劃分為兩類顏色,一種是大於閾值 T 的,另一種是小於閾值 T 的。
比如最常見的二值圖像:
當灰度值小於閾值 T 的時候,可以將其像素設置為 0 ,表示為黑色。
當灰度值大於閾值 T 的時候,可以將其像素設置為 255 ,表示為白色。
在 OpenCV 中,為我們提供了閾值函數 threshold() 來幫助我們實現二值圖像的處理。
函數如下:
retval, dst = threshold(src, thresh, maxval, type, dst=None)
常用的 5 中處理類型如下:
接下來這幾種處理類型有啥不同,我們一個一個來看。
這種二值處理方式最開始需要選定一個閾值 T ,從 0 ~ 255 之間,我這裏選擇出於中間的那個數 127 。
接下來的處理規則就是這樣的:
接下來開始寫代碼,看我們的馬里奧同學(不知道你們還記不記得我們的馬里奧同學):
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 圖像轉灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值圖像處理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY)
# 显示圖像
cv.imshow("src", src)
cv.imshow("result", b)
# 等待显示
cv.waitKey(0)
cv.destroyAllWindows()
這種方式和上面的二值處理非常相似,只是把處理規則給反了一下:
完整代碼如下:
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 圖像轉灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值圖像處理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY_INV)
# 显示圖像
cv.imshow("src", src)
cv.imshow("result", b)
# 等待显示
cv.waitKey(0)
cv.destroyAllWindows()
從圖像上可以看到,顏色和上面的二值圖像正好相反,大部分的位置都變成了白色。
這種方法還是需要先選定一個閾值 T ,圖像中大於該閾值的像素點被設定為該閾值,小於該閾值的保持不變。
完整代碼如下:
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 圖像轉灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值圖像處理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_TRUNC)
# 显示圖像
cv.imshow("src", src)
cv.imshow("result", b)
# 等待显示
cv.waitKey(0)
cv.destroyAllWindows()
這種方式實際上是把圖片比較亮的像素處理成為閾值,其他部分保持不變。
這種方式還是需要先選定一個閾值 T ,將小於 T 的像素點設置為 0 黑色,其他的保持不變。
完整代碼如下:
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 圖像轉灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值圖像處理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO)
# 显示圖像
cv.imshow("src", src)
cv.imshow("result", b)
# 等待显示
cv.waitKey(0)
cv.destroyAllWindows()
這個方法是亮的部分不改,把比較暗的部分修改為 0 。
這個和前面的反二值圖像很像,同樣是反閾值化為 0 ,將大於等於 T 的像素點變為 0 ,其餘保持不變。
完整代碼如下:
import cv2 as cv
src = cv.imread("maliao.jpg")
# BGR 圖像轉灰度
gray_img = cv.cvtColor(src, cv.COLOR_BGR2GRAY)
# 二值圖像處理
r, b = cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO_INV)
# 显示圖像
cv.imshow("src", src)
cv.imshow("result", b)
# 等待显示
cv.waitKey(0)
cv.destroyAllWindows()
這個方法是暗的部分不改,把比較亮的部分修改為 0 。
接下來還是給這幾種閾值處理后的圖像來個全家福,讓大家能有一個直觀的感受,代碼我也給出來,如下:
import cv2 as cv
import matplotlib.pyplot as plt
# 讀取圖像
img=cv.imread('maliao.jpg')
lenna_img = cv.cvtColor(img,cv.COLOR_BGR2RGB)
gray_img=cv.cvtColor(img,cv.COLOR_BGR2GRAY)
# 閾值化處理
ret1, thresh1=cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY)
ret2, thresh2=cv.threshold(gray_img, 127, 255, cv.THRESH_BINARY_INV)
ret3, thresh3=cv.threshold(gray_img, 127, 255, cv.THRESH_TRUNC)
ret4, thresh4=cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO)
ret5, thresh5=cv.threshold(gray_img, 127, 255, cv.THRESH_TOZERO_INV)
# 显示結果
titles = ['Gray Img','BINARY','BINARY_INV','TRUNC','TOZERO','TOZERO_INV']
images = [gray_img, thresh1, thresh2, thresh3, thresh4, thresh5]
# matplotlib 繪圖
for i in range(6):
plt.subplot(2, 3, i+1), plt.imshow(images[i],'gray')
plt.title(titles[i])
plt.xticks([]),plt.yticks([])
plt.show()
如果有需要獲取源碼的同學可以在公眾號回復「OpenCV」進行獲取。
https://blog.csdn.net/Eastmount/article/details/83548652
http://www.woshicver.com/
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※Google地圖已可更新顯示潭子電動車充電站設置地點!!
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※別再煩惱如何寫文案,掌握八大原則!
※網頁設計最專業,超強功能平台可客製化
※回頭車貨運收費標準
這兩年來,向我提問的人真的可以稱得上是不計其數了。哎呀,一不小心又用了個成語,裝逼在無形當中啊。雖然有點誇張,但說真的不算少了,請摸着良心問問,你是不是就問過?
但我特別討厭一種行為,就是用手機拍電腦屏幕的照片來提問。為什麼這麼說呢?請你立刻現在馬上拿起手機自己拍張照試試。如果相機不給力,屏幕像素又低,我的天吶,看這種照片上的代碼真的是痛苦啊!
鑒於此,我在群里三番五次提醒過,請用截屏,不要用手機拍照!但隔三差五還是有人這樣照干不誤,我很生氣,真的。
你提問,肯定想要得到回答,對吧?如果你用心,那回答的人也就會用心,即便是你的問題石沉大海了,那也是對自己的一種負責任啊。
在提問題的時候,你應該做好充分的準備:
那可能有些人會不服氣,拋下一句狠話:“你這大佬也太不負責任了吧?我就是沒有找到答案才過來找你的啊?我努力了呀?”
冷靜一下哈,提個問題你脾氣這麼大,這麼焦躁,怎麼可能得到答案,對不對?
讓你做準備不是我在逃避責任,而是在教你,教會你自己怎麼解決問題。就拿我來說吧,我已經很少向別人提問題了,除了那隻橡皮鴨。
有生以來,我遇到最痛苦的一個問題就是“可學上往”(寫錯別字,我是認真的),真的,當時我問遍了所有我能問的群,我能問的人,因為這個問題很無解,你了解的。
在向別人求助之前,可行的解決方案就是去問度娘,對吧,但度娘又怎麼會告訴答案呢。有些好心的同行會扔一句“VVPPNN”,然後我就搜啊搜——搜不到答案。
大概折騰了兩周的時間,還是青銅時代群里的一位小夥伴告訴了我答案。由於最近風聲緊,我就不提了。不過,我需要坦誠一句,我“可學上往”純粹是為了寫技術文章用(偶爾 1024 下,不騙你),因為有時候,一些知識點,我自己還沒有掌握,必須得先學習一下,然後才能輸出給讀者。
但度娘有時候給的答案,真的是讓你懷疑人生,那些狗屁不是的帖子它的搜索結果里全都有。但骨骼這方面做得真的是不錯,有一說一。
(這篇文章寫了不少錯別字,不要怪我,我也是被逼的,忍耐一下,看不懂的地方就 YY 一下)
為什麼我很少提問題了呢?
不知道你有沒有遇到這樣的情況?反正我真的是經歷了無數次。有時候,答案找不到,我都着急得想捶爆鍵盤了,甚至狠狠地在桌子上捶了一拳,疼痛的感覺讓我冷靜下來。
於是我想到了那隻橡皮鴨,我在心裏對它說:“為什麼這台服務器上的首頁打開時間需要一分多鍾,而另外一台服務器上只需要不到三秒鐘?”
“那你有沒有對比兩台服務器有什麼不同嗎?”橡皮鴨開口回答道。
“比對了呀,完全一樣啊,代碼一樣,只是說參數不一樣,war 包也一樣,都用的 Tomcat 啊。”我憤憤不平地說。
“操作系統一樣嗎?”橡皮鴨耐心地開導着我。
“不太一樣,一台是 Windows Server,一台是 CentOS。”我回答道。
“那你有沒有想過把 Windows Server 那台也換成是 CentOS?”橡皮鴨依然很冷靜。
“不會吧,就因為操作系統不同,首頁打開速度差別就會這麼大?”
“你試試唄,把環境備份下,反正現在切換一下操作系統又不麻煩。”
“好的,聽你的,橡皮鴨。”
一個小時后,神奇的事情發生了,原來問題真的是因為 Windows Server 啊!果然不如 Linux 靠譜啊!
以上就是三年前我真實經歷過的,當時有一台遺留的 Windows Server,我就沒想着把操作系統換成是 Linux 的,然後同樣的 war 包放上去后,首頁打開速度超慢,我嘗試過對首頁加上緩存,減少 JavaScript、CSS 文件的大小,對它們進行壓縮,甚至減少了從後台獲取數據的量,但所有的嘗試都於事無補。
最後,真的是橡皮鴨幫助到了我。當你把自己完全投入到一種假想當中,去問一個透徹而詳盡的問題時,答案真的會悄無聲息地找上門來。
大多數情況下,並不是因為我們的知識庫儲備不足,而是我們尋找問題的方向發生了偏離,一旦我們意識到了這種錯誤,問題也就不再是問題。
當然了,當你嘗盡一切辦法,仍然找不到答案的話,找大牛、找朋友、找同事、找社群,不要臉地提問吧,別怕自己受到打擊,別怕問題無人解答,你要做的是,把問題梳理得越來越清晰,也許答案就在你那裡。
如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。回復關鍵字「簡歷」更有一份技術大佬整理的優質簡歷模板,助你一臂之力。
本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。
我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻。
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※網頁設計公司推薦不同的風格,搶佔消費者視覺第一線
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※南投搬家公司費用需注意的眉眉角角,別等搬了再說!
※教你寫出一流的銷售文案?
※回頭車貨運收費標準
※別再煩惱如何寫文案,掌握八大原則!
相信大部分開發人員,或多或少都看過或寫過併發編程的代碼。併發關鍵字除了Synchronized,還有另一大分支Atomic。如果大家沒聽過沒用過先看基礎篇,如果聽過用過,請滑至底部看進階篇,深入源碼分析。
看過Synchronized相關文章的小夥伴應該知道其是不安全的,再次用代碼應驗下其不安全性:
public class testInt { static int number = 0; public static void main(String[] args) throws Exception { Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < 100000; i++) { number = number+1; } } }; Thread t1 = new Thread(runnable); t1.start(); Thread t2 = new Thread(runnable); t2.start(); t1.join(); t2.join(); System.out.println("number:" + number); } }
運行結果:
在上面的例子中,我們定義一個初始值為0的靜態變量number,再新建並運行兩個線程讓其各執行10萬次的自增操作,如果他是線程安全的,應該兩個線程執行后結果為20萬,但是我們發現最終的結果是小於20萬的,即說明他是不安全的。
在之前Synchronized那篇文章中說過,可以在number=number+1這句代碼上下加Synchronized關鍵字實現線程安全。但是其對資源的開銷較大,所以我們今天再看下另外一種實現線程安全的方法Atomic。
Atomic是jdk提供的一系列包的總稱,這個大家族包括原子整數(AtomicInteger,AtomicLong,AtomicBoolean),原子引用(AtomicReference,AtomicStampedReference,AtomicMarkableReference),原子數組(AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray),更新器(AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater)。
AtomicInteger,AtomicBoolean,AtomicLong三者功能類似,咱就以AtomicInteger為主分析原子類。
先看下有哪些API,及其他們具體啥功能:
public class testInt { public static void main(String[] args) { //定義AtomicInteger類型的變量,值為1 AtomicInteger i = new AtomicInteger(1); //incrementAndGet方法先新增1再返回,所以打印2,此時i為2 System.out.println(i.incrementAndGet()); //getAndIncrement方法先返回值再新增1,所以打印2,此時i為3 System.out.println(i.getAndIncrement()); //get方法返回當前i值,所以打印3,此時i為3 System.out.println(i.get()); //參數為正數即新增,getAndAdd方法先返回值再新增666,所以打印3,此時i為669 System.out.println(i.getAndAdd(666)); //參數為負數即減去,getAndAdd方法先返回值再減去1,所以打印669,此時i為668 System.out.println(i.getAndAdd(-1)); //參數為正數即新增,addAndGet方法先新增666再返回值,所以打印1334,此時i為1334 System.out.println(i.addAndGet(666)); //參數為負數即減去,addAndGet方法先減去-1再返回值,所以打印1333,此時i為1333 System.out.println(i.addAndGet(-1)); //getAndUpdate方法IntUnaryOperator參數是一個箭頭函數,後面可以寫任何操作,所以打印1333,此時i為13331 System.out.println(i.getAndUpdate(x -> (x * 10 + 1))); //最終打印i為13331 System.out.println(i.get()); } }
執行結果:
public class testInt { //1.定義初始值為0的AtomicInteger類型變量number static AtomicInteger number = new AtomicInteger(0); public static void main(String[] args) throws Exception { Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < 100000; i++) { //2.調用incrementAndGet方法,實現加1操作 number.incrementAndGet(); } } }; Thread t1 = new Thread(runnable); t1.start(); Thread t2 = new Thread(runnable); t2.start(); t1.join(); t2.join(); System.out.println("number:" + number.get()); } }
我們可以看到運行結果是正確的20萬,說明AtomicInteger的確保證了線程安全性,即在多線程的過程中,運行結果還是正確的。但是這存在一個ABA問題,下面將原子引用的時候再說,先立個flag。
我們以incrementAndGet方法為例,看下底層是如何實現的,AtomicInteger類中的incrementAndGet方法調用了Unsafe類的getAndAddInt方法。
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
我們看下getAndAddInt方法,裏面有個循環,直接值為compareAndSwapInt返回值為true,才結束循環。這裏就不得不提CAS,這就是多線程安全性問題的解決方法。
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
線程1和線程2同事獲取了主內存變量值0,線程1加1並寫入主內存,現在主內存變量值1,線程2也加2並嘗試寫入主內存,這個時候是不能寫入主內存的,因為會覆蓋掉線程1的操作,具體過程如下圖。
CAS是在線程2嘗試寫入內存的時候,通過比較並設置(CompareAndSet)發現現在主內存當前值為1,和他剛開始讀取的值0不一樣,所以他會放棄本次修改,重新讀取主內存的最新值,然後再重試下線程2的具體邏輯操作,再次嘗試寫入主內存。如果這時候線程1,再次對主內存進行了修改,線程2發現現在主內存的值又和預期不一樣,所以將放棄本次修改,再次讀取主內存最新值,再次重試並嘗試寫入主內存。我們可以發現這是一個重複比較的過程,即直到和預期初始值一樣,才會寫入主內存,否則將一直讀取重試的循環。這就是上面for循環的意義。
CAS的實現實際上利用了CPU指令來實現的,如果操作系統不支持CAS,還是會加鎖的,如果操作系統支持CAS,則使用原子性的CPU指令。
在日常使用中,我們不止對上述基本類型進行原子操作,而是需要對一些複雜類型進行原子操作,所以需要AtomicReference。
先看不安全的BigDecimal類型:
public class testReference { static BigDecimal number = BigDecimal.ZERO; public static void main(String[] args) throws Exception { Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { number=number.add(BigDecimal.ONE); } } }; Thread t1 = new Thread(runnable); t1.start(); Thread t2 = new Thread(runnable); t2.start(); t1.join(); t2.join(); System.out.println(number); } }
運行結果如下圖,我們可以看到兩個線程,自循環1000次加1操作,最終結果應該是2000,可是結果小於2000。
public class testReference { //定義AtomicReference類型BigDecimal變量 static AtomicReference<BigDecimal> number = new AtomicReference<BigDecimal>(BigDecimal.ZERO); public static void main(String[] args) throws Exception { Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { //手動寫循環+CAS判斷 while(true){ BigDecimal pre=number.get(); BigDecimal next=number.get().add(BigDecimal.ONE); if(number.compareAndSet(pre,next)) { break; } } } } }; Thread t1 = new Thread(runnable); t1.start(); Thread t2 = new Thread(runnable); t2.start(); t1.join(); t2.join(); System.out.println(number.get()); } }
運行結果如下:
在上面CAS過程中,是通過值比較來知曉是不是能夠更新成功,那如果線程1先加1再減1,這樣主內存還是原來的值,即線程2還是可以更新成功的。但是這樣邏輯錯了,線程1已經發生了修改,線程2不能直接更新成功。
代碼:
public class testInt { static AtomicInteger number = new AtomicInteger(0); public static void main(String[] args) throws Exception { Thread t1 = new Thread(new Runnable() { @Override public void run() { int a = number.get(); System.out.println("開始number:" + a); try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(number.compareAndSet(a, a++)); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("開始增加操作"); int a = number.incrementAndGet(); System.out.println("當前number:" + a); int b = number.decrementAndGet(); System.out.println("當前number:" + b); } }); t2.start(); t1.join(); t2.join(); } }
我們看線程2對其進行了一系列操作,但是最後打印了還是true,表示可以更新成功的。這顯然不對。
那我們可以使用AtomicStampedReference,為其添加一個版本號。線程1在剛開始讀取主內存的時候,獲取到值為0,版本為1,線程2也獲取到這兩個值,線程1進行加1,減1的操作的時候,版本各加1,現在主內存的值為0,版本為2,而線程2還拿着預計值為0,版本為1的數據嘗試寫入主內存,這個時候因版本不同而更新失敗。具體我們用代碼試下:
public class testInt { static AtomicStampedReference<Integer> number = new AtomicStampedReference<Integer>(0, 0); public static void main(String[] args) throws Exception { Thread t1 = new Thread(new Runnable() { @Override public void run() { int a = number.getReference(); int s = number.getStamp(); System.out.println("開始number:" + a + ",stamp:" + s); try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(number.compareAndSet(a, a + 1, s, s + 1)); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println("開始增加操作"); int a = number.getReference(); int s = number.getStamp(); number.compareAndSet(a, a + 1, s, s + 1); System.out.println("當前number:" + a + ",stamp:" + (s + 1)); a = number.getReference(); s = number.getStamp(); number.compareAndSet(a, a - 1, s, s + 1); System.out.println("當前number:" + a + ",stamp:" + (s+1)); } }); t2.start(); t1.join(); t2.join(); } }
我們可以看到每次操作都會更新stamp(版本號),在最後對比的時候不僅比較值,還比較版本號,所以是不能更新成功的,false.
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray三者類似,所以以AtomicIntegerArray為例,我們可以將下面AtomicIntegerArray看做是AtomicInteger類型的數組,其底層很類似,就不詳細寫了。
AtomicIntegerArray array = new AtomicIntegerArray(10); array.getAndIncrement(0); // 將第0個元素原子地增加1 AtomicInteger[] array = new AtomicInteger[10]; array[0].getAndIncrement(); // 將第0個元素原子地增加1
字段更新器和原子累加器比較簡單,這裏就不說了。
LongAdder是jdk1.8之後新加的,那為什麼要加他?這個問題,下面將回答,我們先看下如何使用。
public class testLongAdder { public static void main(String[] args) throws Exception { LongAdder number = new LongAdder(); Runnable runnable = new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++) { number.add(1L); } } }; Thread t1 = new Thread(runnable); Thread t2 = new Thread(runnable); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("number:" + number); } }
我們可以看到LongAdder的使用和AtomicLong大致相同,使用兩個線程Thread1,Thread2對number值各進行一萬次的自增操作,最後的number是正確的兩萬。
那問題來了,既然AtomicLong能夠完成對多線程下的number進行線程安全的操作,那為什麼還要LongAdder?我們先來段代碼比較下,兩個在結果都是正確的前提下,性能方面的差距。
public class testLongAdder { public static void main(String[] args) { //1個線程,進行100萬次自增操作 test1(1,1000000); //10個線程,進行100萬次自增操作 test1(10,1000000); //100個線程,進行100萬次自增操作 test1(100,1000000); } static void test1(int threadCount,int times){ long startTime=System.currentTimeMillis(); AtomicLong number1=new AtomicLong(); List<Thread> threads1=new ArrayList<>(); for(int i=0;i<threadCount;i++) { threads1.add(new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < times; j++) { number1.incrementAndGet(); } } })); } threads1.forEach(thread -> thread.start()); threads1.forEach(thread ->{ try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } ); long endTime=System.currentTimeMillis(); System.out.println("AtomicLong:"+number1+",time:"+(endTime-startTime)); LongAdder number2=new LongAdder(); List<Thread> threads2=new ArrayList<>(); for(int i=0;i<threadCount;i++) { threads2.add(new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < times; j++) { number2.add(1); } } })); } threads2.forEach(thread -> thread.start()); threads2.forEach(thread ->{ try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } ); System.out.println("LongAdder:"+number2+",time:"+(System.currentTimeMillis()-endTime)); } }
上述代碼對比了1個線程,10個線程,100個線程在進行100百次自增操作后,AtomicLong和LongAdder所花費的時間。通過打印語句,我們發現在最終number1和number2都正確的基礎上,LongAdder花費的時間比AtomicLong少了一個量級。
那為什麼會導致這種情況,我們就要從源碼層面分析。AtomicLong為什麼效率低?因為如果線程數量一多,尤其在高併發的情況下,比如有100個線程同時想要對對象進行操作,肯定只有一個線程會獲取到鎖,其他99個線程可能空轉,一直循環知道線程釋放鎖。如果該線程操作完畢釋放了鎖,其他99個線程再次競爭,也只有一個線程獲取鎖,另外98個線程還是空轉,直到鎖被釋放。這樣CAS操作會浪費大量資源在空轉上,從而使得AtomicLong在線程數越來越多的情況下越來越慢。
AtomicLong是多個線程對同一個value值進行操作,導致多個線程自旋次數太多,性能降低。而LongAdder在無競爭的情況,跟AtomicLong一樣,對同一個base進行操作,當出現競爭關係時則是採用化整為零的做法,從空間換時間,用一個數組cells,將一個value拆分進這個數組cells。多個線程需要同時對value進行操作時候,可以對線程id進行hash得到hash值,再根據hash值映射到這個數組cells的某個下標,再對該下標所對應的值進行自增操作。當所有線程操作完畢,將數組cells的所有值和無競爭值base都加起來作為最終結果。
我們先看下LongAdder裏面的字段,發現其裏面沒有,主要是在其繼承的Stripped64類中,有下面四個主要變量。
/** CPU數量,即cells數組的最大長度*/ static final int NCPU = Runtime.getRuntime().availableProcessors(); /** *cells數組,為2的冪,2,4,8,16.....,方便以後位運算 */ transient volatile Cell[] cells; /** * 基值,主要用於沒有競爭的情況,通過CAS更新。 */ transient volatile long base; /** * 調整單元格大小(擴容),創建單元格時使用的鎖。 */ transient volatile int cellsBusy;
下面是add方法開始。
public void add(long x) { //as:cells數組的引用 //b:base的基礎值 //v:期望值 //m:cells數組大小 //a:當前數組命中的單元 Cell[] as; long b, v; int m; Cell a; //as不為空(cells已經初始化過,說明之前有其他線程對初始化)或者CAS操作不成功(線程間出現競爭) if ((as = cells) != null || !casBase(b = base, b + x)) { //初始化uncontented,true表示未競爭(因為有兩個情況,這裏先初始化,後面對其修改,就能區分這兩種情況) boolean uncontended = true; //as等於null(cells未初始化) //或者線程id哈希出來的下標所對應的值為空(cell等於空),getProbe() & m功能是獲取下標,底層邏輯是位運算 //或者更新失敗為false,即發生競爭,取非就為ture if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x))) //進入到if裏面,說明更新case失敗,或者更新某個cell也失敗了,或者cell為空,或者cells為空 longAccumulate(x, null, uncontended); } }
從LongAdder調用Stripped64的longAccumulate方法,主要是初始化cells,cells的擴容,多個線程同時命中一個cell的競爭操作。
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) { //x:add的值,fn:為null,wasUncontended:是否發生競爭,true為發生競爭,false為不發生競爭 int h;//線程的hash值 //如果該線程為0,即第一次進來,所以ThreadLocalRandom強制初始化線程id,再對其hash if ((h = getProbe()) == 0) { ThreadLocalRandom.current(); h = getProbe(); wasUncontended = true; } //擴容意向,為false肯定不擴容,為true可能擴容 boolean collide = false; //死循環 for (;;) { //as:cells數組的引用 //a:當前線程命中的cell //n:cells的長度 //v:當前線程命中的cell所擁有的value值 Cell[] as; Cell a; int n; long v; //cells不為空 if ((as = cells) != null && (n = as.length) > 0) { //當前線程命中的cell為空,下面邏輯是新增cell if ((a = as[(n - 1) & h]) == null) { if (cellsBusy == 0) { // Try to attach new Cell Cell r = new Cell(x); // Optimistically create if (cellsBusy == 0 && casCellsBusy()) { boolean created = false; try { // Recheck under lock Cell[] rs; int m, j; if ((rs = cells) != null && (m = rs.length) > 0 && rs[j = (m - 1) & h] == null) { rs[j] = r; created = true; } } finally { cellsBusy = 0; } if (created) break; continue; // Slot is now non-empty } } collide = false; } //發生競爭 else if (!wasUncontended) // CAS already known to fail wasUncontended = true; // Continue after rehash //沒有競爭,嘗試修改當前線程對應的cell值,成功跳出循環 else if (a.cas(v = a.value, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) break; //如果n大於CPU最大數量,不可擴容 else if (n >= NCPU || cells != as) collide = false; // At max size or stale else if (!collide) collide = true; //獲取到了鎖,進行擴容,為2的冪, else if (cellsBusy == 0 && casCellsBusy()) { try { if (cells == as) { // Expand table unless stale Cell[] rs = new Cell[n << 1];//左移一位運算符,數量加倍 for (int i = 0; i < n; ++i) rs[i] = as[i]; cells = rs; } } finally { cellsBusy = 0; } collide = false; continue; // Retry with expanded table } h = advanceProbe(h); } //cells等於空,並且獲取到鎖,開始初始化工作,創建結束釋放鎖,繼續循環 else if (cellsBusy == 0 && cells == as && casCellsBusy()) { boolean init = false; try { // Initialize table if (cells == as) { Cell[] rs = new Cell[2]; rs[h & 1] = new Cell(x); cells = rs; init = true; } } finally { cellsBusy = 0; } if (init) break; } else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) break; // Fall back on using base } }
結束了,撒花。這篇主要說了Atomic的一些使用,包括Atomic原子類(AtomicInteger,AtomicLong,AtomicBoolean),Atomic原子引用(AtomicReference,AtomicStampedReference),以及1.8之後LongAdder的優勢,源碼分析。過程還穿插了一些CAS,ABA問題引入和解決方式。
Java多線程進階(十七)—— J.U.C之atomic框架:LongAdder
CAS原理
Java 8 Performance Improvements: LongAdder vs AtomicLong
AtomicInteger深入理解
原子操作類AtomicInteger詳解
玩轉Java併發工具,精通JUC,成為併發多面手
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!
※想知道最厲害的網頁設計公司"嚨底家"!
※別再煩惱如何寫文案,掌握八大原則!
※產品缺大量曝光嗎?你需要的是一流包裝設計!
※回頭車貨運收費標準
※台中搬家公司費用怎麼算?
精英型和豪華型相差10000元了,不多倒是也多出了這麼多的配置,像無鑰匙啟動、真皮坐椅、中控屏幕、氙氣大燈、自動空調等這些配置,就算自己後期去加裝也要花不少錢,況且這些配置最好還是原車帶的比較靠譜,自己加裝的東西出問題又不在質保範圍內。
朗逸的銷量雖好,但是身後也有無數強敵,其中英朗算是一個強有力的競爭對手。英朗在10月份的銷量為34629輛,1-10月份累加銷量為303116輛,這數據真的是亮瞎眼。
英朗的指導價為10.99-15.99萬,車身尺寸為4587*1798*1463mm,軸距為2640mm,英朗的車長沒有達到4600mm以上算是一個遺憾,因為同級別的車子長度都超過了4600mm,不過好在英朗的外觀設計比較大氣,同時軸距不小,所以後排空間還是比較充裕的。
英朗的中控內飾用料以硬塑料為主,這也是這個級別通常的配置,不過英朗的內飾看起來設計感十足,層次感突出,體現出了別克內飾設計的“環繞的感覺”,看起來稍顯高檔。
英朗的動力系統為1.5L 114馬力+5擋手動/6擋手自一體、1.4T 144馬力+7擋雙離合,目前英朗有九款在售車型,消費者面對這麼多的車型,肯定是有點迷茫的,所以今天小編就給大家看一下英朗的哪款車性價比最高,爭取在買車的時候買到最實惠的從車型。
1.5L車型的手動擋和自動的差價在1萬元,也就是1萬元買了一台6AT變速箱。進取型、精英型、豪華型的其它配置是一樣。
低配和次低配差價8000元,主要多了前排側氣囊、天窗、雷達等,對於那些經常吸煙或者買車必須要天窗的消費者來說,精英版還是合適的。但是對於不要求配置的消費者來說,進取型就足夠了。
精英型和豪華型相差10000元了,不多倒是也多出了這麼多的配置,像無鑰匙啟動、真皮坐椅、中控屏幕、氙氣大燈、自動空調等這些配置,就算自己後期去加裝也要花不少錢,況且這些配置最好還是原車帶的比較靠譜,自己加裝的東西出問題又不在質保範圍內。
所以豪華版是為那些比較看重配置消費者準備的。
說完了1.5L車型,接下來該說一說1.4T車型,1.4T車型有三個型號,分別是精英型、豪華型、旗艦型。其中精英型和1.5L的精英型配置相同。
所以頂配車型更多的只是為了提高自身形象,性價比較低。倒是精英和豪華型具有一定的性價比。
總結:其實我們是不怎麼建議消費者選擇雙離合車型,因為變速箱的調教還不是很完美,0-100km/h加速大約為10.2s,成績也不是很理想。所以最好的選擇就是1.5L車型,如果資金不充足進取型就可以,在乎天窗了就精英型,對配置有要求了可以選擇豪華型。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】
※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益
※教你寫出一流的銷售文案?
※別再煩惱如何寫文案,掌握八大原則!