10萬買啥車?這5大終極問答能解決99.1%的選車難題!

還有划不划算。其實這裏還需考慮兩個問題:一、貨幣的貶值二、投資的收益第一點很簡單就是你今年花10萬元買的車這10萬元三年後可能就不值10萬元了又比如你貸10萬元買車這10萬元三年後可能也不值10萬元了可是這個必須考慮到貨幣的貶值率而按現在趨勢來說,是一直在貶值的第二點也舉個例子就是如果你有20萬元預算購車可是你只給10萬,貸款10萬再把剩下的10萬去投資(炒個房地產、買個理財產品什麼的)而當你所投資后得到的收益對於你10萬貸款所需的利息那這個貸款就很划算了可是具體購車貸款利率多少。

人生第一次買車

都有哪些情景?

情景一:

打開微信,找個比較懂車的朋友,上來就問

情景二:

找個汽車App或小程序,勾選條件篩出自己的意向車型

情景三:

先逛幾家4S店,看看哪個銷售妹子腿更長?

好吧,其實情況有很多種,可是最有效的方法應該是:打開微信→點擊公眾號→添加公眾號→搜索“車買買”→點擊關注,一系列購車選車用車評車信息任你看(這裏希望老闆可以看到,嘻嘻嘻),好吧,廢話說多了,直接上乾貨。

如果有足夠預算,就選擇進口車吧

要多大有多大、要多長有多長

要多快有多快、要多帥有多帥

然而理想和現實還是有很大差距的

畢竟不是每個人都叫思聰

而且還有一個姓王的爸爸

更多人還是得用自己辛苦賺來的錢

買一輛物有所值或物超所值的車

選擇自主或合資比起進口,性價比會更高一點吧

每次聊到自主和合資品牌

評論區似乎都兩極分化

要不就是以愛國的名義支持國產

要不就是要求更高的品質支持合資

在此虎哥就不參与任何政治言論了

(希望大家能理解)

不過只針對產品本身

虎哥的看法是:都可以買,喜歡就好

由於現在國人的消費水平越來越高

就算買到不好或者不合適自己的車

用一段時間賣掉好像也不是很虧

誰都說不準哪輛車的質量一定就好或壞

國產車也有五菱宏光這樣的神車

合資品牌也有很多斷軸燒機油的案例

你可以根據自己的見解去選

沒有見解,那自主跟合資可以一起考慮

相信每個男人都有個跑車夢吧?

可是跑車對於在座大部分人來說都不現實吧

我認為絕大部分跑車都只是男人們的大玩具

對於有錢又帥又單身的你,買來耍耍還是可以的

當然,今天虎哥並不是推薦你去買跑車上下班

而是告訴你除了轎車和SUV還有很多選擇

比如近期越來越火的MpV車型

裝得比轎車多,用起來比SUV更舒適更方便

比如越野能力和裝載能力都很強的皮卡

關於買轎車還是SUV的問答在網上實在太多

其實對於城市用車來說無非也就那幾個區別

轎車:操控好、加速快、更省油等等

SUV:坐姿高、視野好、裝得多等等

如果當你了解兩者的區別後還不會選

那你就很有必要去看看其它車型了

二手車性價比高是毋庸置疑的

不過二手車最大的問題就是“水太深”

對於不懂行的人來說沒有必要去嘗試

可是如果你有足夠的積累和經驗

肯定能用很低的價錢買到二手好車

(而且不用給中間商賺差價哦)

所以人生第一次買車

更多的是建議買新車

新車不僅可以給你更多的保障

而且還能給你一種超爽的新鮮感

就跟你小時候收到新玩具的感覺一樣

貸款不是都要收利息嗎?還有划不划算?

其實這裏還需考慮兩個問題:

一、貨幣的貶值

二、投資的收益

第一點很簡單

就是你今年花10萬元買的車

這10萬元三年後可能就不值10萬元了

又比如你貸10萬元買車

這10萬元三年後可能也不值10萬元了

可是這個必須考慮到貨幣的貶值率

而按現在趨勢來說,是一直在貶值的

第二點也舉個例子

就是如果你有20萬元預算購車

可是你只給10萬,貸款10萬

再把剩下的10萬去投資

(炒個房地產、買個理財產品什麼的)

而當你所投資后得到的收益

對於你10萬貸款所需的利息

那這個貸款就很划算了

可是具體購車貸款利率多少?

不同主機廠或經銷商所提供的政策不同

具體還需你親自去計算過才知道

而且那些說0%利率的優本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

8款老闆愛的車最低只要4萬

還有就是形象與同級比起來要更顯莊重,即使去談大買賣,也不至於讓人瞧不起。其實不僅是別克GL8,還有許多定位為商務MpV的車型同樣也充滿了濃郁的商務氣息,而這次推薦GL8主要原因還是因為它空間及乘坐舒適性達到同級一流水平,應有的电子設備都有,相對而言,GL8是現在市面上主流MpV中比較卓越的一款車型。

關於#老闆開什麼車#這樣的話題

相信不少人都有關注過吧

雖然老闆的座駕並沒有什麼標準

可是總有幾款讓人印象深刻

為什麼老闆們都這麼喜歡這些車?

老闆們喜歡的這些車是否適合你?

開上這些車后你是否就能當老闆?

看完這8款老闆車你就會有答案了

首先要排除一個誤區,誰說老闆就一定要開個大奔馳或大賓利的?開五菱宏光的小賣鋪老闆不也是老闆嗎?什麼空間大、油耗低、動力足、操控好、保養便宜、可靠性高等等優點就不多說了,反正銷量說明一切嘛。不過買最便宜的車裝最多的貨,這些簡單的商業意識還是要具備的。

其實能“裝”的確是五菱宏光的核心價值吧,裝人:最多可放置8個座椅,什麼7座SUV都是辣雞;裝貨:只要裝得下,世界就是你的;裝X:方式太多,請自行上論壇選,總有一款適合你。

可能這隻是我的個人印象吧,從小到大,身邊總有一些開着捷達,成天到處跑業務的創業老闆,他們總是會稱讚自己的捷達很省心,很省油,又好開,說這樣的一台車可以完全滿足他們的代步需求,而且外觀低調,卻很有商務范。

其實說到捷達的外觀,雖然現款的捷達也很低調,也很有商務范,同樣也是個“老實”形象,還是以前方頭方腦的造型給我的印象更深刻,可是那個造型放在今天馬路上,也許展現出更多的是“經典”或“復古”吧。

為什麼說開帕薩特的老闆最神秘?首先,一般人根本分不清帕薩特和輝騰,還有就是的確有許多隱形富豪為了低調會選擇帕薩特這款車,首先大眾標就夠低調了,再來就是外觀造型,家族式設計遍布全車身,讓人怎麼看都覺得是一輛不超過20萬的車。

可是20萬要買低調的車選擇很多,為什麼這些隱形富豪要選帕薩特?其實還是圍繞着舒適性吧,德系紮實底盤質感,大空間;還有就是形象與同級比起來要更顯莊重,即使去談大買賣,也不至於讓人瞧不起。

其實不僅是別克GL8,還有許多定位為商務MpV的車型同樣也充滿了濃郁的商務氣息,而這次推薦GL8主要原因還是因為它空間及乘坐舒適性達到同級一流水平,應有的电子設備都有,相對而言,GL8是現在市面上主流MpV中比較卓越的一款車型。

當然,這樣的MpV一般都是公務用車,老闆一般只坐在第二排,坐在舒適的座椅上,有公事要聊的時候,跟助理溝通起來十分方便;沒事聊的時候,放下靠背,也可以睡得很舒服。

作為豐田越野神車蘭德酷路澤的分支車型,普拉多其實已經更傾向於都市SUV,可是在造型方面還是很有越野的味道,這也是許多老闆們喜歡它原因之一,不過實際普拉多還是延續了較好的越野性能。

很多人都說什麼樣的人就會開什麼樣的車,我通過普拉多的車主們就能明顯體現出這點,說不上個個都高大威猛,可是至少也是條硬漢。

雖然說“虎頭奔之後再也沒有S級”,有情懷的車迷都喜歡那個年代的S級,可是現在的S級無疑是更好的S級,動力更好,配置更高,價格更低。雖然造型方面的確是向現在的消費市場妥協了,可是現在的S級仍然是D級車裡最有老闆范的。

但是實際上並不是所有買S級的老闆都會請司機,或者說不是所有買了S級的老闆自己都不開,即使自己開S級的時候的確有點像司機,可是他們還是很願意去駕駛這輛高級車。

與其說開霸道(普拉多)的老闆真霸氣,還不如說開攬勝的人才是真正的“霸道總裁”,說到高大上,縱觀市面上所有高端中大型SUV,好像真沒幾款能媲美路虎攬勝的了。也有人說路虎攬勝根本沒有直接的競爭對手,因為在公路和越野的性能都如此卓越,內飾如此豪華,外觀如此霸氣的SUV屈指可數。

雖然說攬勝也有極高的越野性能,可是車主們更願意把它看作是豪華都市SUV,而且攬勝的形象也似乎更合適出現在都市中。

看到勞斯萊斯,貌似我們的話題就要結束了,我也都相信大家和我一樣,勞斯萊斯才是代表最“尊貴”的老闆車,雖然到了這個等級,選擇還有賓利和邁巴赫,可是這些品牌都不如勞斯萊斯尊貴。

其實到了這個級別的車,更講究的應該就奢華、藝術、品味,多的不說了。

不知道大家看了這麼多老闆車之後,有沒有你心儀的那輛呢?或者在你的印象里,什麼車才算老闆車呢?歡迎把你的所有見解評論到下方留意區哦。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

大廠面試過程復盤(微信/阿里/頭條均拿offer,附答案篇)

背景

本人前端,3年經驗,由於個人的原因,決定跳槽,於是大概3月開始找工作,總歷時大概2個月,面試了微信/阿里/頭條,三家都拿到了offer,來分享一下面經。

問題比較多,而且很多面試題都是跟個人項目相關的,項目相關的問題借鑒意義不大,所以這裏總結一些與項目無絕對關係的問題,歡迎圍觀~

本文答案均以鏈接的形式給出,因為我本人的一個特點就是回答題目一定要夠詳細,而且大部分的題目我都寫過文章或者公眾號轉發過文章了,感興趣可以直接關注公眾號前端Q,希望對你有幫助~

整體流程

這裏,我梳理了一些自己的心得,方便自己後面做直播,這裏就當預告一下,先放個思維導圖。

PS: 圖片有點大,如果打不開,直接訪問我的博客

微信

筆試

  1. 數組交集,編寫一個函數,輸入兩個數組,輸出它們的交集。輸出數組中不含重複的元素,元素排列順序可隨意。

原題地址:https://leetcode-cn.com/problems/intersection-of-two-arrays-ii/

  1. 二叉樹的搜索,輸入一個普通二叉樹的根節點,實現一個調度器,調用調度器的next()方法,將返回二叉樹中下一個最小的數;調用迭代器的hasNext()方法,將返回是否存在下一個數。二叉樹節點是整數,無序。

原題地址:https://leetcode-cn.com/problems/binary-search-tree-iterator/

  1. 三角形個數,輸入一個非負整數的數組,如果將數組元素選作三角形的邊長,編寫一個函數,輸出這個數組可構成的三角形數量。

原題地址:https://leetcode-cn.com/problems/valid-triangle-number/

  1. 數組切分問題,輸入一個正序排列的整型數組,如果它可以被切分為1個或多個子序列,輸出True,反之False。子序列需為連續的整型數組,並且長度至少為3。
例1:
輸入: [1,2,3,3,4,5]
輸出: True
解釋:可以切分為2個各自連續的子序列:
1, 2, 3
3, 4, 5
例2:
輸入: [1,2,3,3,4,4,5,5]
輸出: True
解釋:可以切分為2個各自連續的子序列:
1, 2, 3, 4, 5
3, 4, 5
例3:
輸入: [1,2,3,4,4,5]
輸出: False
解釋:無法切分出長度至少為3的子序列。

原題地址:https://leetcode-cn.com/problems/split-array-into-consecutive-subsequences/

  1. 一隻青蛙一次可以跳上1級台階,也可以跳上2級。求該青蛙跳上一個n級的台階總共有多少種跳法.
  2. 求下面代碼的輸出
function test(a,b) {
  console.log(b)
  return {
    test:function(c){
      return test(c,a);
    }
  };
}

var retA = test(0);  
retA.test(2);  
retA.test(4);  
retA.test(8);
var retB = test(0).test(2).test(4).test(8);
var retC = test('good').test('bad');  
retC.test('good');  
retC.test('bad');
  1. top k

原題地址:https://leetcode-cn.com/problems/kth-largest-element-in-an-array/

  1. 閉包問題及優化
for (var i = 0; i < 4; i++) {
  setTimeout(function() {
    console.log(i);
  }, 300);
}

PS:一面筆試要求是30分鐘,前4道算法題,二面筆試題是15分鐘

與項目關係不大的問答題

  1. 節流防抖(實現/場景/源碼,如lodash實現原理)(https://www.cnblogs.com/LuckyWinty/p/5949970.html#4564734)
  2. 事件循環(瀏覽器/node/版本差異)(https://mp.weixin.qq.com/s/QgfE5Km1xiEkQqADMLmj-Q)
  3. setTimeout 實現原理(https://mp.weixin.qq.com/s/7qTRSMqaqG8XZ9rpEBhYNQ)
  4. react 和 vue 的區別
  5. Promise 原理(https://juejin.im/post/5d6f7c83e51d4561c541a712)
  6. 前端錯誤監控及容災(https://mp.weixin.qq.com/s/prf-mXexBh1Ie-ctq9FnzA)
  7. 性能優化(https://mp.weixin.qq.com/s/qXFd3f7CkEzz4u_1zl-TSw、https://mp.weixin.qq.com/s/dy1u2g9TeCoq2WOdPLqAXw、https://mp.weixin.qq.com/s/DJ8Fdq1_cIoW0_NYekZwFw、https://mp.weixin.qq.com/s/azeUIx0EA86EFQrtIRUKwQ、https://mp.weixin.qq.com/s/wDKKj5R8SYm-_75Zn1y30A、https://mp.weixin.qq.com/s/BOdnCYbMQfmCpUC3w3KZqw、https://mp.weixin.qq.com/s/FdG7uVIDXltNyskL3qh8Cw、太多了,貼不過來了,自己再總結吧)
  8. 談談 node 的內存泄漏(我的總結暫時沒時間寫,可以看看這篇,https://zhuanlan.zhihu.com/p/25736931)
  9. 開發過程中遇到的最大挑戰是什麼(梳理項目,得出難點)
  10. 學習的動力怎麼來的,如何維持(有興趣可以留意一下我的直播)
  11. 瀏覽器的渲染機制是怎樣的(https://mp.weixin.qq.com/s/wcw30EUb2ds3AoW176WGeg)
  12. SSR 作用及優缺點(seo/首屏快/開發成本高等,我項目用的是vue ssr,沒專門總結,可以先看這個,https://zhuanlan.zhihu.com/p/35871344)
  13. 如何進行狀態管理(vuex/redux)
  14. webpack 及瀏覽器的技術分享目的是什麼,分享了什麼,怎麼做的分享(https://mp.weixin.qq.com/s/1FySzmVrNjS6wjgqALC96g、https://mp.weixin.qq.com/s/wcw30EUb2ds3AoW176WGeg)
  15. 如何進行項目重構(知乎很多討論,直接去參考就可以了,或者看看這個https://www.itzhai.com/refactoring/refactoring-principle.html)
  16. 進程與線程的區別(https://www.zhihu.com/question/25532384)
  17. 說說知道的設計模式(參考掘金小冊子就可以了)
  18. CI/CD(https://mp.weixin.qq.com/s/MbeW8UNZ1fPekWcaNqmsCQ)

PS:由於面試全過程歷時比較長,一些題目已經忘記了。大概記得這麼點,然後很看項目,每一輪都詳細問了項目~

可能感興趣的問答

  1. 面試一共幾輪

我本人一共 4 輪技術面 + 1 輪HR面。目前微信職級為9級以下的技術崗不用經過面委,9級以下的一般4輪技術面,內部轉崗可3輪技術面,9級及以上的需要走面委面試,大概形式是做份ppt去講自己的項目經歷及其他技術項。9級及以上的就5-10輪技術面不等。

  1. 歷時多長

我本人總歷時一個多月(相對來說算很快了),所以如果你要面騰訊,千萬不要裸辭,不對,面什麼都不要裸辭,這年頭不好混,慫一點。

  1. 整體面試流程是怎樣的

內推->一面->二面->三面->四面->HR面->口頭offer->正式offer->背調->入職

每個階段耗時一到兩周的時間

  1. 微信工作地點

目前微信總部在廣州,大部分的組都在廣州,其中微信支付在深圳。

  1. 微信考算法多嗎

多,基本上就是算法+項目

阿里

筆試

  1. n階樓梯問題(尾遞歸的優化[函數最後調用自身可以減少中間變量|化遞歸為循環去優化]/備忘錄優化)
  2. 節流防抖的實現(分析筆試答案時問到,總結差別,說出應用場景)(https://www.cnblogs.com/LuckyWinty/p/5949970.html#4564734)
  3. 深拷貝(類型考慮/Symbol/Date等的構造/循環優化/共用引用優化等),這裏貼2篇文章給大家參考,https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1,https://mp.weixin.qq.com/s/M7KBX3w2KqlWhZFHJSYP6Q)
  4. 封裝一個只能輸入数字的React組件

問答題

  1. 輸入URL后發生了什麼(https://mp.weixin.qq.com/s/DLq_GIkdnuOayThfi3jI0A)
  2. 事件循環說一下(https://mp.weixin.qq.com/s/QgfE5Km1xiEkQqADMLmj-Q)
  3. JS有幾種基本類型,分別是什麼,與對象的區別(不貼鏈接了,我認為是前端的都應該知道)
  4. JS 執行過程中是如何保存上下文的(堆和棧,可以看極客時間的《瀏覽器工作原理》,或者參考我這裏,https://mp.weixin.qq.com/s/klZ6j3Gj9cwHGQFys6Hdjg、https://mp.weixin.qq.com/s/HqADCzMGlIFDNnMRLsPTAA)
  5. 你認為什麼是前端
  6. 為什麼想來阿里
  7. 最近在看什麼書
  8. 微前端了解嗎(https://mp.weixin.qq.com/s/awBqvJmqWc7JGxwgB0QQZA、https://mp.weixin.qq.com/s/80S-RFmoZoq8KfCH6YZ_Tg、https://mp.weixin.qq.com/s/HVwcY8JGyrK8VcN1Ic9sWQ)
  9. 你想成為什麼樣的人(個人成長規劃)
  10. 項目中遇到的最大挑戰是什麼,如何解決的
  11. node 大量日誌怎麼處理的(緩衝隊列)
  12. http2 的特點(https://mp.weixin.qq.com/s/OitrWEosrpuXh19o_TDasg)
  13. node 與其他語言有什麼區別,其優劣勢是什麼
  14. csrf 的核心原理(https://juejin.im/post/5bc009996fb9a05d0a055192)
  15. node 事件循環(https://mp.weixin.qq.com/s/QgfE5Km1xiEkQqADMLmj-Q)
  16. FCP/FMP/FP 分別是怎樣定義,如何統計(https://mp.weixin.qq.com/s/wDKKj5R8SYm-_75Zn1y30A)
  17. MySQL 題目,id 唯一,name 可以重複,求 name 有重複的所有數據
  18. 有沒有推動過什麼東西
  19. 未來的一個發展是否是全棧,如何規劃(可以留意我的直播)
  20. 鏈路錄屏的原理(可以分析rrweb即可,參考例子,https://mp.weixin.qq.com/s/PxEzBrr3YF1jD84c_p1uAA)
  21. 鏈路錄屏法務問題如何解決(屏蔽關鍵信息/用戶授權等)
  22. FMP 定義及統計,w3c的草案你知道嗎(自行谷歌查找)
  23. react 項目的可持續維護性如何體現(自行谷歌查找)
  24. react hooks用過嗎,為什麼要用(沒有專門總結,自行谷歌查找)
  25. h5 和小程序有什麼區別(自行谷歌查找)
  26. https加密原理,中間人攻擊知道嗎(https://mp.weixin.qq.com/s/aMYp6Y5n26r9vdQIom4g0w、https://mp.weixin.qq.com/s/sHtZhRTNOihmxap5sDD6xQ)
  27. ng 負載均衡的方式有哪些(https://mp.weixin.qq.com/s/u-XbBwGxHrhJGiMiiqz26w、https://mp.weixin.qq.com/s/o2Us4Zj6DO2NHGQVaHIa1A)
  28. CDN 有什麼作用(https://github.com/LuckyWinty/fe-weekly-questions/issues/24)
  29. 不可變數據裏面,immutable,刪除數組裡面的列表(https://juejin.im/post/5b9b30a35188255c6418e67c,
    https://juejin.im/post/5e6a14b1f265da572978a1d3)
  30. 垃圾回收中,內部函數返給上層的對象是如何管理的
  31. es 靜態分析原理(https://zhuanlan.zhihu.com/p/33843378)
  32. 有一個很大很大的文件,如何統計文件裏面重複最多的行是哪行(參考git的記錄存儲)

頭條

筆試題

一面:

  1. 執行代碼求輸出,並說明為什麼,嚴格模式下輸出有變化嗎,為什麼
var a = function () {this.b = 3;}
var c = new a();
a.prototype.b = 9;
var b = 7;
a();

console.log(b);
console.log(c.b);
  1. 給定一個升序整型數組[0,1,2,4,5,7,13,15,16],找出其中連續出現的数字區間,輸出為[“0->2″,”4->5″,”7″,”13″,”15->16”]
function summaryRanges(arr){
    //TODO
}
  1. 請實現以下的函數,可以批量請求數據,所有的URL地址在urls參數中,同時可以通過 max 參數控制請求的併發度,當所有請求結束之後,需要執行 callback 回調函數。發請求的函數可以直接使用 fetch 即可
function sendRequest(urls: sring[],max:number,callback:()=>void){
    //TODO
}

二面:

  1. 實現一個字符串反轉:
    輸入:www.toutiao.com.cn
    輸出:cn.com.toutiao.www

要求:
1.不使用字符串處理函數
2.空間複雜度盡可能小

  1. 不藉助變量,交換兩個數。
function swap(a, b) {
    //TODO
}
  1. 觀察者模式與發布訂閱者區別,並寫出其模型

與項目無絕對相關的問答題

  1. vue 事件機制是如何實現的 (https://juejin.im/post/59ca5e975188257a8908959b)
  2. vue 的組件通信方式有哪些
  3. react fiber 的實現原理 (https://juejin.im/post/5dadc6045188255a270a0f85#comment)
  4. vue 響應式數據原理(vue2/vue3/依賴收集/發布訂閱/watcher消息隊列控制/Vue.set實現)(https://mp.weixin.qq.com/s/zDv_IQ36o_rRD25xN9uyuw、https://mp.weixin.qq.com/s/hTygoAan4yH3V4XV9iE1Pw)
  5. vue 轉小程序怎麼實現(ast/生命周期對齊/跨平台模塊兼容/兼容細節點實現過程)
  6. 性能指標,如何理解TTI,如何統計,與FID有什麼區別,如何實現統計,還聊了很多性能的東西(https://mp.weixin.qq.com/s/wDKKj5R8SYm-_75Zn1y30A)
  7. 說說你所了解的安全問題及防護方法(https://mp.weixin.qq.com/s/rU32rVM6Q-ele01ZB3RFzg)
  8. 說說你知道的設計模式,並舉個對應的模式例子(https://mp.weixin.qq.com/s/x3OCgS_knmM4D5zNSIVGRg)
  9. 未來規劃及學習方法
  10. 你覺得你是怎樣拿到京東校招的sp的
  11. 遇到的最大挑戰/過去的最大收穫分別是什麼

我本人的博客/公眾號,似乎面試官都看過,都知道我學過什麼,知道什麼,所以很多我博客上有的東西都沒問,甚至我引申到過去,都不讓說,有種沒機會表現的感覺,哈哈

感受&更多學習資料

其實單看問題,參考性還是比較小的,畢竟面試官主要會根據簡歷去發問。這裏的一個感受就是,項目中遇到的最大挑戰,這個問題真不好答,我當時答得也不夠好,後面又回去總結了一波項目,建議大家都好好總結一下自己平時遇到的問題,尋根究底。

這部分的題目,我會同步到我的每周N題上,歡迎大家來交流答案啊。想刷題的也可以來,地址如下:
https://github.com/LuckyWinty/fe-weekly-questions/issues

有很多知識點,雖然我沒被問到,但是我寫了不少文章,感興趣的話可以關注一下我的博客,地址如下:
https://github.com/LuckyWinty/blog

更多我覺得比較好的博客,地址如下:

  • https://juejin.im/user/5cf288385188254abb110e3b/posts
  • https://juejin.im/user/58d4e734570c350058cbb900/posts
  • http://blog.poetries.top/FE-Interview-Questions/
  • https://juejin.im/user/5bea27965188250edf4ad8b7/posts
  • https://juejin.im/user/5c45ddf06fb9a04a006f5491/posts

交流

  • 歡迎加我微信(winty230),拉你進技術群,長期交流學習…
  • 歡迎關注「前端Q」,認真學前端,做個專業的技術人…

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

【其他文章推薦】

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

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

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

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

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

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

面試:在面試中關於List(ArrayList、LinkedList)集合會怎麼問呢?你該如何回答呢?

前言

在一開始基礎面的時候,很多面試官可能會問List集合一些基礎知識,比如:

  • ArrayList默認大小是多少,是如何擴容的?

  • ArrayList和LinkedList的底層數據結構是什麼?

  • ArrayList和LinkedList的區別?分別用在什麼場景?

  • 為什麼說ArrayList查詢快而增刪慢?

  • Arrays.asList方法后的List可以擴容嗎?

  • modCount在非線程安全集合中的作用?

  • ArrayList和LinkedList的區別、優缺點以及應用場景

 

ArrayList(1.8)

ArrayList是由動態再分配的Object[]數組作為底層結構,可設置null值,是非線程安全的。

ArrayList成員屬性

//默認的空的數組,在構造方法初始化一個空數組的時候使用
private static final Object[] EMPTY_ELEMENTDATA = {};
​
//使用默認size大小的空數組實例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
​
//ArrayList底層存儲數據就是通過數組的形式,ArrayList長度就是數組的長度。
transient Object[] elementData; 
​
//arrayList的大小
private int size;

那麼ArrayList底層數據結構是什麼呢?

很明顯,使用動態再分配的Object[]數組作為ArrayList底層數據結構了,既然是使用數組實現的,那麼數組特點就能說明為什麼ArrayList查詢快而增刪慢?

因為數組是根據下標查詢不需要比較,查詢方式為:首地址+(元素長度*下標),基於這個位置讀取相應的字節數就可以了,所以非常快;但是增刪會帶來元素的移動,增加數據會向後移動,刪除數據會向前移動,導致其效率比較低。

 

ArrayList的構造方法

  • 帶有初始化容量的構造方法

  • 無參構造方法

  • 參數為Collection類型的構造器

//帶有初始化容量的構造方法
public ArrayList(int initialCapacity) {
    //參數大於0,elementData初始化為initialCapacity大小的數組
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    //參數小於0,elementData初始化為空數組
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    //參數小於0,拋出異常
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}
​
//無參構造方法
public ArrayList() {
    //在1.7以後的版本,先構造方法中將elementData初始化為空數組DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    //當調用add方法添加第一個元素的時候,會進行擴容,擴容至大小為DEFAULT_CAPACITY=10
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

那麼ArrayList默認大小是多少?

從無參構造方法中可以看出,一開始默認為一個空的實例elementData為上面的DEFAULTCAPACITY_EMPTY_ELEMENTDATA,當添加第一個元素的時候會進行擴容,擴容大小就是上面的默認容量DEFAULT_CAPACITY為10

ArrayList的Add方法

  • boolean add(E):默認直接在末尾添加元素

  • void add(int,E):在特定位置添加元素,也就是插入元素

  • boolean addAll(Collection<? extends E> c):添加集合

  • boolean addAll(int index, Collection<? extends E> c):在指定位置后添加集合

boolean add(E)
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

通過ensureCapacityInternal方法為確定容量大小方法。在添加元素之前需要確定數組是否能容納下,size是數組中元素個數,添加一個元素size+1。然後再數組末尾添加元素。

其中,ensureCapacityInternal方法包含了ArrayList擴容機制grow方法,當前容量無法容納下數據時1.5倍擴容,進行:

private void ensureCapacityInternal(int minCapacity) {
    //判斷當前的數組是否為默認設置的空數據,是否取出最小容量
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    //包括擴容機制grow方法
    ensureExplicitCapacity(minCapacity);
}
​
private void ensureExplicitCapacity(int minCapacity) {
        //記錄著集合的修改次數,也就每次add或者remove它的值都會加1
        modCount++;
​
        //當前容量容納不下數據時(下標超過時),ArrayList擴容機制:擴容原來的1.5倍
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
​
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //ArrayList擴容機制:擴容原來的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

ArrayList是如何擴容的?

根據當前的容量容納不下新增數據時,ArrayList會調用grow進行擴容:

//相當於int newCapacity = oldCapacity + oldCapacity/2
int newCapacity = oldCapacity + (oldCapacity >> 1);

擴容原來的1.5倍。

void add(int,E)
public void add(int index, E element) {
    //檢查index也就是插入的位置是否合理,是否存在數組越界
    rangeCheckForAdd(index);
    //機制和boolean add(E)方法一樣
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

ArrayList的刪除方法

  • remove(int):通過刪除指定位置上的元素,

  • remove(Object):根據元素進行刪除,

  • clear():將elementData中每個元素都賦值為null,等待垃圾回收將這個給回收掉,

  • removeAll(collection c):批量刪除。

 

remove(int)
public E remove(int index) {
    //檢查下標是否超出數組長度,造成數組越界
    rangeCheck(index);
​
    modCount++;
    E oldValue = elementData(index);
    //算出數組需要移動的元素數量
    int numMoved = size - index - 1;
    if (numMoved > 0)
        //數組數據遷移,這樣會導致刪除數據時,效率會慢
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //將--size上的位置賦值為null,讓gc(垃圾回收機制)更快的回收它。
    elementData[--size] = null; // clear to let GC do its work
    //返回刪除的元素
    return oldValue;
}

為什麼說ArrayList刪除元素效率低?

因為刪除數據需要將數據後面的元素數據遷移到新增位置的後面,這樣導致性能下降很多,效率低。

remove(Object)
public boolean remove(Object o) {
    //如果需要刪除數據為null時,會讓數據重新排序,將null數據遷移到數組尾端
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                //刪除數據,並遷移數據
                fastRemove(index);
                return true;
            }
    } else {
        //循環刪除數組中object對象的值,也需要數據遷移
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

可以看出,arrayList是可以存放null值。

 

LinkedList(1.8)

LinkedList是一個繼承於AbstractSequentialList的雙向鏈表。它也可以被當做堆棧、隊列或雙端隊列進行使用,而且LinkedList也為非線程安全, jdk1.6使用的是一個帶有 header節頭結點的雙向循環鏈表, 頭結點不存儲實際數據 ,在1.6之後,就變更使用兩個節點first、last指向首尾節點。

LinkedList的主要屬性

//鏈表節點的個數 
transient int size = 0; 
//鏈表首節點
 transient Node<E> first; 
//鏈表尾節點
 transient Node<E> last; 
//Node節點內部類定義
private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;
​
        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

一旦變量被transient修飾,變量將不再是對象持久化的一部分,該變量內容在序列化后無法獲得訪問

LinkedList構造方法

無參構造函數, 默認構造方法聲明也不做,first和last節點會被默認初始化為null。

*
/** Constructs an empty list. \*/*
​
public LinkedList() {}
​

LinkedList插入

由於LinkedList由雙向鏈表作為底層數據結構,因此其插入無非由三大種

  • 尾插: add(E e)、addLast(E e)、addAll(Collection<? extends E> c)

  • 頭插: addFirst(E e)

  • 中插: add(int index, E element)

可以從源碼看出,在鏈表首尾添加元素很高效,在中間添加元素比較低效,首先要找到插入位置的節點,在修改前後節點的指針。

 

 

尾插-add(E e)和addLast(E e)
//常用的添加元素方法
public boolean add(E e) {
    //使用尾插法
    linkLast(e);
    return true;
}
​
//在鏈表尾部添加元素
public void addLast(E e) {
        linkLast(e);
    }
​
//在鏈表尾端添加元素
void linkLast(E e) {
        //尾節點
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        //判斷是否是第一個添加的元素
        //如果是將新節點賦值給last
        //如果不是把原首節點的prev設置為新節點
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        //將集合修改次數加1
        modCount++;
    }
​
頭插-addFirst(E e)
public void addFirst(E e) {
    //在鏈表頭插入指定元素
    linkFirst(e);
}
​
private void linkFirst(E e) {
         //獲取頭部元素,首節點
        final Node<E> f = first;
        final Node<E> newNode = new Node<>(null, e, f);
        first = newNode;
        //鏈表頭部為空,(也就是鏈表為空)
        //插入元素為首節點元素
        // 否則就更新原來的頭元素的prev為新元素的地址引用
        if (f == null)
            last = newNode;
        else
            f.prev = newNode;
        //
        size++;
        modCount++;
    }
中插-add(int index, E element)

當index不為首尾的的時候,實際就在鏈表中間插入元素。

 // 作用:在指定位置添加元素
    public void add(int index, E element) {
        // 檢查插入位置的索引的合理性
        checkPositionIndex(index);
​
        if (index == size)
            // 插入的情況是尾部插入的情況:調用linkLast()。
            linkLast(element);
        else
            // 插入的情況是非尾部插入的情況(中間插入):linkBefore
            linkBefore(element, node(index));
    }
​
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
​
    private boolean isPositionIndex(int index) {
        return index >= 0 && index <= size;
    }
​
    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;  // 得到插入位置元素的前繼節點
        final Node<E> newNode = new Node<>(pred, e, succ);  // 創建新節點,其前繼節點是succ的前節點,後接點是succ節點
        succ.prev = newNode;  // 更新插入位置(succ)的前置節點為新節點
        if (pred == null)
            // 如果pred為null,說明該節點插入在頭節點之前,要重置first頭節點 
            first = newNode;
        else
            // 如果pred不為null,那麼直接將pred的後繼指針指向newNode即可
            pred.next = newNode;
        size++;
        modCount++;
    }

LinkedList 刪除

刪除和插入一樣,其實本質也是只有三大種方式,

  • 刪除首節點:removeFirst()

  • 刪除尾節點:removeLast()

  • 刪除中間節點 :remove(Object o)、remove(int index)

在首尾節點刪除很高效,刪除中間元素比較低效要先找到節點位置,再修改前後指針指引。

 

 

刪除中間節點-remove(int index)和remove(Object o)
remove(int index)和remove(Object o)都是使用刪除指定節點的unlink刪除元素

 public boolean remove(Object o) {
     //因為LinkedList允許存在null,所以需要進行null判斷        
     if (o == null) {
         //從首節點開始遍歷
         for (Node<E> x = first; x != null; x = x.next) {
             if (x.item == null) {
                 //調用unlink方法刪除指定節點
                 unlink(x);
                 return true;
             }
         }
     } else {
         for (Node<E> x = first; x != null; x = x.next) {
             if (o.equals(x.item)) {
                 unlink(x);
                 return true;
             }
         }
     }
    return false;
 } 
​
//刪除指定位置的節點,其實和上面的方法差不多
    //通過node方法獲得指定位置的節點,再通過unlink方法刪除
    public E remove(int index) {
        checkElementIndex(index);
       
        return unlink(node(index));
    }
​
 //刪除指定節點
    E unlink(Node<E> x) {
        //獲取x節點的元素,以及它上一個節點,和下一個節點
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        //如果x的上一個節點為null,說明是首節點,將x的下一個節點設置為新的首節點
        //否則將x的上一節點設置為next,將x的上一節點設為null
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        //如果x的下一節點為null,說明是尾節點,將x的上一節點設置新的尾節點
        //否則將x的上一節點設置x的上一節點,將x的下一節點設為null
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
        //將x節點的元素值設為null,等待垃圾收集器收集
        x.item = null;
        //鏈表節點個數減1
        size--;
        //將集合修改次數加1
        modCount++;
        //返回刪除節點的元素值
        return element;
    }
刪除首節點-removeFirst()
//刪除首節點
public E remove() {
        return removeFirst();
    }
 //刪除首節點
 public E removeFirst() {
      final Node<E> f = first;
      //如果首節點為null,說明是空鏈表,拋出異常
      if (f == null)
          throw new NoSuchElementException();
      return unlinkFirst(f);
  }
  //刪除首節點
  private E unlinkFirst(Node<E> f) {
      //首節點的元素值
      final E element = f.item;
      //首節點的下一節點
      final Node<E> next = f.next;
      //將首節點的元素值和下一節點設為null,等待垃圾收集器收集
      f.item = null;
      f.next = null; // help GC
      //將next設置為新的首節點
      first = next;
      //如果next為null,說明說明鏈表中只有一個節點,把last也設為null
      //否則把next的上一節點設為null
      if (next == null)
          last = null;
      else
          next.prev = null;
      //鏈表節點個數減1
      size--;
      //將集合修改次數加1
      modCount++;
      //返回刪除節點的元素值
      return element;
 }
刪除尾節點-removeLast()
  
 //刪除尾節點
    public E removeLast() {
        final Node<E> l = last;
        //如果首節點為null,說明是空鏈表,拋出異常
        if (l == null)
            throw new NoSuchElementException();
        return unlinkLast(l);
    }
    private E unlinkLast(Node<E> l) {
        //尾節點的元素值
        final E element = l.item;
        //尾節點的上一節點
        final Node<E> prev = l.prev;
        //將尾節點的元素值和上一節點設為null,等待垃圾收集器收集
        l.item = null;
        l.prev = null; // help GC
        //將prev設置新的尾節點
        last = prev;
        //如果prev為null,說明說明鏈表中只有一個節點,把first也設為null
        //否則把prev的下一節點設為null
        if (prev == null)
            first = null;
        else
            prev.next = null;
        //鏈表節點個數減1
        size--;
        //將集合修改次數加1
        modCount++;
        //返回刪除節點的元素值
        return element;
    }

其他方法也是類似的,比如查詢方法 LinkedList提供了get、getFirst、getLast等方法獲取節點元素值。

modCount屬性的作用?

modCount屬性代表為結構性修改( 改變list的size大小、以其他方式改變他導致正在進行迭代時出現錯誤的結果)的次數,該屬性被Iterator以及ListIterator的實現類所使用,且很多非線程安全使用modCount屬性。

初始化迭代器時會給這個modCount賦值,如果在遍歷的過程中,一旦發現這個對象的modCount和迭代器存儲的modCount不一樣,Iterator或者ListIterator 將拋出ConcurrentModificationException異常,

這是jdk在面對迭代遍歷的時候為了避免不確定性而採取的 fail-fast(快速失敗)原則:

在線程不安全的集合中,如果使用迭代器的過程中,發現集合被修改,會拋出ConcurrentModificationExceptions錯誤,這就是fail-fast機制。對集合進行結構性修改時,modCount都會增加,在初始化迭代器時,modCount的值會賦給expectedModCount,在迭代的過程中,只要modCount改變了,int expectedModCount = modCount等式就不成立了,迭代器檢測到這一點,就會拋出錯誤:urrentModificationExceptions。

 

總結

ArrayList和LinkedList的區別、優缺點以及應用場景

區別:

  • ArrayList是實現了基於動態數組的數據結構,LinkedList是基於鏈表結構。

  • 對於隨機訪問的get和set方法查詢元素,ArrayList要優於LinkedList,因為LinkedList循環鏈表尋找元素。

  • 對於新增和刪除操作add和remove,LinkedList比較高效,因為ArrayList要移動數據。

優缺點:

  • 對ArrayList和LinkedList而言,在末尾增加一個元素所花的開銷都是固定的。對ArrayList而言,主要是在內部數組中增加一項,指向所添加的元素,偶爾可能會導致對數組重新進行分配;而對LinkedList而言,這個開銷是 統一的,分配一個內部Entry對象。

  • 在ArrayList集合中添加或者刪除一個元素時,當前的列表移動元素後面所有的元素都會被移動。而LinkedList集合中添加或者刪除一個元素的開銷是固定的。

  • LinkedList集合不支持 高效的隨機隨機訪問(RandomAccess),因為可能產生二次項的行為。

  • ArrayList的空間浪費主要體現在在list列表的結尾預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗相當的空間

應用場景:

ArrayList使用在查詢比較多,但是插入和刪除比較少的情況,而LinkedList用在查詢比較少而插入刪除比較多的情況

各位看官還可以嗎?喜歡的話,動動手指點個,點個關注唄!!謝謝支持!

歡迎掃碼關注,原創技術文章第一時間推出

 

 

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

【其他文章推薦】

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

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

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

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

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

11_條件隨機場

  今天是2020年6月14號星期日。從這篇開始,發布時間就正常了。前邊的文章是在寒假寫好的,後來因為趕進度和改小論文,沒有及時整理。3月到6月,兩個半月的時間做了些什麼?真的好怕浪費了時間~《統計學習方法》這本書(第二版),大概在四月底就看個差不多了,半生不熟的好歹是通篇過了一遍,當然不止一遍,除了潛在狄利克雷分配。期間夾雜着手撕代碼的過程,因為腦容量有限的原因,寫代碼的時候,又要把書面內容重新過一遍。就連發布,也要重新過一遍,把重要的地方加上不同顏色…於是這個假期,《統計學習方法》這本書的每個章節,不知道反反覆復的過了多少遍,肯定不超過十遍…也就是看的多,不代表學的多…但是看了總歸是看了,也比不看強…

  CRF條件隨機場~本節說難,挺難;說簡單,也簡單。為什麼我覺得CRF和LR很像呢?可能有很多人在應用場景上反駁我,LR做數據分類問題,CRF是針對序列標註問題。我覺得像,提供一下幾個觀點(遞進着看):

  1.LR和CRF都是判別模型。這句話要從計算方式上來理解:LR是通過線性函數,對特徵向量擬合不同的權值,最後加和的線性函數值作為sigmoid函數的輸入,轉換為概率值(對數幾率)。CRF在公式(11.10)中,不,在更明顯的公式(11.15)中,wkfk(y,x)對1->K加和。個人膚淺的理解:公式(11.15)就是對各個特徵擬合不同的權值wk,加和的線性函數值作為某個“求概率公式”的輸入。

  2.從觀點1出發,思考一下“特徵”。在LR中,“特徵”很明顯,就是特徵向量的各個分量,相當於一個分量一個特徵。LR擬合的w,其實就是看看每個特徵分量對分類結果占什麼樣的貢獻。在CRF中,“特徵”是定義在最大團上的,拋去“最大團”“勢函數”“特徵函數”這些抽象概念,CRF的“特徵”其實不就是定義在相互聯繫的兩個節點上嘛?相當於“成對”構成一個特徵。轉移特徵也好,狀態特徵也罷,線性鏈條件隨機場不就限定了“特徵”要產生在“成對”的關聯關係上。然後,公式(11.15)告訴我,[權值×特徵==》加和],CRF和LR計算線性函數值的方式沒區別啊(區別在特徵的定義上)。

  3.從觀點1出發,思考一下“概率的計算”。不知道大家有沒有仔細思考LR中,第六章公式(6.6)分母位置的1是怎麼計算的,或者為什麼要在分母添加這一項。是不是可以理解為exp(0*wx)=1,這樣能夠把分母作為歸一化項Z(x)看待。分母的1+exp(wx)看作Z(x)=exp(0*wx)+exp(1*wx),P(Y=1|x)=exp(1*wx)/Z(x),另一個類別Y=0的概率P(Y=0|x)=exp(0*wx)/Z(x),怎麼樣,香不香?這裏不就是兩個項的線性函數值進行了指數歸一化。在這個角度上,看CRF的概率計算方式,公式(11.15)和公式(11.16)不就是多了幾項嘛…多個項(K項)進行了歸一化,把數值轉換成了概率值,這這這,和softmax()指數歸一化不就是一回事嘛…然後再考慮考慮為什麼CRF中勢函數是嚴格正的…

  暫時先這三點,實際上都是圍繞[判別模型]提出的。

GitHub:https://github.com/wangzycloud/statistical-learning-method

條件隨機場

引入

  書中對條件隨機場CRF的描述是:給定一組輸入隨機變量條件下另一組輸出隨機變量的條件概率分佈模型,其特點是假設輸出隨機變量構成馬爾可夫隨機場。先拋去“輸出隨機變量構成馬爾可夫隨機場”這個限定條件不看,該模型是條件概率模型,並且是一組隨機變量,在另外一組隨機變量條件下的條件概率模型。

  組這個概念,限定了模型中發生作用的因素要產生在多個隨機變量上。類似這樣,形象不(僅代表個人理解哈~不能保證正確):

  像示意圖中表示的,如果一組隨機變量之間,關係是散亂無序的,我們該怎樣求解呢?各個組上隨機變量的關係都發現不了,何談尋找A組在B組條件下的條件概率分佈?因此,考慮對輸出組的隨機變量施加限定條件。人類最常做的就是發現規律,總結經驗。比如說,讓輸出組的隨機變量兩兩之間產生聯繫,會不會簡化問題?

  於是,模型變成了這樣。又或者,讓輸入組隨機變量和輸出組隨機變量,都變成序列關係。這時,問題變成了由輸入序列對輸出序列預測的判別模型。具體的,讓輸出組隨機變量構成馬爾可夫隨機場。需要的知識包括圖與無向圖、馬爾可夫性、無向圖模型的因子分解、團與最大團…

概率無向圖

  概率無向圖模型,又稱為馬爾可夫隨機場,是一個可以由無向圖表示的聯合概率分佈。“馬爾可夫隨機場”正式出場了,簡單講它是一個聯合概率分佈,是由無向圖表示的。想象無向圖的樣子,由結點和邊構成,且邊沒有方向(無箭頭)。深入一點,一個結點表示一個隨機變量,如果兩個結點之間有邊關聯,就認為這兩個結點具有關聯關係,並且是“相互關聯”(無向邊),相互產生影響。

  可以看到,“有向變無向”這個操作,讓限定條件變的更寬泛了,也就是讓CRF的應用場景增多。當然,無向圖不只是上圖的線性序列關係。根據馬爾可夫性的不同,概率無向圖模型也有很多種類。接下來由圖開始,具體介紹一下模型的定義。

  看一下馬爾可夫性,我的理解是這樣:在無向圖G上給定Cs(與集合C有關的一堆結點),As和Bs條件獨立。As和Bs之間沒有邊關聯,就算利用Cs作為結點把As和Bs連接了起來,兩者仍然保持獨立關係。

  有三種類型,分別是成對馬爾可夫性、局部馬爾可夫性、全局馬爾可夫性,具體定義較晦澀。如下:

  由以上馬爾可夫性,概率無向圖模型定義為:

  有了概率無向圖的定義,實際上我們更關心的是如何求解各個隨機變量的聯合概率分佈。那麼,對給定的概率無向圖模型,我們能不能找到將整體的聯合概率寫成若干個子聯合概率的乘積的方法?也就是將聯合概率進行因子分解(類似:6=2×3,整數6可以分解成因子2乘以因子3),就能方便模型的學習與計算了。事實上,概率無向圖模型最大的特點就是易於因子分解。這裏留幾個疑問,“整體的”變成若干個“子聯合概率的”這句話,子聯合概率我不是可以理解成“子集”的,這個子集要如何劃分?我們知道,圖結構動不動就會像漁網一樣,橫七豎八的全是連接邊。

  另外,聯合概率的因子分解,不僅要能解決無向圖上劃分小子集的問題,還要保證各個小子集乘積的聯合概率要等於整體的聯合概率。

  這裏劃分小子集的問題,由無向圖上的“最大團”解決的,定義如下:

  例子中描述的,應該是挺清楚的,最大團中的任何兩個結點均有邊連接。於是,概率無向圖模型的聯合概率分佈可以表示為其最大團上的隨機變量的函數的乘積,這種操作稱為概率無向圖模型的因子分解。接下來,考慮聯合概率如何計算。

  不管整體的聯合概率也好,子集的聯合概率也好,無非是表示一大撮和一小撮隨機變量的區別。問題在於,這個東西它是概率啊。怎麼在圖結構上體現概率啊…公式(11.15)和公式(11.16)給出了答案,對每個Yi(N個狀態序列中的一個序列Yi)求分值,將各個“得分”歸一化后的結果作為該無向圖Yi的聯合概率。實際上也就是每一個無向圖Yi是所有狀態序列中的一種情況,這個Yi的得分佔所有情況的比例,就認為是Yi的聯合概率。

  這裏Yi的得分,是如何反映的呢?可以從公式(11.15)看出,圖中所有最大團上各個最大團勢函數的乘積,作為Yi的得分。

  串到CRF特徵抓取的過程中,我認為這裏的勢函數,就是抓取最大團集合中的結點特徵,並且是各個結點之間的關聯特徵。由於訓練過程中擬合參數wk,所以這裏只需要將每個特徵作為正的“量”就可以了。勢函數僅作標示“特徵”的度量(metric),擬合的wk決定該特徵對“得分”影響的正負、大小程度(對應開頭的觀點3)。

  現在利用的這種方式,不是空穴來風,是有定理來保證的:

條件隨機場的定義與形式

  條件隨機場是給定隨機變量X條件下,隨機變量Y的馬爾可夫隨機場。我們要用到的CRF指的是定義在線性鏈上的特殊的條件隨機場,稱為線性鏈條件隨機場。一般情況下,在條件概率模型P(Y|X)中,Y是輸出變量,表示標記序列;X是輸入變量,表示觀測序列。這裏,我們也把標記序列稱為狀態序列。在學習時,利用訓練數據通過極大似然估計或正則化的極大似然估計得到條件概率模型;預測時,對於給定的輸入序列x,求出條件概率模型最大的輸出序列y。

  我認為在給定輸入序列X的條件下,計算輸出序列Y的條件概率(且序列Y構成線性鏈條件隨機場)的場景是與圖11.4相符合的。文中描述的,現實中一般假設X和Y有相同的圖結構,形如圖11.5這樣,這就好像對模型進行了簡化,讓模型更容易實現。換一個角度看,圖11.4中的X表達的是輸入序列整體對輸出序列Y的影響,具體到如何產生影響,如圖11.5表達的這樣,輸入序列X的每個分量Xi,與相同時刻的Yi相互影響。

  這裏留兩個疑問,(Xi,Yi)的相互影響關係,會不會傳遞到t時刻?與時刻t有沒有關係?

  看一下線性鏈條件隨機場的具體定義:

  圖示如下,公式(11.9)實際上表達了:馬爾可夫性規定Yi(Y3)只與相鄰的結點有關聯關係(藍框代表等式左側,紅框代表等式右側)。

  根據定理11.1,可以給出線性鏈條件隨機場P(Y|X)的因子分解式,各因子是定義在相鄰兩個結點(最大團)上的勢函數。兩個疑問的解答,相信後面部分的描述中可以找到答案。接下來看一下條件隨機場的三種形式。

1)條件隨機場的參數化形式

  該基本形式,表示給定輸入序列x,對輸出序列y預測的條件概率。實際上現在的具體定義,就是把最大團、勢函數、概率求解等概念落實下來,變成可以量化的東西。公式(11.11)為歸一化項,相當於把Y的每種情況(各種Y序列)考慮到,作為求概率時的分母(每種情況下,各個得分加和)。在某個情況Y下,計算該Y的概率,需要先得到所有勢函數構成的特徵和,也就是“得分分值”。

  這裏的特徵來自兩部分,yi-1與yi橫向上的轉移特徵tk(·)和yi與x豎向上的狀態特徵sl(·)。

  看一下具體定義:

  這裏tk和sl的取值,類似於最大熵模型中特徵函數的定義。我的理解,取1或者0,是標記這個特徵有還是沒有。至於此時該特徵的貢獻是大是小,是正是負,這取決於模型訓練時擬合的參數情況(λ、μ)。

  舉個例子,看下計算時的具體過程:

  該標記序列y=(y1,y2,y3)的非規範化概率,實際上就是通過存在的特徵×對應權重,然後加和。符合[權值×特徵==》加和]這個方式。

2)條件隨機場的簡化形式

  注意到條件隨機場公式(11.10)中同一個特徵在各個位置都有定義,可以對同一個特徵在各個位置求和,從而將局部特徵轉化為一個全局特徵函數。這樣就可以將條件隨機場寫成權值向量和特徵向量的內積形式,也就是現在要描述的簡化形式。實際上,這更進一步貼近了[權值×特徵==》加和]這種方式。

  如上圖所示,假設每個位置i都有這個局部特徵(沒有該特徵的話,特徵值為0),每個i都要針對這一個局部特徵求一個參數,這個工作量似乎有點大,並且重複。那麼,是不是可以把該局部變量上升到全局特徵,每個局部位置特徵值加和,讓這一個特徵在全局上學習一個權重參數。一來減少沒必要的參數估計,二來可以把重點放在“特徵”的增加和估計上。

  這裏公式(11.13)表明了全局特徵是通過各個位置i的局部特徵加和得到的。

  接下來,用向量形式進行表示:

  這樣就得到了條件隨機場的簡化形式。經過知識點細化,再抽象,看CRF公式(11.19)和公式(11.20)和LR中的公式(6.5)和公式(6.6)多像。各個特徵的線性函數值(得分),通過指數歸一化轉化成概率,學習的過程擬合各個參數。

3)條件隨機場的矩陣形式

  先看定義:

  這裏先記一個要點,對每個標記序列引進特殊的起點和終點狀態標記,這時標註序列的概率Pw(y|x)可以通過矩陣形式表示並有效計算。划重點:引進特殊起點和重點標記之後,才可以通過矩陣形式計算。

  不知道怎麼分析這個地方,現在從以下幾個問題開始:

  ①公式(11.21)中Mi的下標i和yi的下標i是否表示同一個意思?

  我認為這兩個下標i指的不是同一個意思,yi的i表示該矩陣中狀態的取值;Mi中的i表示第i個矩陣,實際上每個位置都有對應的矩陣。同時矩陣的維度是M×M維的,因為yi可以取到M個狀態(yi有M個狀態,yi-1的某個狀態可以轉移到M个中任意一個,yi-1有M個狀態,所以轉移有M×M種)。例M2(1,3)表示在狀態序列的第2個位置上(t=2),由t=1時刻的“狀態1”轉移到t=2的“狀態3”的非規範化概率。

  ②Mi中的元素是怎麼計算的,是什麼?

  計算方式見公式(11.22)和公式(11.23)。我先想到的是,Mi矩陣為yi-1到yi的轉移矩陣,每個時刻yi都是M種狀態,轉移也都是yi-1到yi這些轉移方式,計算方式都由公式(11.23)計算。那麼,不同的Mi之間的區別是什麼呢。我的理解是針對不同的時刻t、不同的x,特徵函數是否存在和相應的權值大小,決定了Mi不同。

  ③公式(11.24)的分子部分n+1個矩陣的適當元素的乘積,是什麼?

  仔細看一下CRF的簡化形式中公式(11.15)的分子部分,利用公式(11.13)對i進行展開,有:

  是不是就變成了公式(11.24)的分子部分n+1個矩陣的適當元素乘積的形式。也就是說,CRF的矩陣形式來源於簡化形式,至於為什麼會有這種方式,我覺得是便於計算吧,下邊前後向算法會用到這種形式。

  ④公式(11.25)規範化因子,是什麼?

  看上去是對所有序列的非規範化概率的總和。其實追根究底④這個問題是想知道矩陣運算在這裏計算的是什麼。

  綜上,條件隨機場矩陣形式的要點有:

  以2×2的Mi矩陣為例(例11.2),具體表示如下:

  看一下例11.2:

  以狀態序列(1,2,1)為例:

  解析如下:

  接下來看一下求規範化因子的過程:

  上面提到的問題④,n+1個矩陣連乘后,得到的結果仍然是M×M維的。但第1行第1列的元素,正好是所有路徑上的非規範化概率之和。

  了解完概率無向圖、條件隨機場的定義和各種表示方法之後,與隱馬爾可夫模型類似的,接下來介紹條件隨機場的3個基本問題:概率計算問題、學習問題、預測問題。

條件隨機場的概率計算問題

  與隱馬爾可夫模型類似,引進前向-後向向量,遞歸的計算概率(遞歸的計算過程是非常不同的)。

  先看前向計算過程。注意CRF作為無向圖模型,拋去了HMM的方向性,我們要從矩陣乘法的角度進行分析。仔細看一下公式(11.28),轉換成矩陣語言如下(以m=5為例,這裏將時刻的下標標記為t,用來區分yi的狀態和時刻t:αi->αt,Mi->Mt):

  具體到轉換過程中(考慮矩陣乘法的過程):

  可以看到,α1t的得出,是結合了t-1時刻各個αi的結果。再來理解一下“αt(yi|x)表示在位置t的標記是yi並且從1到t的前部分標記序列的非規範化概率”這句話,見下圖:

  實際上,t時刻α向量中的某一個分量,αi可以視作終點狀態取yi時的非規範化概率,並且這個概率是1->t時刻的整個過程中,所有可能序列的非規範化概率之和(從start到stop所有路徑上的非規範化概率之和)。如圖中,α1t也就是從start=y1到stop=y1過程中,所有可能序列的非規範化概率之和。因此,每個αi是start=y1到stop=yi的規範化因子Zi。這樣就能看出與HMM特別不同了,一個應用矩陣乘法,一個應用條件概率公式。

  我理解的大概就是這個樣子,不知道能不能寫清楚,接下來看下後向算法的計算過程。從公式(11.31)開始:

  具體到轉換過程中(考慮矩陣乘法的過程):

  於是,βt(yi|x)可以表示從t時刻yt:start=y1到yn+1:stop=y1,所有路徑上的非規範化概率之和(共T-t個結點狀態的序列)。前向算法也好,後向算法也好,這裏的箭頭指向僅表示乘的方向,不是有向圖結構。

  與HMM類似的,接下來看幾個概率計算。

  公式(11.32)和公式(11.33)還是好理解的,看一下示意圖:

  通過示意圖,先來看一下Z(X),Z(X)既可以通過前向向量,又可以通過後向向量來求。實際上,不管前向向量αn也好,後向向量β1也好,Z(X)的計算矩陣過程,實際上是把m個值加和,也就是得到所有狀態序列的規範化因子。

  分開看公式(11.32)分子部分,我覺得是兩個值(α、β)進行了相乘。第一個α值,代表了從0時刻start=y1到t時刻stop=yi的非規範化概率;第二個β值,代表了從t時刻start=yi到n+1時刻stop=y1的非規範化概率。公式(11.33)是類似分析方法。

  再來看一下幾個期望的計算,就不具體分析了:

 

 

條件隨機場的學習問題

  學習問題實際上討論的是在給定訓練數據集上估計模型參數的問題。條件隨機場模型實際上定義在時序數據上的對數線性模型(是不是與LR像),學習方法包括極大似然估計等,具體的有改進的迭代尺度法IIS、梯度下降法及擬牛頓法。

  具體的方法目前先不弄了(有大概了解,但了解的程度,不足以寫出來),趕趕刷題的進度去了…等找着工作了,準備畢業的時候再把這些方法整理上~

  話說這章的代碼沒寫,因為不會…不知道從什麼地方下手。

條件隨機場的預測問題

  同HMM,大名鼎鼎的維特比算法。

PASS

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

【其他文章推薦】

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

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

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

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

※回頭車貨運收費標準

QNX Message Passing,一個讓人頭禿的 IPC BUG

問題描述

QNX系統中 Client 與 Server 通過 QNX Message Passing 進行進程間通信。正式開發前,寫過測試程序 (Client),和 Server 通信一切正常。但把同樣的代碼拷貝到正式的 Client 中,結果發現調用 MsgSend() 后無響應,pidin 显示 Client 處於 REPLY PENDING 的狀態。

幾番嘗試,發現一個讓人很難接受的事實:只能在 Client 的主線程中調用 MsgSend() 才能收到 Server 的回復,如果在 Client 的對等線程中調用MsgSend(),則會因為收不到 Server 的回復而 Pending。

問題分析

測試程序能正常工作,所以起初懷疑是 Client 的問題。面對龐大的 Client,始終沒想到突破口。甚至曾一度懷疑是否是 QNX 的系統限制,MsgSend() 只能在主線程中調用,官網文檔翻了一圈並沒有發現這樣的限制。自己寫了個 IPC Server 來測試,發現並沒有這個問題。

Client Server 結果
測試程序,主線程中調用 MsgSend() 正式 Server OK
正式 Client,對等線程中調用 MsgSend() 正式 Server REPLY PENDING
正式 Client,對等線程中調用 MsgSend() 簡化版測試 Server OK

最後不得不懷疑起 Server,莫非 Server 端有什麼機制能檢測到消息是發自主線程還是對等線程,然後只回復來自主線程的 IPC 請求?

一個典型的 IPC Server 示例代碼如下:

While(1) {
   int rcvid = MsgReceive(chid, &recvBuf, sizeof(recvBuf), NULL);
   /* … process the request based on recvBuf … */
   MsgReply(rcvid, EOK, &replyBuf, sizeof(replyBuf)); // reply to unblock the IPC client
}

如果是從 Client 的主線程發送來的消息,rcvid 是一個很小的数字,如 1, 3, 5… 如果是從Client 的對等線程發送來的消息,rcvid 是一個大於 65535 的数字,如 65538, 65540, 65542… 

讓人意外的是 Server 用了一個 int16_t 來保存 rcvid,直接導致後續的 MsgReply() 無法正確的將消息回給 Client,從而導致 Client 一直處於 REPLY PENDING 狀態。

Reference

  • http://www.qnx.com/developers/docs/7.0.0/#com.qnx.doc.neutrino.sys_arch/topic/ipc_Robust.html  

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

Docker_01

目錄

  • 1.1 Docker簡介
    • 1.1.1 為什麼會有Docker的出現?
    • 1.1.2 Docker理念
    • 1.1.3 Docker or 虛擬機?
  • 2.1 Docker安裝
  • 3.1 Docker基本使用
    • 3.1.1 什麼是鏡像?
    • 3.1.2 Docker鏡像加載原理
    • 3.1.3 常用命令
  • 4.1 重要知識點

1.1 Docker簡介

1.1.1 為什麼會有Docker的出現?

一款產品從開發到上線,從操作系統,到運行環境,再到應用配置。作為開發+運維之間的協作我們需要關心很多東西,這也是很多互聯網公司都不得不面對的問題,特別是各種版本的迭代之後,不同版本環境的兼容,對運維人員都是考驗
Docker之所以發展如此迅速,也是因為它對此給出了一個標準化的解決方案。
環境配置如此麻煩,換一台機器,就要重來一次,費力費時。很多人想到,能不能從根本上解決問題。軟件可以帶環境安裝?1. 也就是說,安裝的時候,把原始環境一模一樣地複製過來。開發人員利用 Docker 可以消除協作編碼時“在我的機器上可正常工作”的問題。

1.1.2 Docker理念

Docker是基於Go語言實現的雲開源項目。
Docker的主要目標是“Build,Ship and Run Any App,Anywhere”,也就是通過對應用組件的封裝、分發、部署、運行等生命周期的管理,使用戶的APP(可以是一個WEB應用或數據庫應用等等)及其運行環境能夠做到“一次封裝,到處運行”。
Linux 容器技術的出現就解決了這樣一個問題,而 Docker 就是在它的基礎上發展過來的。將應用運行在 Docker 容器上面,而 Docker 容器在任何操作系統上都是一致的,這就實現了跨平台、跨服務器。只需要一次配置好環境,換到別的機子上就可以一鍵部署好,大大簡化了操作。

總而言之:解決了運行環境和配置問題軟件容器,方便做持續集成並有助於整體發布的容器虛擬化技術。

1.1.3 Docker or 虛擬機?

虛擬機(virtual machine)就是帶環境安裝的一種解決方案。
它可以在一種操作系統裏面運行另一種操作系統,比如在Windows 系統裏面運行Linux 系統。應用程序對此毫無感知,因為虛擬機看上去跟真實系統一模一樣,而對於底層系統來說,虛擬機就是一個普通文件,不需要了就刪掉,對其他部分毫無影響。這類虛擬機完美的運行了另一套系統,能夠使應用程序,操作系統和硬件三者之間的邏輯不變。 
但是虛擬機有很多缺點:1.資源佔用多2.冗餘步驟多3.啟動慢

由於前面虛擬機存在這些缺點,Linux 發展出了另一種虛擬化技術:Linux 容器(Linux Containers,縮寫為 LXC)。
Linux 容器不是模擬一個完整的操作系統,而是對進程進行隔離。有了容器,就可以將軟件運行所需的所有資源打包到一個隔離的容器中。容器與虛擬機不同,不需要捆綁一整套操作系統,只需要軟件工作所需的庫資源和設置。系統因此而變得高效輕量並保證部署在任何環境中的軟件都能始終如一地運行。
 
比較了 Docker 和傳統虛擬化方式的不同之處:

  • 傳統虛擬機技術是虛擬出一套硬件后,在其上運行一個完整操作系統,在該系統上再運行所需應用進程;
  • 而容器內的應用進程直接運行於宿主的內核,容器內沒有自己的內核,而且也沒有進行硬件虛擬。因此容器要比傳統虛擬機更為輕便。
  • 每個容器之間互相隔離,每個容器有自己的文件系統 ,容器之間進程不會相互影響,能區分計算資源。

對於開發和運維來說Docker的特點:

  • 更快速的應用交付和部署:傳統的應用開發完成后,需要提供一堆安裝程序和配置說明文檔,安裝部署后需根據配置文檔進行繁雜的配置才能正常運行。Docker化之後只需要交付少量容器鏡像文件,在正式生產環境加載鏡像並運行即可,應用安裝配置在鏡像里已經內置好,大大節省部署配置和測試驗證時間。
  • 更便捷的升級和擴縮容:隨着微服務架構和Docker的發展,大量的應用會通過微服務方式架構,應用的開發構建將變成搭樂高積木一樣,每個Docker容器將變成一塊“積木”,應用的升級將變得非常容易。當現有的容器不足以支撐業務處理時,可通過鏡像運行新的容器進行快速擴容,使應用系統的擴容從原先的天級變成分鐘級甚至秒級。
  • 更簡單的系統運維:應用容器化運行后,生產環境運行的應用可與開發、測試環境的應用高度一致,容器會將應用程序相關的環境和狀態完全封裝起來,不會因為底層基礎架構和操作系統的不一致性給應用帶來影響,產生新的BUG。當出現程序異常時,也可以通過測試環境的相同容器進行快速定位和修復。
  • 更高效的計算資源利用: Docker是內核級虛擬化,其不像傳統的虛擬化技術一樣需要額外的Hypervisor支持,所以在一台物理機上可以運行很多個容器實例,可大大提升物理服務器的CPU和內存的利用率。

2.1 Docker安裝

安裝地址有兩個:

  • docker官網:(http://www.docker.com)
  • docker中文官網:(https://www.docker-cn.com/)
    跟github一樣docker也有自己的中心倉庫Docker Hub官網: https://hub.docker.com/
    Linux環境下安裝的話可能要注意套個CDN加速一般選擇阿里雲或者網易的鏡像庫。

安裝命令:

$ sudo yum install -y yum-utils

$ sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo
These repositories are included in the docker.repo file above but are disabled by default. You can enable them alongside the stable repository. The following command enables the nightly repository.

$ sudo yum-config-manager --enable docker-ce-nightly
To enable the test channel, run the following command:

$ sudo yum-config-manager --enable docker-ce-test
You can disable the nightly or test repository by running the yum-config-manager command with the --disable flag. To re-enable it, use the --enable flag. The following command disables the nightly repository.

$ sudo yum-config-manager --disable docker-ce-nightly

具體方法參考官網:https://docs.docker.com/engine/install/centos/

3.1 Docker基本使用

3.1.1 什麼是鏡像?

鏡像是一種輕量級、可執行的獨立軟件包,用來打包軟件運行環境和基於運行環境開發的軟件,它包含運行某個軟件所需的所有內容,包括代碼、運行時、庫、環境變量和配置文件。
在Docker打包一個鏡像的時候會使用一個叫UnionFS(聯合文件系統)的文件系統:UnionFS(聯合文件系統):Union文件系統(UnionFS)是一種分層、輕量級並且高性能的文件系統,它支持對文件系統的修改作為一次提交來一層層的疊加,同時可以將不同目錄掛載到同一個虛擬文件系統下(unite several directories into a single virtual filesystem)。Union 文件系統是 Docker 鏡像的基礎。鏡像可以通過分層來進行繼承,基於基礎鏡像(沒有父鏡像),可以製作各種具體的應用鏡像。
特性:一次同時加載多個文件系統,但從外面看起來,只能看到一個文件系統,聯合加載會把各層文件系統疊加起來,這樣最終的文件系統會包含所有底層的文件和目錄。

3.1.2 Docker鏡像加載原理

Docker鏡像加載原理:
docker的鏡像實際上由一層一層的文件系統組成,這種層級的文件系統UnionFS。
bootfs(boot file system)主要包含bootloader和kernel, bootloader主要是引導加載kernel, Linux剛啟動時會加載bootfs文件系統,在Docker鏡像的最底層是bootfs。這一層與我們典型的Linux/Unix系統是一樣的,包含boot加載器和內核。當boot加載完成之後整個內核就都在內存中了,此時內存的使用權已由bootfs轉交給內核,此時系統也會卸載bootfs。

rootfs (root file system) ,在bootfs之上。包含的就是典型 Linux 系統中的 /dev, /proc, /bin, /etc 等標準目錄和文件。rootfs就是各種不同的操作系統發行版,比如Ubuntu,Centos等等。

平時我們安裝進虛擬機的CentOS都是好幾個G,為什麼docker這裏才200M??
對於一個精簡的OS,rootfs可以很小,只需要包括最基本的命令、工具和程序庫就可以了,因為底層直接用Host的kernel,自己只需要提供 rootfs 就行了。由此可見對於不同的linux發行版, bootfs基本是一致的, rootfs會有差別, 因此不同的發行版可以公用bootfs。

分層的鏡像:以我們的pull為例,在下載的過程中我們可以看到docker的鏡像好像是在一層一層的在下載.

為什麼 Docker 鏡像要採用這種分層結構呢?
最大的一個好處就是 – 共享資源
 
比如:有多個鏡像都從相同的 base 鏡像構建而來,那麼宿主機只需在磁盤上保存一份base鏡像,
同時內存中也只需加載一份 base 鏡像,就可以為所有容器服務了。而且鏡像的每一層都可以被共享。

3.1.3 常用命令

1.幫助命令:

  • docker version
  • docker info
  • docker –help

2.鏡像命令:

  • docker images OPTIONS說明:-a :列出本地所有的鏡像(含中間映像層)-q :只显示鏡像ID –digests :显示鏡像的摘要信息 –no-trunc :显示完整的鏡像信息。

  • docker search 某個XXX鏡像名字 OPTIONS說明:–no-trunc : 显示完整的鏡像描述 -s : 列出收藏數不小於指定值的鏡像 –automated : 只列出 automated build類型的鏡像;

  • docker pull 某個XXX鏡像名字

  • docker rmi 某個XXX鏡像名字ID 刪除鏡像
    3.容器命令:

  • 新建並啟動容器:
    docker run [OPTIONS] IMAGE [COMMAND] [ARG…]
    OPTIONS說明(常用):有些是一個減號,有些是兩個減號
     
    –name=”容器新名字”: 為容器指定一個名稱;
    -d: 後台運行容器,並返回容器ID,也即啟動守護式容器;
    -i:以交互模式運行容器,通常與 -t 同時使用;
    -t:為容器重新分配一個偽輸入終端,通常與 -i 同時使用;
    -P: 隨機端口映射;
    -p: 指定端口映射,有以下四種格式
          ip:hostPort:containerPort
          ip::containerPort
          hostPort:containerPort
          containerPort

  • 列出當前所有正在運行的容器
    docker ps [OPTIONS]
    OPTIONS說明(常用):
     
    -a :列出當前所有正在運行的容器+歷史上運行過的
    -l :显示最近創建的容器。
    -n:显示最近n個創建的容器。
    -q :靜默模式,只显示容器編號。
    –no-trunc :不截斷輸出。

  • 退出容器
    exit:容器停止退出。 ctrl+P+Q:容器不停止退出

  • 啟動容器
    docker start 容器ID或者容器名

  • 重啟容器
    docker restart 容器ID或者容器名

  • 停止容器
    docker stop 容器ID或者容器名

  • 強制停止容器
    docker kill 容器ID或者容器名

  • 刪除已停止的容器
    docker rm 容器ID(一次性刪除多個容器:1.docker rm -f $(docker ps -a -q)2.docker ps -a -q | xargs docker rm)

4.1 重要知識點

1.關於docker ps -a命令它不會列出你後台啟動的進程也就是說你用守護進程方式啟動容器的話:docker run -d 容器名是不會被ps出來的。
這個是docker的機制問題,比如你的web容器,我們以nginx為例,正常情況下,我們配置啟動服務只需要啟動響應的service即可。例如service nginx start但是,這樣做,nginx為後台進程模式運行,就導致docker前台沒有運行的應用,這樣的容器後台啟動后,會立即自殺因為他覺得他沒事可做了.所以,最佳的解決方案是,將你要運行的程序以前台進程的形式運行。
2.如何重新進入已經啟動的容器
有兩個法子:

  • docker exec -it 容器ID bashShell
  • docker attach 容器ID

上述兩個區別:

  • attach 直接進入容器啟動命令的終端,不會啟動新的進程
  • exec 是在容器中打開新的終端,並且可以啟動新的進程
    3.從容器內拷貝文件到主機上
    docker cp 容器ID:容器內路徑 目的主機路徑。

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

【其他文章推薦】

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

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

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

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

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

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

【原創】Linux中斷子系統(三)-softirq和tasklet

背景

  • Read the fucking source code! –By 魯迅
  • A picture is worth a thousand words. –By 高爾基

說明:

  1. Kernel版本:4.14
  2. ARM64處理器,Contex-A53,雙核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

中斷子系統中有一個重要的設計機制,那就是Top-half和Bottom-half,將緊急的工作放置在Top-half中來處理,而將耗時的工作放置在Bottom-half中來處理,這樣確保Top-half能儘快完成處理,那麼為什麼需要這麼設計呢?看一張圖就明白了:

  • ARM處理器在進行中斷處理時,處理器進行異常模式切換,此時會將中斷進行關閉,處理完成后再將中斷打開;
  • 如果中斷不分上下半部處理,那麼意味着只有等上一个中斷完成處理后才會打開中斷,下一个中斷才能得到響應。當某个中斷處理處理時間較長時,很有可能就會造成其他中斷丟失而無法響應,這個顯然是難以接受的,比如典型的時鐘中斷,作為系統的脈搏,它的響應就需要得到保障;
  • 中斷分成上下半部處理可以提高中斷的響應能力,在上半部處理完成后便將中斷打開(通常上半部處理越快越好),這樣就可以響應其他中斷了,等到中斷退出的時候再進行下半部的處理;
  • 中斷的Bottom-half機制,包括了softirq、tasklet、workqueue、以及前文中提到過的中斷線程化處理等,其中tasklet又是基於softirq來實現的,這也是本文討論的主題;

在中斷處理過程中,離不開各種上下文的討論,了解不同上下文的區分有助於中斷處理的理解,所以,還是來一張老圖吧:

  • task_struct結構體中的thread_info.preempt_count用於記錄當前任務所處的context狀態;
  • PREEMPT_BITS:用於記錄禁止搶佔的次數,禁止搶佔一次該值就加1,使能搶佔該值就減1;
  • SOFTIRQ_BITS:用於同步處理,關掉下半部的時候加1,打開下半部的時候減1;
  • HARDIRQ_BITS:用於表示處於硬件中斷上下文中;

前戲結束了,直奔主題吧。

2. softirq

2.1 初始化

softirq不支持動態分配,Linux kernel提供了靜態分配,關鍵的結構體描述如下,可以類比硬件中斷來理解:

/* 支持的軟中斷類型,可以認為是軟中斷號, 其中從上到下優先級遞減 */
enum
{
	HI_SOFTIRQ=0,       /* 最高優先級軟中斷 */
	TIMER_SOFTIRQ,      /* Timer定時器軟中斷 */
	NET_TX_SOFTIRQ,     /* 發送網絡數據包軟中斷 */
	NET_RX_SOFTIRQ,     /* 接收網絡數據包軟中斷 */
	BLOCK_SOFTIRQ,      /* 塊設備軟中斷 */
	IRQ_POLL_SOFTIRQ,   /* 塊設備軟中斷 */
	TASKLET_SOFTIRQ,    /* tasklet軟中斷 */
	SCHED_SOFTIRQ,      /* 進程調度及負載均衡的軟中斷 */
	HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on thenumbering. Sigh! */
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq, RCU相關的軟中斷 */

	NR_SOFTIRQS
};

/* 軟件中斷描述符,只包含一個handler函數指針 */
struct softirq_action {
	void	(*action)(struct softirq_action *);
};
/* 軟中斷描述符表,實際上就是一個全局的數組 */
static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;

/* CPU軟中斷狀態描述,當某個軟中斷觸發時,__softirq_pending會置位對應的bit */
typedef struct {
	unsigned int __softirq_pending;
	unsigned int ipi_irqs[NR_IPI];
} ____cacheline_aligned irq_cpustat_t;
/* 每個CPU都會維護一個狀態信息結構 */
irq_cpustat_t irq_stat[NR_CPUS] ____cacheline_aligned;

/* 內核為每個CPU都創建了一個軟中斷處理內核線程 */
DEFINE_PER_CPU(struct task_struct *, ksoftirqd);

來一張圖吧:

  • softirq_vec[]數組,類比硬件中斷描述符表irq_desc[],通過軟中斷號可以找到對應的handler進行處理,比如圖中的tasklet_action就是一個實際的handler函數;
  • 軟中斷可以在不同的CPU上并行運行,在同一個CPU上只能串行執行;
  • 每個CPU維護irq_cpustat_t狀態結構,當某個軟中斷需要進行處理時,會將該結構體中的__softirq_pending字段或上1UL << XXX_SOFTIRQ;

2.2 流程分析

2.2.1 軟中斷註冊

中斷處理流程中設備驅動通過request_irq/request_threaded_irq接口來註冊中斷處理函數,而在軟中斷處理流程中,通過open_softirq接口來註冊,由於它實在是太簡單了,我忍不住想把代碼貼上來:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

也就是將軟中斷描述符表中對應描述符的handler函數指針指向對應的函數即可,以便軟中斷到來時進行回調。

那麼,問題來了,什麼時候進行軟中斷函數回調呢?

2.2.2 軟中斷執行之一:中斷處理后

先看第一種情況,用圖片來回答問題:

  • Linux中斷子系統(二)-通用框架處理文章中講述了整个中斷處理流程,在接收到中斷信號后,處理器進行異常模式切換,並跳轉到異常向量表處進行執行,關鍵的流程為:el0_irq->irq_handler->handle_arch_irq(gic->handle_irq)->handle_domain_irq->__handle_domain_irq;
  • 在__handle_domain_irq函數中,irq_enter和irq_exit分別用於來標識進入和離開硬件中斷上下文處理,這個從preempt_count_add/preempt_count_sub來操作HARDIRQ_OFFSET可以看出來,這也對應到了上文中的Context描述圖;
  • 在離開硬件中斷上下文後,如果!in_interrupt() && local_softirq_pending為真,則進行軟中斷處理。這個條件有兩個含義:1)!in_interrupt()表明不能處在中斷上下文中,這個範圍包括in_nmi、in_irq、in_softirq(Bottom-half disable)、in_serving_softirq,凡是處於這幾種狀態下,軟中斷都不會被執行;2)local_softirq_pending不為0,表明有軟中斷處理請求;

軟中斷執行的入口就是invoke_softirq,繼續分析一波:

  • invoke_softirq函數中,根據中斷處理是否線程化進行分類處理,如果中斷已經進行了強制線程化處理(中斷強制線程化,需要在啟動的時候傳入參數threadirqs),那麼直接通過wakeup_softirqd喚醒內核線程來執行,否則的話則調用__do_softirq函數來處理;
  • Linux內核會為每個CPU都創建一個內核線程ksoftirqd,通過smpboot_register_percpu_thread函數來完成,其中當內核線程運行時,在滿足條件的情況下會執行run_ksoftirqd函數,如果此時有軟中斷處理請求,調用__do_softirq來進行處理;

上圖中的邏輯可以看出,最終的核心處理都放置在__do_softirq函數中完成:

  • local_softirq_pending函數用於讀取__softirq_pending字段,可以類比於設備驅動中的狀態寄存器,用於判斷是否有軟中斷處理請求;
  • 軟中斷處理時會關閉Bottom-half,處理完后再打開;
  • 軟中斷處理時,會打開本地中斷,處理完后關閉本地中斷,這個地方對應到上文中提到的Top-half和Bottom-half機制,在Bottom-half處理的時候,是會將中斷打開的,因此也就能繼續響應其他中斷,這個也就意味着其他中斷也能來打斷當前的Bottom-half處理;
  • while(softirq_bit = ffs(pending)),循環讀取狀態位,直到處理完每一個軟中斷請求;
  • 跳出while循環之後,再一次判斷是否又有新的軟中斷請求到來(由於它可能被中斷打斷,也就意味着可能有新的請求到來),有新的請求到來,則有三個條件判斷,滿足的話跳轉到restart處執行,否則調用wakeup_sotfirqd來喚醒內核線程來處理:
    1. time_before(jiffies, MAX_SOFTIRQ_TIME),軟中斷處理時間小於兩毫秒;
    2. !need_resched,當前沒有進程調度的請求;
    3. max_restart = MAX_SOFTIRQ_RESTART,跳轉到restart循環的次數不大於10次;
      這三個條件的判斷,是基於延遲和公平的考慮,既要保證軟中斷儘快處理,又不能讓軟中斷處理一直佔據系統,正所謂trade-off的藝術;

__do_softirq既然可以在中斷處理過程中調用,也可以在ksoftirqd中調用,那麼softirq的執行可能有兩種context,插張圖吧:

讓我們來思考最後一個問題:硬件中斷觸發的時候是通過硬件設備的電信號,那麼軟中斷的觸發是通過什麼呢?答案是通過raise_softirq接口:

  • 可以在中斷處理過程中調用raise_softirq來進行軟中斷處理請求,處理的實際也就是上文中提到過的irq_exit退出硬件中斷上下文之後再處理;
  • raise_softirq_irqoff函數中,最終會調用到or_softirq_pending,該函數會去讀取本地CPU的irq_stat中__softirq_pending字段,然後將對應的軟中斷號給置位,表明有該軟中斷的處理請求;
  • raise_softirq_irqoff函數中,會判斷當前的請求的上下文環境,如果不在中斷上下文中,就可以通過喚醒內核線程來處理,如果在中斷上下文中處理,那就不執行;
  • 多說一句,在軟中斷整個處理流程中,會經常看到in_interrupt()的條件判斷,這個可以確保軟中斷在CPU上的串行執行,避免嵌套;

2.2.3 軟中斷執行之二:Bottom-half Enable后

第二種軟中斷執行的時間點,在Bottom-half使能的時候,通常用於併發處理,進程空間上下文中進行調用:

  • 在討論併發專題的時候,我們談到過Bottom-half與進程之間能產生資源爭奪的情況,如果在軟中斷和進程之間有臨界資源(軟中斷上下文優先級高於進程上下文),那麼可以在進程上下文中調用local_bh_disable/local_bh_enable來對臨界資源保護;
  • 圖中左側的函數,都是用於打開Bottom-half的接口,可以看出是spin_lock_bh/read_lock_bh/write_lock_bh等併發處理接口的變種形式調用;
  • __local_bh_enable_ip函數中,首先判斷調用該本接口時中斷是否是關閉的,如果已經關閉了再操作BH接口就會告警;
  • preempt_count_sub需要與preempt_count_add配套使用,用於操作thread_info->preempt_count字段,加與減的值是一致的,而在__local_bh_enable_ip接口中,將cnt值的減操作分成了兩步:preempt_count_sub(cnt-1)和preempt_count_dec,這麼做的原因是執行完preempt_count_sub(cnt-1)后,thread_info->preempt_count字段的值保留了1,把搶佔給關閉了,當do_softirq執行完畢后,再調用preempt_count_dec再減去剩下的1,進而打開搶佔;
  • 為什麼在使能Bottom-half時要進行軟中斷處理呢?在併發處理時,可能已經把Bottom-half進行關閉了,如果此時中斷來了后,軟中斷不會被處理,在進程上下文中打開Bottom-half時,這時候就會檢查是否有軟中斷處理請求了;

3. tasklet

從上文中分析可以看出,tasklet是軟中斷的一種類型,那麼兩者有啥區別呢?先說結論吧:

  • 軟中斷類型內核中都是靜態分配,不支持動態分配,而tasklet支持動態和靜態分配,也就是驅動程序中能比較方便的進行擴展;
  • 軟中斷可以在多個CPU上并行運行,因此需要考慮可重入問題,而tasklet會綁定在某個CPU上運行,運行完后再解綁,不要求重入問題,當然它的性能也就會下降一些;

3.1 數據結構

  • DEFINE_PER_CPU(struct tasklet_head, tasklet_vec)為每個CPU都分配了tasklet_head結構,該結構用來維護struct tasklet_struct鏈表,需要放到該CPU上運行的tasklet將會添加到該結構的鏈表中,內核中為每個CPU維護了兩個鏈表tasklet_vec和tasklet_vec_hi,對應兩個不同的優先級,本文以tasklet_vec為例;
  • struct tasklet_struct為tasklet的抽象,幾個關鍵字段如圖所示,通過next來鏈接成鏈表,通過state字段來標識不同的狀態以確保能在CPU上串行執行,func函數指針在調用task_init()接口時進行初始化,並在最終觸發軟中斷時執行;

3.2 流程分析

  • tasklet本質上是一種軟中斷,所以它的調用流程與上文中討論的軟中斷流程是一致的;
  • 調度tasklet運行的接口是tasklet_schedule,如果tasklet沒有被調度則進行調度處理,將該tasklet添加到CPU對應的鏈表中,然後調用raise_softirq_irqoff來觸發軟中斷執行;
  • 軟中斷執行的處理函數是tasklet_action,這個在softirq_init函數中通過open_softirq函數進行註冊的;
  • tasklet_action函數,首先將該CPU上tasklet_vec中的鏈表挪到臨時鏈表list中,然後再對這個list進行遍歷處理,如果滿足執行條件則調用t->func()執行,並continue跳轉遍歷下一個節點。如果不滿足執行條件,則繼續將該tasklet添加回原來的tasklet_vec中,並再次觸發軟中斷;

3.3 接口

簡單貼一下接口吧:

/* 靜態分配tasklet */
DECLARE_TASKLET(name, func, data)

/* 動態分配tasklet */
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data);

/* 禁止tasklet被執行,本質上是增加tasklet_struct->count值,以便在調度時不滿足執行條件 */
void tasklet_disable(struct tasklet_struct *t);

/* 使能tasklet,與tasklet_diable對應 */
void tasklet_enable(struct tasklet_struct *t);

/* 調度tasklet,通常在設備驅動的中斷函數里調用 */
void tasklet_schedule(struct tasklet_struct *t);

/* 殺死tasklet,確保不被調度和執行, 主要是設置state狀態位 */
void tasklet_kill(struct tasklet_struct *t);

收工!

歡迎關注個人公眾號,不定期分享Linux內核機制文章。

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

【其他文章推薦】

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

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

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

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

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

Java中容易遺漏的小知識點( 一 )(為了和小白一樣馬上要考試的兄弟準備的,希望小白和大家高過不掛)

筆者csdn博客同文地址:https://blog.csdn.net/weixin_45791445/article/details/106597515

我是小康小白,一個平平無奇的Java小白。熱愛有趣的文字,生活和遠方。
個人博客:https://blog.csdn.net/weixin_45791445

小白最近快考試了,複習的順便總結一下一些小白認為容易忘記的知識點,希望能幫助到大家。

小白的總結系列二,三已上線Java中容易遺漏的常用的知識點( 二 )(為了和小白一樣馬上要考試的兄弟準備的,希望小白和大家高過不掛)
Java中容易遺漏的常用的知識點( 三 )(為了和小白一樣馬上要考試的兄弟準備的,希望小白和大家高過不掛)
  1. Java代碼運行機制

     1. 編寫源代碼
     	得到的是以“.java”為擴展名的源文件,源文件名應與該文件中被修飾為public的類名相同。
     2. 編譯(使用cmd命令編譯時使用的命令是javac 文件名.java。使用的javac編輯器)
     	編譯后的文件叫做類文件。得到的是以“.class”為擴展名的字節碼文件。
     	這裏出現的錯誤叫編譯時錯誤。
     	
     然後使用java命令運行,命令格式為:java 項目名(注意這裏沒有後綴))	
     3.類裝載ClassLoader
     4.字節碼(byte-code)校驗
     5. 解釋
     6. 運行
     這裏出現的錯誤叫運行時錯誤。
    
  2. 在一個簡單的Java程序中有一個獲以上的類組合而成。其次在一個可以獨立運行的Java程序中有且只有一個main()方法,它是程序運行的開端。程序的主體被包含在類中,這個起始的地方被稱為main方法。main方法之前必須加上public,static,void。static表示

  3. switch中的控製表達式必須為char,int,short或byte類型的。case後面的常量表達式必須和控製表達式的類型相同。(書上)

書上這一節後面的習題中有個選項說支持long類型是錯誤的。

switch表達式後面的數據類型只能是byte,short,char,int四種整形類型,枚舉類型和java.lang.String類型(從java 7才允許),不能是boolean類型。至於四種基本類型的包裝類是由於有自動拆箱的操作所以支持(實際)

  1. instanceof是對象運算符,用來判定一個對象是否屬於莫個指定的類(或其子類)的實例。

  2. 邏輯運算符分為標準邏輯運算符(!& ^ |)和條件邏輯運算符(&& ||)

  3. byte:1字節,short:2字節,int:4字節,long:8字節,float:4字節,double:8字節,char:2字節,boolean:書上此處為空。

switch的格式

do-while的格式(最後要加一個”;”)

foreach

  1. 運算符優先級
  2. 在判斷語句中的i++和–j是一個完整的獨立語句,先進行自加或者自減后再進行比對。
    (下面是debug測試)
  3. outer標籤

outer標籤是什麼,上面的文章講的很詳細。
小白在這裏補充一下break outer;和continue outer;的區別

break outer;是直接完全的跳出到outer標籤標記的循環外面。
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200608102636433.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTc5MTQ0NQ==,size_16,color_FFFFFF,t_70#pic_center =500×500)
continue outer;的作用並非完全跳出外層循環,而是跳出outer標籤標記循環的本次循環,直接進行此循環的下一次循環;

![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200608102735262.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTc5MTQ0NQ==,size_16,color_FFFFFF,t_70#pic_center =500×500)
12. 構造方法

  1. 類是對象模板,只有實例化之後才可以使用。new運算符用於創建一個類的實例並返回對象的引用。
    默認構造方法對成員變量的初始化原則如下:
    對於整型成員變量,默認初值是0;對與浮點型,默認初值是0.0;對於boolean類型,默認初值是false;對於引用型,默認值是null。

  2. this關鍵字
    this可以出現在類的構造方法和非static修飾的成員方法(即實例方法)中,this代表實例對象自身,可以通過this來訪問實例對象的成員變量或調用成員方法。
    類方法中不可以使用this
    由於類方法可以通過類名字直接調用,這時可能還沒有任何對象產生,因此指代對象實例本身的this關鍵字不可以出現在類方法中。

  3. 	1)public訪問權限最具有開放性,可以用來修飾類、類與接口的成員(包括成員變量、成員方法)
    。由public類修飾的類或類成員可被任何類訪問。
    	2)protected可以用來修飾類的成員變量或方法。具有protected訪問特性的類成員可以被本類、
    本包中的其他類訪問,也可以被其他包中的子類繼承,它的可訪問性低於public,高於默認。
    	3)如果在定義類、接口、成員變量、成員方法時沒有指定訪問權限控制符,他們的權限就為默認權
    限。具有默認權限的類、接口、成員變量、成員方法,只能被本類和同一個包中的其他類、接口及成員方
    法引用,因此默認權限也被稱為包權限。
    	4)私有訪問控制符private用來聲明類的私有成員,它提供了最高的保護級別。用private修飾的
    成員變量或方法只能被該類自身所訪問和修改,而不能被任何其他類(包括該類的子類)來獲取和引用。
    

Java文件名應與修飾為public的類名相同。且Java文件中只能有一個public修飾的類

特殊情況:
只有被修飾為public訪問權限的成員變量和方法才可以被不同包的其他類使用,修飾為protected的成員變量和方法可以被不同包的子類繼承,訪問。不同包的其他類禁止訪問修飾為private和默認權限的成員變量和方法。

  1. super關鍵字
    子類在繼承父類時,可能會出現變量隱藏、方法覆蓋(overriding)等現象。變量隱藏指子類的成員變量與父類成員同名,此時,父類的成員變量被隱藏。方法覆蓋指子類的方法與父類的方法名相同,方法的返回值類型、入口參數的數目、類型、順序均相同,只是方法實現的功能不同,此時父類的方法被覆蓋。如果子類需要調用或訪問父類被隱藏的變量或被覆蓋的方法,可以使用super關鍵字實現。
    當用子類的構造方法創建一個子類對象時,子類的構造方法總會顯式或隱式地先調用父類的某個構造方法。如果子類的構造方法沒有明顯地指明調用父類的哪個構造方法,Java會默認調用父類的無參構造方法;子類也可以通過super關鍵字顯式調用父類指定的構造方法,具體調用哪個構造方法需要根據super()的參數類型決定。

  2. Java中的子類對象可以向上轉換為父類對象(也稱上轉型對象),允許將子類的實例賦值給父類的引用,也允許一個父類的引用指向子類對象。
    但是反過來,一個父類對象的類型未必可以向下轉換成子類對象,因為子類具有的信息,父類未必包含,這種轉換是不安全的。只有當父類引用實際上指向一個子類對象時,才可以進行這種轉換。

  3. 實例變量依附於對象,在使用new關鍵字創建對象時,Java運行系統為每個對象分配一個內存空間,不同的實例變量佔用不同的內存區域。類變量依附於類(而非對象),對於類變量來說,Java運行系統為類的類變量分配公共的存儲空間,該類的每個實例對象共享同一類變量的存儲空間。因此,每個對象對類變量的修改都會影響其他實例對象。
    (1)類變量可以通過類名直接訪問,而不需要創建對象。
    (2)任何一個對象對類變量的修改,都是在同一內存單元上完成的。因此,每個對象對類變量的修改都會影響其他實例對象。

    聲明為static的方法稱為類方法(或稱靜態方法),與此相對,沒有static修飾的成員方法則為實例方法。類方法的本質是該方法屬於整個類,而不是屬於某個實例,可以不創建實例,直接通過類名調用。 類方法的使用具有以下特點:
    (1)類方法可以通過類直接調用,而不需要創建實例對象。例如:Java Application的入口main()方法就被聲明為static類方法,可以不需要創建任何實例對象對調用。
    (2)類方法屬於整個類,被調用時可能還沒有創建任何對象實例,因此類方法內只可以訪問類變量,而不能直接訪問實例變量和實例方法。
    (3)類方法中不能使用this關鍵字,因為靜態方法不屬於任何一個實例。

  4. 抽象類和抽象方法(為什麼會有抽象類?)
    語法:

    abstract class <類名> [extends<父類>][implements<接口名>]{ <類主體> }
    

如果一個類A需要繼承抽象類,則該類必須實現抽象類中定義的所有抽象方法。否則,該類也必須修飾為抽象類。也就是說,抽象類的子類如果僅實現父類的部分抽象方法,子類也必須聲明為抽象類。

為什麼會有抽象類和抽象方法?
有時候,我們沒有足夠的信息去描述一個具體的對象,這樣就不能用正常的類來描述它,那麼這時候就需要抽象類了。抽象方法沒有方法體,為什麼呢?小白的理解是我們只知道這個對象有這樣的一種方法(或者理解為” 行為 “),但不知到它的這種方法能夠干什麼,就沒有寫具體的方法主體。因此,抽象類不能實例化(創建的人都不知道它具體是什麼,怎麼實例成一個具體的對象)
![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200607181353317.png#pic_center =240×240)

  1. 接口
    接口(interface)是Java所提供的另一種重要結構。接口是一種特殊的類,但接口與類存在着本質的區別。類有成員變量和成員方法,而接口卻只有常量和抽象方法,也就是說接口的成員變量必須初始化,同時接口中的所有方法必須聲明為abstract方法。

語法:

[接口修飾符] interface〈接口名〉[extends〈父類接口列表〉] { 接口體 }

用implements子句表示類實現某個接口。一個類可以同時實現多個接口,接口之間用逗號“,”分隔。 在類體中可以使用接口中定義的常量,由於接口中的方法為抽象方法,所以必須在類體中加入要實現接口方法的代碼,如果一個接口是從別的一個或多個父接口中繼承而來,則在類體中必須加入實現該接口及其父接口中所有方法的代碼。 在實現一個接口時,類中對方法的定義要和接口中的相應方法的定義相匹配,其方法名、方法的返回值類型、方法的訪問權限和參數的數目與類型信息要一致。

接口繼承中出現的方法重名和常量重名
(1)方法重名 如兩個方法完全一樣,只保留一個。 如果兩個方法有不同的參數(類型或個數),那麼子接口中包括兩個方法,方法被重載。 若兩個方法僅在返回值上不同,出現錯誤。
(2)常量重名 兩個重名常量全部保留,並使用原來的接口名作為前綴。

  1. 內部類:
    Java支持在一個類中聲明另一個類,這樣的類稱為內部類(InnerClass),而包含內部類的類稱為內部類的外部類(OuterClass)。內部類一般用來實現一些沒有通用意義的功能邏輯。

eaducoder裏面老用。小白表示心累ಥ_ಥಥ_ಥ。

一個類把內部類看成是自己的成員,外部類的成員變量在內部類中仍然有效,內部類可以直接使用外部類中的成員變量和方法,即使他們是private的,這也是內部類的一個好處。

  1. 匿名類(小白表示從未用過,但寫一下吧。萬一考呢。)
    使用類創建對象時,Java允許把類體與對象的創建組合在一起。也就是說,類創建對象時,除了構造方法還有類體,此類體被稱為匿名類。
    實戰:
一個匿名類:
Student3 liu = new Student3() { 
	void speak() { 
			System.out.println("這是匿名類中的方法"); 
		} 
};// 匿名類體結束

重點來了

  1. 泛型
    泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。這種參數類型可以用在類、接口和方法的創建中,分別稱為泛型類、泛型接口、泛型方法。

1)泛型類聲明:
創建一個簡單的泛型是非常容易的。首先,在一對尖括號(< >)中聲明類型變量,以逗號間隔變量名列表。在類的實例變量和方法中,可以在任何類型的地方使用那些類型變量。
格式:

class 泛型類名<泛型列表>{ 
	類體 
}

泛型類聲明時並不指明泛型列表是什麼類型的數據,可以是任何對象或接口,但不能是基本類型數據。泛型列表處的泛型可以作為類的成員變量的類型、方法的類型以及局部變量的類型。

2)使用泛型類聲明對象:
使用泛型類聲明對象時,必須指定類中使用泛型的具體具體類名,如: Chorus<Student,Button> model model = new Chorus<Student,Button>();

3)泛型接口:
格式:

Interface 泛型接口名<泛型列表>{
	 接口體 
}

小白沒想到書上對於泛型講的這麼多。(唉,不禁為我的菜雞感覺億點悲傷。)

小白實戰中常用的是:
在創建arraylist或者list集合時加泛型

ArrayList<String> listTest = new ArrayList<String>();

這樣限定了創建集合的類型。對於此小白的理解時泛型是在創建list或者arrayList集合時指定集合中的元素是同一種類型。例如你在創建時指定了創建的list集合中只能有String類型的變量,那麼你向其中添加數據時,如果不是String類型的數據,程序在編譯時就會報錯。如果不是用泛型這種錯誤只有在程序運行時才會報錯。而且,泛型讓我們在get獲得集合中的數據時,無需因為類型不同需要強轉而煩惱。

當你向加了泛型的語句中加入不同數據類型的數據時是會報錯的。但如果不加,這種錯誤就只能在程序運行是才報錯。

這次先寫到這裏小白明天接着寫。

Java中容易遺漏的常用的知識點( 二 )(為了和小白一樣馬上要考試的兄弟準備的,希望小白和大家高過不掛)
Java中容易遺漏的常用的知識點( 三 )(為了和小白一樣馬上要考試的兄弟準備的,希望小白和大家高過不掛)

![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200607181353317.png#pic_center =240×240)

小白的總結系列二已上線Java中容易遺漏的常用的知識點( 二 )(為了和小白一樣馬上要考試的兄弟準備的,希望小白和大家高過不掛)

兄弟們,小白編寫不易。希望各位兄弟們,點贊評論收藏加關注。小白在此謝謝各位老爺們,也祝福和我一樣面臨考試的兄弟們高分通過。

對於白嫖的兄弟們,

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

【其他文章推薦】

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

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

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

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

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

聽說你的資源被盜用了,那你知道 Nginx 怎麼防盜鏈嗎?

上一篇文章講了 Nginx 中的變量和運行原理,下面就來說一個主要提供變量並修改變量的值的模塊,也就是我們要講的防盜鏈模塊:referer 模塊。

簡單有效的防盜鏈手段

場景

如果做過個人站點的同學,可能會遇到別人盜用自己站點資源鏈接的情況,這就是盜鏈。說到盜鏈就要說一個 HTTP 協議的 頭部,referer 頭部。當其他網站通過 URL 引用了你的頁面,用戶在瀏覽器上點擊 URL 時,HTTP 請求的頭部會通過 referer 頭部將該網站當前頁面的 URL 帶上,告訴服務器本次請求是由誰發起的。

例如,在谷歌中搜索 Nginx 然後點擊鏈接:

在打開的新頁面中查看請求頭會發現,請求頭中包含了 referer 頭部且值是 https://www.google.com/。

像谷歌這種我們是允許的,但是有一些其他的網站想要引用我們自己網站的資源時,就需要做一些管控了,不然豈不是誰都可以拿到鏈接。

目的

這裏目的其實已經很明確了,就是要拒絕非正常的網站訪問我們站點的資源。

思路

  • invalid_referer 變量
    • referer 提供了這個變量,可以用來配置哪些 referer 頭部合法,也就是,你允許哪些網站引用你的資源。

referer 模塊

要實現上面的目的,referer 模塊可得算頭一號,一起看下 referer 模塊怎麼用的。

  • 默認編譯進 Nginx,通過 --without-http_referer_module 禁用

referer 模塊有三個指令,下面看一下。

Syntax: valid_referers none | blocked | server_names | string ...;
Default: —
Context: server, location

Syntax: referer_hash_bucket_size size;
Default: referer_hash_bucket_size 64; 
Context: server, location

Syntax: referer_hash_max_size size;
Default: referer_hash_max_size 2048; 
Context: server, location
  • valid_referers 指令,配置是否允許 referer 頭部以及允許哪些 referer 訪問。
  • referer_hash_bucket_size 表示這些配置的值是放在哈希表中的,指定哈希表的大小。
  • referer_hash_max_size 則表示哈希表的最大大小是多大。

這裏面最重要的是 valid_referers 指令,需要重點來說明一下。

valid_referers 指令

可以同時攜帶多個參數,表示多個 referer 頭部都生效。

參數值

  • none
    • 允許缺失 referer 頭部的請求訪問
  • block:允許 referer 頭部沒有對應的值的請求訪問。例如可能經過了反向代理或者防火牆
  • server_names:若 referer 中站點域名與 server_name 中本機域名某個匹配,則允許該請求訪問
  • string:表示域名及 URL 的字符串,對域名可在前綴或者後綴中含有 * 通配符,若 referer 頭部的值匹配字符串后,則允許訪問
  • 正則表達式:若 referer 頭部的值匹配上了正則,就允許訪問

invalid_referer 變量

  • 允許訪問時變量值為空
  • 不允許訪問時變量值為 1

實戰

下面來看一個配置文件。

server {
	server_name referer.ziyang.com;
    listen 80;

	error_log logs/myerror.log debug;
	root html;
	location /{
		valid_referers none blocked server_names
               		*.ziyang.com www.ziyang.org.cn/nginx/
               		~\.google\.;
		if ($invalid_referer) {
    			return 403;
		}
		return 200 'valid\n';
	}
}

那麼對於這個配置文件而言,以下哪些請求會被拒絕呢?

curl -H 'referer: http://www.ziyang.org.cn/ttt' referer.ziyang.com/
curl -H 'referer: http://www.ziyang.com/ttt' referer.ziyang.com/
curl -H 'referer: ' referer.ziyang.com/
curl referer.ziyang.com/
curl -H 'referer: http://www.ziyang.com' referer.ziyang.com/
curl -H 'referer: http://referer.ziyang.com' referer.ziyang.com/
curl -H 'referer: http://image.baidu.com/search/detail' referer.ziyang.com/
curl -H 'referer: http://image.google.com/search/detail' referer.ziyang.com/

我們需要先來解析一下這個配置文件。valid_referers 指令配置了哪些值呢?

 valid_referers none blocked server_names
        *.ziyang.com www.ziyang.org.cn/nginx/
        ~\.google\.;
  • none:表示沒有 referer 的可以訪問
  • blocked:表示 referer 沒有值的可以訪問
  • server_names:表示本機 server_name 也就是 referer.ziyang.com 可以訪問
  • *.ziyang.com:匹配上了正則的可以訪問
  • www.ziyang.org.cn/nginx/:該頁面發起的請求可以訪問
  • ~\.google\.:google 前後都是正則匹配

下面就實際看下響應:

# 返回 403,沒有匹配到任何規則
  ~ curl -H 'referer: http://www.ziyang.org.cn/ttt' referer.ziyang.com/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
  ~ curl -H 'referer: http://image.baidu.com/search/detail' referer.ziyang.com/
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.17.8</center>
</body>
</html>
# 匹配到了 *.ziyang.com
  ~ curl -H 'referer: http://www.ziyang.com/ttt' referer.ziyang.com/
valid
  ~ curl -H 'referer: http://www.ziyang.com' referer.ziyang.com/
valid
# 匹配到了 server name
  ~ curl -H 'referer: http://referer.ziyang.com' referer.ziyang.com/
valid
# 匹配到了 blocked
  ~ curl -H 'referer: ' referer.ziyang.com/
valid
# 匹配到了 none
  ~ curl referer.ziyang.com/
valid
# 匹配到了 ~\.google\.
  ~ curl -H 'referer: http://image.google.com/search/detail' referer.ziyang.com/
valid

防盜鏈另外一種解決方案:secure_link 模塊

referer 模塊是一種簡單的防盜鏈手段,必須依賴瀏覽器發起請求才會有效,如果攻擊者偽造 referer 頭部的話,這種方式就失效了。

secure_link 模塊是另外一種解決的方案。

它的主要原理是,通過驗證 URL 中哈希值的方式防盜鏈。

基本過程是這個樣子的:

  • 由服務器(可以是 Nginx,也可以是其他 Web 服務器)生成加密的安全鏈接 URL,返回給客戶端
  • 客戶端使用安全 URL 訪問 Nginx,由 Nginx 的 secure_link 變量驗證是否通過

原理如下:

  • 哈希算法是不可逆的
  • 客戶端只能拿到執行過哈希算法的 URL
  • 僅生成 URL 的服務器,驗證 URL 是否安全的 Nginx,這兩者才保存原始的字符串
  • 原始字符串通常由以下部分有序組成:
    • 資源位置。如 HTTP 中指定資源的 URI,防止攻擊者拿到一個安全 URI 后可以訪問任意資源
    • 用戶信息。如用戶的 IP 地址,限制其他用戶盜用 URL
    • 時間戳。使安全 URL 及時過期
    • 密鑰。僅服務器端擁有,增加攻擊者猜測出原始字符串的難度

模塊:

  • ngx_http_secure_link_module
    • 未編譯進 Nginx,需要通過 –with-http_secure_link_module 添加
  • 變量
    • secure_link
    • secure_link_expires
Syntax: secure_link expression;
Default: —
Context: http, server, location

Syntax: secure_link_md5 expression;
Default: —
Context: http, server, location

Syntax: secure_link_secret word;
Default: —
Context: location

變量值及帶過期時間的配置示例

  • secure_link
    • 值為空字符串:驗證不通過
    • 值為 0:URL 過期
    • 值為 1:驗證通過
  • secure_link_expires
    • 時間戳的值

命令行生成安全鏈接

  • 生成 md5
echo -n '時間戳URL客戶端IP密鑰' | openssl md5 -binary | openssl base64 | tr +/ - | tr -d =
  • 構造請求 URL
/test1.txt?md5=md5生成值&expires=時間戳(如 2147483647)

Nginx 配置

  • secure_link $arg_md5,$arg_expires;
    • secure_link 後面必須跟兩個值,一個是參數中的 md5,一個是時間戳
  • secure_link_md5 “$secure_link_expires$uri$remote_addr secret”;
    • 按照什麼樣的順序構造原始字符串

實戰

下面是一個實際的配置文件,我這裏就不做演示了,感興趣的可以自己做下實驗。

server {
	server_name securelink.ziyang.com;
    listen 80;
	error_log  logs/myerror.log  info;
	default_type text/plain;
	location /{
		secure_link $arg_md5,$arg_expires;
        secure_link_md5 "$secure_link_expires$uri$remote_addr secret";

        if ($secure_link = "") {
            return 403;
        }

        if ($secure_link = "0") {
            return 410;
        }

		return 200 '$secure_link:$secure_link_expires\n';
	}

	location /p/ {
        secure_link_secret mysecret2;

        if ($secure_link = "") {
            return 403;
        }

        rewrite ^ /secure/$secure_link;
	}

	location /secure/ {
		alias html/;
    	internal;
	}
}

僅對 URI 進行哈希的簡單辦法

除了上面這種相對複雜的方式防盜鏈,還有一種相對簡單的防盜鏈方式,就是只對 URI 進行哈希,這樣當 URI 傳

  • 將請求 URL 分為三個部分:/prefix/hash/link
  • Hash 生成方式:對 “link 密鑰” 做 md5 哈希
  • 用 secure_link_secret secret; 配置密鑰

命令行生成安全鏈接

  • 原請求
    • link
  • 生成的安全請求
    • /prefix/md5/link
  • 生成 md5
    • echo -n 'linksecret' | openssl md5 –hex

Nginx 配置

  • secure_link_secret secret;

這個防盜鏈的方法比較簡單,那麼具體是怎麼用呢?大家都在網上下載過資源對吧,不管是电子書還是軟件,很多網站你點擊下載的時候往往會彈出另外一個頁面去下載,這個新的頁面其實就是請求的 Nginx 生成的安全 URL。如果這個 URL 被拿到的話,其實還是可以用的,所以需要經常的更新密鑰來確保 URL 不會被盜用。

今天這篇文章詳細講了防盜鏈的具體用法,最近的這兩篇文章都是說的已有的變量用法,下一篇文章講一下怎麼生成新的變量。

本文首發於我的個人博客:iziyang.github.io,所有配置文件我已經放在了 Nginx 配置文件,大家可以自取。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

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