為什麼需要雲IDE?

一.雲 IDE?是新概念嗎?

不不不,早在 2010 年就有成熟的產品了:Cloud9 IDE

時至如今,雲 IDE 已經相當常見了,比如:

  • Cloud9:亞馬遜為其雲計算服務提供的 IDE

  • Eclipse Che及Eclipse Theia:老牌 IDE 的雲化版本

  • Coder:以及前不久開源的code-server

  • Expo Snack:React Native 的雲端開發環境

  • Coding:國內的雲 IDE 產品

  • codesandbox:面向 Web 項目的雲 IDE

 

二.為什麼需要雲 IDE?

一般的開發工作流中,我們會建立一套本地環境,包括順手的 IDE 和整套本地工具,但這種本地開發模式存在一些問題:

  • 開發機性能要求高:冷編譯一次 40 分鐘

  • 開發環境配置複雜:工具環境能夠通過容器技術或一系列版本管理工具(如 nvm)解決,但網絡、安全等環境就不那麼容易配置了

  • 依賴特定設備:休假可以,但是帶上電腦,24 小時 On Call,10 分鐘無響應記大過一次

  • 巨型代碼庫的管理難題:巨型代碼庫切換個 Git 分支,動輒半小時

於是,遠程開發的理念應運而生,連接遠程測試服務器,直接在服務器環境完成日常開發工作,免去本地重建並維護一套測試環境的成本

現有的遠程開發模式下,工程師大多通過終端交互工具連接遠程機器,並通過 vim、naro 等文本編輯器來開發。而這些編輯器通常對項目文件管理、運行任務、調試器、智能提示/補全等基礎功能的支持不那麼友好,並不能像本地 IDE 一樣提供舒適的開發環境。開發體驗下降的同時,也限制了開發效率

那麼,有沒有兩全其美的辦法?

有,把 IDE 也搬到遠程,即雲 IDE

P.S.或者把雲拽下來,即,本地 IDE 提供遠程開發能力,但理念上與雲 IDE 並無二致(本地 IDE 相當於瀏覽器),具體見VSCode 遠程開發套件

 

三.雲 IDE 能解決什麼問題?

綜上,IDE 上雲能解決兩方面問題:

  • 本地開發模式難以解決的問題:不再要求本地機器十分強大,不必擔心環境,不依賴特定辦公設備,硬盤也不用再瘋狂旋轉

  • 遠程開發模式的體驗問題:不再是 Web Editor 玩具,而與本地 IDE 一樣順手的開發環境

雲 IDE 也是遠程開發模式的一種實現形式,自然能夠解決本地開發模式所存在的一些難題

同時,作為 Web Editor 的升級形態,雲 IDE 能夠提供更好的遠程開發體驗,補足遠程開發模式的體驗短板,解決工具不稱手限制開發效率的問題

 

四.雲 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

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

【其他文章推薦】

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

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

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

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

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

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

如何拿到阿里P8 Offer-候選人視角談面試

阿里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以及轉化率。

這就是領域突破跟業務增值兩點的簡單闡釋。

能力要求

在崗位特徵之外,下面是三點具體的能力要求。比如說業務理解與實現,技術的沉澱與傳承,團隊的建設與發展等,字比較多,這裏就不一一念出來。

歸結起來其實主要就是三點。

  • 第一個就是具備較強的技術影響力以及業務影響力。
  • 第二個對業務有深入的思考,參与業務決策。
  • 第三個是參与團隊集團的人才建設。

準備簡歷

接下來講一下怎麼準備簡歷。

首先最重要的一點,就是要充分展示自己的能力,以及自己能力跟崗位的匹配度。也就是說告訴面試官,你是他們想要的那一個人。

第二個就是要注意能力舉證的邏輯,最好是数字說話。比如說介紹首屏性能優化,你必須要告訴面試官說你通過哪一些關鍵的手段提升了哪一些性能。在過往的面試中經常會遇到,有些候選人,他在簡歷裏面可能寫了上千字,說他做了什麼事情,但是他最終通過哪一些關鍵的步驟,取得了什麼樣的成果,沒有體現出來。這對看簡歷的面試官來說,也是一個比較頭大的事情。

最後一點,就是要謹慎挑選你的簡歷素材。簡而言之就是不要給自己挖坑,寫進簡歷裏面的每一句話,每一個数字都要經得起面試官的挑戰。被問倒的話是其次,千萬不要弄虛作假。因為弄虛作假,不管是在哪個企業裏面,都是紅線,不能觸碰。

準備面試問題

最後一個就是準備面試的問題。我自己也做了挺長時間的面試官,也經常面試別人。所以在面試之前,我通常會進行自我模擬面試,主要問自己以下幾個問題:

  1. 如果我是面試官我會問什麼問題?
  2. 如果我是面試官,我希望聽到什麼樣的回答?
  3. 我的優勢、不足在哪裡,如何揚長避短?

模擬面試的話,需要對崗位的能力模型、崗位的要求、自我的能力特長 以及 產出有比較清晰的認識。如果沒有什麼底的話,建議可以找比較有經驗的朋友幫自己把把關。也可以藉助搜索引擎去網上看一下,需要哪些知識和能力。

面試建議

接下來就是具體的面試過程中的建議。這裡是長者說過的一段話,我自己挺喜歡的,跟大家分享一下。

一個人的命運當然要靠自我的奮鬥,同時也要考慮到歷史的進程。

為什麼這麼說呢?因為面試本身是一個充滿了不確定性的事情。七分看實力,三分看運氣。

面試的不確定性

拋開個人的能力,以及面試的臨場表現,不確定性它主要體現在哪裡呢?

首先是面試官的個人偏好。比如你自己比較擅長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

謝謝大家。就是今天的分享主要就到這裏。然後大家看一下大家有什麼問題。

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維修中心

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

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

[C#.NET 拾遺補漏]02:數組的幾個小知識

閱讀本文大概需要 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網頁設計為架站首選

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

※回頭車貨運收費標準

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

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

視頻處理之OSD

欲觀原文,請君移步

OSD簡介

OSD,on-screen display的簡稱,即屏幕菜單式調節方式。一般我們按一下Menu鍵后屏幕彈出的显示器各項調節項目信息的矩形菜單,比如調亮度,色調,飽和度等信息,這個显示這個菜單的功能就是視頻行業的OSD。

基於FPGA的OSD設計與實現

1 Xilinx OSD IP功能

  • 支持最多8個layer
  • 背景顏色可編程
  • 位置,大小,顏色,透明度(alpha)可編程
  • 支持RGB和YUV視頻流

2 硬件結構框圖

硬件平台是基於xilinx xc7z035芯片開發的,關鍵模塊框圖如下圖1所示。

  • 一個時序產生模塊Video Timing Controller(在本次實驗中採用1080P標準時序)

  • 首先PL端將視頻流通過VDMA讀出,輸出接口為AXI4-Stream的數據流

  • 然後視頻流進入OSD IP(OSD輸入輸出也是AXI4-Stream接口)

  • 最後OSD輸出數據流與1080P時序同時送入到AXI4-Stream to Video Out模塊,輸出為HDMI接口

下面小編會詳細介紹OSD IP的例化與使用

3 OSD PL端

如果在使用OSD IP過程中出現如下錯誤,請在xilinx官網上申請OSD的License,這裏不再詳述如何申請License。

首先在Block Design中加入Video On Screen Display,打開後會看到如下圖2。

(1) 勾選AXI-Lite Interface:PS端通過AXI-Lite進行配置該IP

(2) Video Format選擇RGB

(3) Layers選擇2

(4) Layer Configuration:LAYER0選擇外部視頻流,也就是實時視頻,LAYER1選擇Internal,PS端可以控制進行圖文疊加

點擊Screen Layout Options,如圖3所示

(1) Background size(選擇背景大小)里寬度輸入1920,高度輸入1080

(2) Background color輸入背景顏色,這裏選擇黑色,可以通過axi-lite來控制

(3) layer0因為選擇的是外部視頻流,所以比如設置為外部視頻流分辨率,輸錯了,可能無法正常使用,小編已經遇到過了,本文使用1920×1080外部視頻流

(4) layer1因為選擇的是內部圖像控制器,所以無所謂設置什麼,可以通過axi-lite控制,所以選擇默認即可

點擊LAYER 1 Options出現圖4界面:因為是內部圖像控制器,所以可以進行一些配置,比如顏色多少,字符數目,比特像素等等,這裏小編選擇的是默認。

配置完OSD IP后,將其AXI-lite掛在總線上即可,然後保存Block Design即可。

注:點開Address Editor,一定要確定該IP被分配地址空間,如果沒有,點擊一下自動分配(小編遇到的問題就是這個ip地址空間在unmap里,導致後續PS端OSD IP無法初始化)。

4 OSD PS端

下面就是常規操作include bitstream導出SDK,其實就是生成hdf文件,硬件信息。然後就launch SDk。如圖4所示,可以看到hdf文件里都有什麼。

新建一個design_top的空工程,如圖5所示,在design_top_bsp下面可以看到system.mss文件,在紅框中找到OSD IP。

點擊import examples,出現如圖6所示,勾選對話框后選擇OK(不會玩IP先整個example)。

打開XosdSelfTestExample.c即可看到該例子程序是怎麼個處理流程(圖7為main函數,圖8為使用流程)小編稱之為PS端IP使用三步法

  • 第一步先進行lookup該IP

  • 第二步就是初始化該IP

  • 第三步就是使用IP

運行一下,結果如圖9所示,可以看到UART會打印OSD成功信息。

OSD 實例

閑話不說,上面經過了OSD example,小編也作為一個PS端初學者來玩一玩。

尷尬的是經常在寫c代碼的時候想着寫begin…end,真是verilog寫習慣了,思想難以改變,原來是花括弧啊{}

初始化模塊如下

在初始化后必須將該模塊進行複位,然後,否則該IP啟動不了。

int OsdInit(int DeviceID)
{
	int Status;
	/* Initialize the OSD instance */
	OsdCfgPtr = XOSD_LookupConfig(DeviceID);
	Status = XOSD_CfgInitialize(&Osd, OsdCfgPtr,OsdCfgPtr->BaseAddress);
	if (Status != XST_SUCCESS)		return 1;
	/* Reset the devices */
	XOSD_Reset(&Osd);
	/* Enable the OSD device and tell it to pick up the register changes	*/
	XOSD_Enable(&Osd);
	XOSD_RegUpdateEnable(&Osd);
	return 0;
}

配置模塊

這裏需要注意的是把BANKIndex輸入為1,否則會進行字符填寫方式就需要改變,不符合操作習慣XOSD_LoadCharacterSetBank(&Osd, Gcindex, 1, (u32 *)Font);

void Graphics_setting(u8 Gcindex,u8 LayerPriority,u32 ColorData[],u32 *TextData)
{
	int width = 1920;
	int height = 1080;
	int LayerAlphaValue = 0xFF;
	int LayerGlobalAlphaEnable = 0;
	XOSD_SetLayerAlpha(&Osd, Gcindex, LayerGlobalAlphaEnable,LayerAlphaValue);
	XOSD_SetLayerPriority(&Osd, Gcindex, LayerPriority);
	XOSD_SetLayerDimension(&Osd, Gcindex, 0, 0, width, height);
	XOSD_EnableLayer(&Osd, Gcindex);
	/* Load color, font and text and set the active banks */
	XOSD_LoadColorLUTBank(&Osd, Gcindex, 0, ColorData);
	//set BankIndex is 1(fit our keyboard)
	XOSD_LoadCharacterSetBank(&Osd, Gcindex, 1, (u32 *)Font);
	XOSD_LoadTextBank(&Osd, Gcindex, 0, (u32 *)TextData);
	XOSD_SetActiveBank(&Osd, Gcindex, 0, 0, 0, 0);
}

圖文疊加模塊

void OsdDrawText(int Gcindex,int x_pos, int y_pos, int color, int string_index, int text_size)
{
	u32 Instruction[XOSD_INS_SIZE];
	u16 ObjType = XOSD_INS_OPCODE_BOXTXT; /* A text string 	XOSD_INS_OPCODE_TXT*/
	u8 ObjSize = (text_size<<4); /* Text Scale factor (1x, 2x, 4x, 8x)	*/
	u16 XStart = x_pos; /* Horizontal start pixel of the text	*/
	u16 YStart = y_pos; /* Vertical start line of the text */
	u16 XEnd = x_pos; /* Horizontal end pixel of the text (not	used) */
	u16 YEnd = y_pos; /* Vertical end line of the text (must be same as YStart) */
	u8 TextIndex = string_index; /* Index of Text String */
	u8 ColorIndex = color; /* Color used to draw text */


	XOSD_CreateInstruction(&Osd, Instruction, Gcindex,ObjType, ObjSize,	XStart, YStart, XEnd, YEnd,TextIndex, ColorIndex);
	XOSD_LoadInstructionList(&Osd, Gcindex, 0, Instruction, 1);
	return;
}

實現結果

前面只給出了兩個圖層,工程退不回去了,目前工程實現的是疊加8個圖層(1個視頻,7個內部圖像控制器)。

參考鏈接

百度網盤源碼以及參考文檔鏈接如下

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

※回頭車貨運收費標準

Python 圖像處理 OpenCV (6):圖像的閾值處理

前文傳送門:

「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)
  • retval: 閾值
  • dst: 處理后的圖像
  • src: 原圖像
  • thresh: 閾值
  • maxval: 最大值
  • type: 處理類型

常用的 5 中處理類型如下:

  • cv.THRESH_BINARY: 二值處理
  • cv.THRESH_BINARY_INV: 反二值處理
  • cv.THRESH_TRUNC: 截斷閾值化
  • cv.THRESH_TOZERO: 閾值化為 0
  • cv.THRESH_TOZERO_INV: 反閾值化為 0

接下來這幾種處理類型有啥不同,我們一個一個來看。

二值處理

這種二值處理方式最開始需要選定一個閾值 T ,從 0 ~ 255 之間,我這裏選擇出於中間的那個數 127 。

接下來的處理規則就是這樣的:

  • 大於等於 127 的像素點的灰度值設定為最大值,也就是 255 白色
  • 小於 127 的像素點的灰度值設定為 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_BINARY)

# 显示圖像
cv.imshow("src", src)
cv.imshow("result", b)

# 等待显示
cv.waitKey(0)
cv.destroyAllWindows()

反二值處理

這種方式和上面的二值處理非常相似,只是把處理規則給反了一下:

  • 大於等於 127 的像素點的灰度值設定為 0 ,也就是白色
  • 小於 127 的像素點的灰度值設定為最大值,也就是 255 白色

完整代碼如下:

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()

這種方式實際上是把圖片比較亮的像素處理成為閾值,其他部分保持不變。

閾值化為 0

這種方式還是需要先選定一個閾值 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

這個和前面的反二值圖像很像,同樣是反閾值化為 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 一下)

為什麼我很少提問題了呢?

  • 我碰到了一個問題;
  • 我自己研究了一會,沒找到答案;
  • 我決定還是骨骼吧;
  • 我把關鍵詞扔在搜索框里,答案好像還沒找到;
  • 我看了很多帖子,好像答案完全不着邊;
  • 我把問題重新梳理了一遍,準備找技術比我還好的朋友提問;
  • 10 分鐘過去后,我寫下了一百個字,感覺問題已經描述得很清楚了,在好友列表裡準備找朋友了;
  • 突然,我發現好像我搜索的關鍵詞不太對,於是我就換了一個,又換了一個,抱着試一試的心態,結果真的找到了想要的答案。

不知道你有沒有遇到這樣的情況?反正我真的是經歷了無數次。有時候,答案找不到,我都着急得想捶爆鍵盤了,甚至狠狠地在桌子上捶了一拳,疼痛的感覺讓我冷靜下來。

於是我想到了那隻橡皮鴨,我在心裏對它說:“為什麼這台服務器上的首頁打開時間需要一分多鍾,而另外一台服務器上只需要不到三秒鐘?”

“那你有沒有對比兩台服務器有什麼不同嗎?”橡皮鴨開口回答道。

“比對了呀,完全一樣啊,代碼一樣,只是說參數不一樣,war 包也一樣,都用的 Tomcat 啊。”我憤憤不平地說。

“操作系統一樣嗎?”橡皮鴨耐心地開導着我。

“不太一樣,一台是 Windows Server,一台是 CentOS。”我回答道。

“那你有沒有想過把 Windows Server 那台也換成是 CentOS?”橡皮鴨依然很冷靜。

“不會吧,就因為操作系統不同,首頁打開速度差別就會這麼大?”

“你試試唄,把環境備份下,反正現在切換一下操作系統又不麻煩。”

“好的,聽你的,橡皮鴨。”

一個小時后,神奇的事情發生了,原來問題真的是因為 Windows Server 啊!果然不如 Linux 靠譜啊!

以上就是三年前我真實經歷過的,當時有一台遺留的 Windows Server,我就沒想着把操作系統換成是 Linux 的,然後同樣的 war 包放上去后,首頁打開速度超慢,我嘗試過對首頁加上緩存,減少 JavaScript、CSS 文件的大小,對它們進行壓縮,甚至減少了從後台獲取數據的量,但所有的嘗試都於事無補。

最後,真的是橡皮鴨幫助到了我。當你把自己完全投入到一種假想當中,去問一個透徹而詳盡的問題時,答案真的會悄無聲息地找上門來。

大多數情況下,並不是因為我們的知識庫儲備不足,而是我們尋找問題的方向發生了偏離,一旦我們意識到了這種錯誤,問題也就不再是問題。

當然了,當你嘗盡一切辦法,仍然找不到答案的話,找大牛、找朋友、找同事、找社群,不要臉地提問吧,別怕自己受到打擊,別怕問題無人解答,你要做的是,把問題梳理得越來越清晰,也許答案就在你那裡。

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。回復關鍵字「簡歷」更有一份技術大佬整理的優質簡歷模板,助你一臂之力。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

一篇文章快速搞懂 Atomic(原子整數/CAS/ABA/原子引用/原子數組/LongAdder)

前言

相信大部分開發人員,或多或少都看過或寫過併發編程的代碼。併發關鍵字除了Synchronized,還有另一大分支Atomic。如果大家沒聽過沒用過先看基礎篇,如果聽過用過,請滑至底部看進階篇,深入源碼分析。

提出問題:int線程安全嗎?

看過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基礎篇分界線

原子整數(基礎類型)

整體介紹

Atomic是jdk提供的一系列包的總稱,這個大家族包括原子整數(AtomicInteger,AtomicLong,AtomicBoolean),原子引用(AtomicReference,AtomicStampedReference,AtomicMarkableReference),原子數組(AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray),更新器(AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater)。

AtomicInteger

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());
    }
} 

 

 

執行結果:

對上述int類型的例子改進

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;
}

 

CAS

線程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。

安全實現-使用CAS

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());

    }
}

 

 

運行結果如下:

ABA問題及解決

在上面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

 

字段更新器和原子累加器比較簡單,這裏就不說了。 

Atomic進階篇分界線

LongAdder源碼分析

LongAdder使用

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是正確的兩萬。

與Atomic的對比優勢

那問題來了,既然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方法,主要是初始化cellscells的擴容多個線程同時命中一個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,成為併發多面手

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

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

10.99萬起 神車朗逸 軒逸都忌憚的車型哪款車型最值得買?

精英型和豪華型相差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/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

奇駿 途觀都被逼至絕路!這新款SUV顏值操控同級無敵?

內飾上也是進行了重新設計,用料也更為厚道,相當於原來的中庸內飾設計,現款設計無疑更為時尚。加入懸浮式中控屏幕以及改變了空調出風口的形狀。儀錶盤依然是三錶盤式設計,中規中矩,若是可以能把轉速表放在中央相信能贏得更多馬粉的喜歡。

前言

馬自達的車型對於很多消費者來說就像是夢中情人的存在,非常喜歡但可能因為價格、實用性等問題最後卻是選擇其他的車型。而在今年剛剛開幕的洛杉磯車展上,馬自達給我們帶來了最新的CX-5,那麼它有着什麼樣的改變?是否能讓人突破理性果斷將其開回家?

2017款CX-5使用的依然是馬自達近年來力推的魂動設計,所以它看起來還是給到我們比較熟悉的感覺,但是相對於現款的設計,無疑是更為激進動感,在顏值方面再次領跑整個緊湊型SUV。

大燈方面變得更為凌厲,而且尺寸也相對小了。進氣格柵面積更大,並且從原本有着的鍍鉻裝飾變成了現在的黑色網狀格柵,更像是一輛性能車,一改現款的儒雅氣質,突顯了自己的凌厲氣質。

大燈光源上始終使用的是氙氣大燈,不過LED日間行車燈效果佳,造型漂亮。

得益短后懸的設計,2017款CX-5看起來更為緊湊。除此以外,馬自達還增大了A柱的角度帶來了更佳的視野,降低了重心以及更寬的輪距以求有着更為優秀的操控表現,所以最佳操控緊湊型SUV的地位CX-5是再次坐實了。

大尺寸輪轂配合短前後懸的設計使得新CX-5車身更為緊湊,車頭前吻是整車中最為性感的部分。

家族化設計使得CX-5在尾部沒有太多的新意,但總歸是更為動感,上方的擾流板相當個性。

內飾上也是進行了重新設計,用料也更為厚道,相當於原來的中庸內飾設計,現款設計無疑更為時尚。加入懸浮式中控屏幕以及改變了空調出風口的形狀。

儀錶盤依然是三錶盤式設計,中規中矩,若是可以能把轉速表放在中央相信能贏得更多馬粉的喜歡。

在空間表現上將於現款持平,所以空間和競爭對手相比還是處於劣勢,不過加入了傾角可調節式的後排座椅,後排還是可以坐得比較舒適的。

寬大的座椅是佔據空間的大戶,但是這樣乘坐舒適度,尤其是長途時就有着很大的保證。

動力方面將維持現款的2.0L以及2.5L創馳藍天發動機,配搭的是6AT變速箱,油耗表現優秀。

價格方面相信將維持現款的17-25萬區間,據悉2017款將在下一年引進國產,這款全新的CX-5在外觀內飾上以及操控上有了一定的提升 ,相對於其他車企的改款來說這相當的厚道,不過在國人重視的空間方面馬自達始終沒能交出一份滿意的結果。對此,你願意為這“夢中情人”埋單嗎?還是覺得“可遠觀而不可褻玩焉”?本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

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

16萬起就能買B級車 這“三寶”又拉風又能裝逼!

6T渦輪增壓發動機,最大功率132。4千瓦,最大扭矩265牛米,傳動系統方面只有1。6T車型是搭載7擋雙離合變速箱,2。0L和2。4L都是配備傳統的6擋手自一體,在動力的輸出上,1。6T車型前段的加速比2。0L、2。4L車型來得會更加直接,而在中後段就有種動力衰弱的感覺,在平順性方面,2。

現在年輕的消費者,在購車方面更多是注重外觀漂不漂亮,在空間上並不會像前輩們這麼注重,而更多的是強調自己個性潮流的一面,所以買車首選就是外觀夠不夠帥,今天編者就給大家推薦年輕人最愛的“三寶”

東風悅達起亞-起亞K5

指導價:15.98-24.88萬

新款K5在外觀上並沒有開刀闊斧的感覺,但在細節上做了提升,讓整車的細節、線條更為的精緻。“滿天星”的中網有一種奢華的格調,在視覺效果上非常的吸睛,在尾部方面並沒有做太多的改進,還是溜背式轎跑的設計,排氣依舊還是保留了雙邊共兩出的設計,更強調時尚、運動感。

在內飾設計上,K5更為簡潔,整个中控微微向駕駛員傾斜,能讓駕駛員在操作上更為簡單。按鍵的布局也十分平整,在做工用料上也在同級別中已經是中上的水準,整車的氛圍比上一代更加的簡約時尚。在空間方面,它也達到了B級車應有的水準,無論在腿部頭部空間都還有很大的余量,座椅的柔軟度恰到好處,在乘坐上的感受非常舒適。

K5提供2.0L、1.6T和2.0T、2.0L混動四款發動機可選,其中2.0L自然吸氣發動機配備的傳動系統為6擋手自一體在動力上更為平順,並不會有給你帶來很強的動力,適合斯斯文文的有為青年,而1.6T的渦輪增壓發動機,傳動系統配備的是7擋雙離合,在平順性不及2.0L發動機,但在動力上的響應性和油門的靈敏度會強上不少。2.0T渦輪增壓發動機在動力上的爆發更為明顯,最大功率180千瓦,最大扭矩350牛米,傳動系統配備的傳統的6擋手自一體,雖然6擋手自一體在響應性不會像雙離合那麼激進,但是因為發動機的動力性能夠足,駕駛快感也會更加直接。

北京現代-索納塔九

指導價:17.48-24.98萬

索納塔在外觀上的視覺衝擊更強,大嘴式的中網進氣格柵,有那麼有點點像奧迪的感覺,線條更為流暢、平緩,典型的韓系風格,整體外觀造型更加年輕、時尚。

在內飾上並沒有太多的花俏,而更多的是傳遞一種簡約時尚風,中控的設計十分大方得體,六邊形的銀色飾板,空調出風口有一塊長條的防碳纖維飾板點綴,營造出非常前衛的氣氛。在乘坐的空間上,完全達到了B級車應有的水準。索九有全景天窗、多功能方向盤、無鑰匙進入/啟動等豐富配置。

索納塔九提供了1.6T、2.0L和2.4L三款發動機,1.6T渦輪增壓發動機,最大功率132.4千瓦,最大扭矩265牛米,傳動系統方面只有1.6T車型是搭載7擋雙離合變速箱,2.0L和2.4L都是配備傳統的6擋手自一體,在動力的輸出上,1.6T車型前段的加速比2.0L、2.4L車型來得會更加直接,而在中後段就有種動力衰弱的感覺,在平順性方面,2.0L和2.4L車型更有優勢。

上汽通用雪佛蘭-邁銳寶

指導價:16.49-19.99萬

邁銳寶的外觀設計比上一代車型更加的精緻、車身的線條更加的飽滿,發動機微微隆起的線條,濃濃的美系的風格。前臉的設計很有層次感,大嘴式的進氣格柵,增強車頭的時尚感,整車的視覺感受,簡約但不簡單。

內飾上採用了“翼展式”中控設計,並且在一些部位用鍍鉻裝飾進行點綴,突出一定的精緻感,在按鍵的整體布局還是簡約大方,簡單而又有運動氛圍。在空間上並不是他的優勢,但在乘坐的舒適度非常高,因為底盤非常紮實,靜音也非常出眾。

邁銳寶提供了1.5T、1.6T和2.0L、2.4L四款發動機,傳動系統方面全系都配備6擋手自一體變速箱,其中1.5T車型最大功率125千瓦,最大扭矩252牛米,雖然排量小,但在動力的表現上比2.0L自然吸氣發動機更為突出。如更加追求動力的表現,1.6T、2.4L車型在輸出上更為優異,而行駛質感也非常高,底盤的調校非常紮實、從容。

編者點評:

面對競爭火熱的B級車市場,K5索納塔邁銳寶個性的風格,迎來不少年輕消費者的追捧,在外觀上和同級別的車型相比,不會顯得太平庸,而是更加的時尚動感,雖然都迎來了更新換代,但它們依然火爆。走在潮流尖端的年輕消費者來說,更加註重時尚,車的樣子要足夠的漂亮,這也是最基本的要求。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

※回頭車貨運收費標準

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

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