面试官问我平时怎么看源码的,我把这篇文章甩给他了。
本文來自作者投稿,原作者:WwpwW
1.1,為什么要分析源碼?
分析源碼可以培養(yǎng)一下自己獨(dú)立思考問題的能力(愿意讀源碼找問題的能力),最重要的是我們不用再買紙質(zhì)書去學(xué)習(xí)數(shù)據(jù)結(jié)構(gòu)了,數(shù)據(jù)結(jié)構(gòu)的應(yīng)用都在源碼里面了,正如那句被人熟知的"營養(yǎng)都在湯里面"一樣,當(dāng)我們看過一遍一遍數(shù)據(jù)結(jié)構(gòu)的理論知識后還是想不起它在哪里用到時(shí),可能看一看源碼加上自己的一點(diǎn)思考時(shí)就知道它的使用場景了,解答完畢。
1.2,分析源碼的好處是?
其實(shí),對于工作一段時(shí)間的小伙伴來說,我們都是面向業(yè)務(wù)開發(fā),就是被人飯后談資的增刪改查程序員/程序猿,當(dāng)然了,有的人說起來這樣的話,"api調(diào)包俠"就有點(diǎn)過分了,其實(shí)對于我個(gè)人來說,我是受不了這樣的話語的,因?yàn)樵鰟h改查是常用的操作,即滿足了"二八原則",其實(shí),程序員/程序猿都是工作中不可或缺的一部分,也是企業(yè)開發(fā)應(yīng)用中重要的一環(huán),分析源碼可能就會帶來顯而易見的內(nèi)功提升,其次就是分析源碼的過程中就是在向優(yōu)秀的人學(xué)習(xí)的一種體現(xiàn),畢竟源碼里面隱藏了大師多年的心得和思想。
1.3,如何分析源碼?
在整個(gè)文章的閱讀過程中,想必你已經(jīng)學(xué)會了如何分析源碼了,從哪入手,這也是一種潛移默化的過程。
本文以分析LinkedBlockingQueue的源碼為例,介紹下應(yīng)該如何看源碼!
二, ?方法分析
2.1,構(gòu)造函數(shù)
public?LinkedBlockingQueue()?{//創(chuàng)建容量為整形最大值this(Integer.MAX_VALUE); }public?LinkedBlockingQueue(int?capacity)?{//若capacity小于0,則直接拋出參數(shù)不合法的異常if?(capacity?<=?0)?throw?new?IllegalArgumentException();this.capacity?=?capacity;//默認(rèn)創(chuàng)建一個(gè)元素為null的節(jié)點(diǎn)Nodelast?=?head?=?new?Node<E>(null); }2.2,add()方法
public?boolean?add(E?e)?{//添加到隊(duì)列的末尾addLast(e);return?true;} //第二步 public?void?addLast(E?e)?{//若添加失敗,則直接拋出隊(duì)列已滿的異常信息,給與提示if?(!offerLast(e))throw?new?IllegalStateException("Deque?full");} //第三步 public?boolean?offerLast(E?e)?{//若添加的元素e為null,則直接拋出空指針異常,因?yàn)椴辉试S添加元素為null的情況if?(e?==?null)?throw?new?NullPointerException();//構(gòu)造一個(gè)元素為e的節(jié)點(diǎn)nodeNode<E>?node?=?new?Node<E>(e);final?ReentrantLock?lock?=?this.lock;//進(jìn)行加鎖操作lock.lock();try?{//進(jìn)行第四步操作return?linkLast(node);}?finally?{//進(jìn)行釋放鎖,當(dāng)然了,這里你要記住鎖釋放是放到finally語句塊里面的(重要)lock.unlock();}} //第四步 private?boolean?linkLast(Node<E>?node)?{//?assert?lock.isHeldByCurrentThread();//如果隊(duì)列里元素個(gè)數(shù)大于等于了隊(duì)列的容量,說明此時(shí)不能再將元素放入隊(duì)列里面了,直接返回false即可if?(count?>=?capacity)return?false;//創(chuàng)建一個(gè)臨時(shí)變量l,將最后一個(gè)節(jié)點(diǎn)的引用賦值給lNode<E>?l?=?last;//將最后一個(gè)節(jié)點(diǎn)的引用賦值給新節(jié)點(diǎn)node的前一個(gè)引用(鏈表的特點(diǎn))node.prev?=?l;//將新節(jié)點(diǎn)node賦值給最后一個(gè)節(jié)點(diǎn)(因?yàn)樵厝珀?duì)列是放在隊(duì)列的末尾的,隊(duì)列的特點(diǎn)->先進(jìn)先出)last?=?node;//為什么這里要判斷first是否為null呢?因?yàn)樘砑訒r(shí)不知道隊(duì)列里是否已經(jīng)存在元素,若first為null,說明隊(duì)列里沒有元素if?(first?==?null)//此時(shí)的node就是第一個(gè)結(jié)點(diǎn),賦值即可first?=?node;else//將新節(jié)點(diǎn)node掛在原有結(jié)點(diǎn)的下一個(gè),即l.next=nodel.next?=?node;//隊(duì)列元素個(gè)數(shù)進(jìn)行加一++count;//發(fā)送一個(gè)信號,隊(duì)列不滿的信號notEmpty.signal();//默認(rèn)將元素e添加到隊(duì)列里面了~,即返回truereturn?true;}2.3,size()方法
?public?int?size()?{//直接返回隊(duì)列里表示元素個(gè)數(shù)的成員變量count即可return?count.get();}2.4,peek()方法
public?E?peek()?{//若隊(duì)列元素個(gè)數(shù)為0,說明隊(duì)列里還未有元素,直接返回nullif?(count.get()?==?0)return?null;//獲取鎖final?ReentrantLock?takeLock?=?this.takeLock;//進(jìn)行加鎖操作takeLock.lock();try?{//獲取隊(duì)列的第一個(gè)結(jié)點(diǎn)引用firstNode<E>?first?=?head.next;//若隊(duì)列的第一個(gè)節(jié)點(diǎn)引用為null,則直接返回null即可if?(first?==?null)return?null;else//獲取第一個(gè)節(jié)點(diǎn)引用first的元素item返回return?first.item;}?finally?{//進(jìn)行釋放鎖,記得釋放鎖要放在finally語句塊里,確保鎖可以得到釋放takeLock.unlock();}}2.5,contains()方法
public?boolean?contains(Object?o)?{//引入隊(duì)列里不允許放入null,所以若元素o為null,則直接返回false,相當(dāng)于進(jìn)行了前置校驗(yàn)操作if?(o?==?null)?return?false;//第二步fullyLock();try?{//循環(huán)遍歷隊(duì)列的每個(gè)節(jié)點(diǎn)node的元素item是否與元素o相等,若存在相等元素,則包含,返回true即可for?(Node<E>?p?=?head.next;?p?!=?null;?p?=?p.next)if?(o.equals(p.item))return?true;return?false;}?finally?{//第四步,第三次說明了,釋放鎖要放在finally語句塊里面,確保鎖可以得到正確的釋放fullyUnlock();}}/***?Locks?to?prevent?both?puts?and?takes.*///第二步,上面的注釋說明,進(jìn)行加鎖操作,禁止進(jìn)行添加元素到隊(duì)列,禁止進(jìn)行從隊(duì)列里取元素,下面就慢慢分析take()方法了void?fullyLock()?{putLock.lock();takeLock.lock();}//第四步,因?yàn)榧渔i和釋放鎖是成對的,所以最后一定要記得釋放鎖哈~,即加了什么鎖,要對應(yīng)的解鎖/***?Unlocks?to?allow?both?puts?and?takes.*/void?fullyUnlock()?{takeLock.unlock();putLock.unlock();}2.6,put()方法
public?void?put(E?e)?throws?InterruptedException?{//這個(gè)隊(duì)列是不允許添加空元素的,先來個(gè)前置校驗(yàn),元素為null,則拋出NPE異常if?(e?==?null)?throw?new?NullPointerException();//?Note:?convention?in?all?put/take/etc?is?to?preset?local?var//?holding?count?negative?to?indicate?failure?unless?set.int?c?=?-1;//構(gòu)造一個(gè)節(jié)點(diǎn)node,元素為eNode<E>?node?=?new?Node<E>(e);//獲取putLock這把鎖引用final?ReentrantLock?putLock?=?this.putLock;final?AtomicInteger?count?=?this.count;putLock.lockInterruptibly();try?{//當(dāng)隊(duì)列元素個(gè)數(shù)等于隊(duì)列容量capacity時(shí),進(jìn)行等待,這里存在一個(gè)阻塞操作while?(count.get()?==?capacity)?{notFull.await();}//第二步操作,入隊(duì)列enqueue(node);//元素個(gè)數(shù)增加1,這里使用的是cas機(jī)制c?=?count.getAndIncrement();if?(c?+?1?<?capacity)//進(jìn)行信號的通知,和notify一樣notFull.signal();}?finally?{//釋放鎖操作putLock.unlock();}if?(c?==?0)signalNotEmpty();}//第二步,入隊(duì)列操作(隊(duì)列的特點(diǎn)是先進(jìn)先出)private?void?enqueue(Node<E>?node)?{//將新元素結(jié)點(diǎn)node掛在原有隊(duì)列最后一個(gè)元素的后面,然后將最后一個(gè)節(jié)點(diǎn)的引用賦值給lastlast?=?last.next?=?node;}2.7,take()方法
public?E?take()?throws?InterruptedException?{E?x;int?c?=?-1;final?AtomicInteger?count?=?this.count;//獲取takeLock鎖引用final?ReentrantLock?takeLock?=?this.takeLock;//這把鎖是可以中斷的takeLock.lockInterruptibly();try?{//若隊(duì)列元素個(gè)數(shù)為0,說明隊(duì)列里沒元素了,此時(shí)需要進(jìn)行發(fā)送一個(gè)等待的通知while?(count.get()?==?0)?{notEmpty.await();}//進(jìn)行從隊(duì)列里進(jìn)行取元素操作,見下面的第二步操作?x?=?dequeue();//隊(duì)列元素個(gè)數(shù)進(jìn)行減一操作c?=?count.getAndDecrement();if?(c?>?1)notEmpty.signal();}?finally?{//?釋放鎖takeLock.unlock();}if?(c?==?capacity)signalNotFull();return?x;}//第二步操作private?E?dequeue()?{//下面的操作就是隊(duì)首元素出來了,隊(duì)列的后面元素要前移,如果這一步不是很好理解的話,可以按照下面的方式進(jìn)行debug看下//在分析這塊時(shí),自己也有所成長,因?yàn)閐ebug是可以看到元素在數(shù)據(jù)流中如何處理的Node<E>?h?=?head;Node<E>?first?=?h.next;h.next?=?h;?//?help?GChead?=?first;//獲取隊(duì)首元素xE?x?=?first.item;//觸發(fā)gc機(jī)制進(jìn)行垃圾回收,什么是垃圾對象呢,就是不可達(dá)對象,不了解的可以看下jvm對應(yīng)的機(jī)制first.item?=?null;//返回隊(duì)列的隊(duì)首元素return?x;} image-202011221024062862.8,remove()方法
?public?boolean?remove(Object?o)?{//這個(gè)隊(duì)列里不允許添加元素為null的元素,所以這里在刪除的時(shí)候做了一下前置校驗(yàn)if?(o?==?null)?return?false;//第二步,禁止入隊(duì)列和出隊(duì)列操作,和上文的contains()方法采取的策略一致fullyLock();try?{//循環(huán)遍歷隊(duì)列的每個(gè)元素,進(jìn)行比較,這里是不是和你寫業(yè)務(wù)邏輯方法一樣,嘖嘖,有點(diǎn)意思吧//這里你就明白為什么要看源碼了,以及看源碼你能得到什么for?(Node<E>?trail?=?head,?p?=?trail.next;p?!=?null;trail?=?p,?p?=?p.next)?{//此時(shí)找到待刪除的元素oif?(o.equals(p.item))?{//進(jìn)行第三步操作unlink(p,?trail);return?true;}}return?false;}?finally?{fullyUnlock();}} //第二步,禁止入隊(duì)列,出隊(duì)列操作void?fullyLock()?{putLock.lock();takeLock.lock();} //第三步 void?unlink(Node<E>?p,?Node<E>?trail)?{//觸發(fā)gc機(jī)制,將垃圾對象進(jìn)行回收p.item?=?null;//將待刪除元素的下一個(gè)節(jié)點(diǎn)【掛載】待刪除元素的前一個(gè)元素的next后面trail.next?=?p.next;//判斷待刪除的元素是否是隊(duì)列的最后一個(gè)元素,如果是,則trail賦值給last,這里你可以想象一下鏈表的刪除操作if?(last?==?p)last?=?trail;//隊(duì)列的元素個(gè)數(shù)減一if?(count.getAndDecrement()?==?capacity)notFull.signal();}2.9,clear()方法
????/***?Atomically?removes?all?of?the?elements?from?this?queue.*?The?queue?will?be?empty?after?this?call?returns.*/public?void?clear()?{//方法上的注釋已經(jīng)說明了clear()方法的作用是干什么的了,很清晰//在clear()操作時(shí),不允許進(jìn)行put/take操作,進(jìn)行了加鎖fullyLock();try?{//循環(huán)遍歷隊(duì)列的每一個(gè)元素,將其節(jié)點(diǎn)node對應(yīng)的元素item置為null,觸發(fā)gcfor?(Node<E>?p,?h?=?head;?(p?=?h.next)?!=?null;?h?=?p)?{h.next?=?h;p.item?=?null;}//隊(duì)首隊(duì)尾相同表明隊(duì)列為空了head?=?last;//將隊(duì)列的容量capacity置為0if?(count.getAndSet(0)?==?capacity)notFull.signal();}?finally?{//記得在這里釋放鎖fullyUnlock();}}2.10, toArray()方法
?public?Object[]?toArray()?{//禁止put/take操作,所以進(jìn)行加鎖,看下面的第二步含義fullyLock();try?{//獲取隊(duì)列元素個(gè)數(shù)sizeint?size?=?count.get();//創(chuàng)建大小為size的object數(shù)組,之所以為Object類型是因?yàn)镺bject對象的范圍最大,什么類型都可以裝下Object[]?a?=?new?Object[size];int?k?=?0;//循環(huán)遍歷隊(duì)列的每一個(gè)元素,將其裝入到數(shù)組object里面for?(Node<E>?p?=?head.next;?p?!=?null;?p?=?p.next)a[k++]?=?p.item;return?a;}?finally?{//最后進(jìn)行釋放對應(yīng)的鎖,其實(shí)這里你也可以學(xué)到很多東西的,比如說加鎖,解鎖操作,為啥要放到finally語句塊呢等等等//都會潛移默化的指導(dǎo)你編碼的過程fullyUnlock();}} //第二步,注釋已經(jīng)很好的說明了這個(gè)方法的含義,就是阻止put和take操作的,所以進(jìn)行獲取對應(yīng)的鎖進(jìn)行加鎖操作/***?Locks?to?prevent?both?puts?and?takes.*/void?fullyLock()?{putLock.lock();takeLock.lock();}2.11,方法總結(jié)
這里自己沒有對隊(duì)列的poll()方法,offer()方法進(jìn)行分析,是因?yàn)樗蛅ake(),add()方法的分析過程都太一樣了,至于一點(diǎn)點(diǎn)差別,你可以去自己看下源碼哈,這里由于篇幅的問題就不過多介紹了,其實(shí)整個(gè)方法的分析就是基于鏈表和數(shù)組的操作。不過這里沒有在方法的分析過程中去寫時(shí)間復(fù)雜度,不過,你看過vector源碼之后就知道如何分析了。
三,總結(jié)一下
3.1,思考一下
文章的開頭拋出了下面的3個(gè)問題
1.1,為什么要分析源碼? 那我這里在問下,你分析完這個(gè)集合源碼,學(xué)習(xí)到了什么?是不是有點(diǎn)啟發(fā)?
1.2,分析源碼的好處是? 在分析的過程中,你學(xué)會了如何更加去編碼,或許你沒有意識到,但這是潛移默化的過程,相信你學(xué)習(xí)到了
1.3,如何分析源碼? 在整個(gè)文章的閱讀過程中,想必你已經(jīng)學(xué)會了如何分析源碼了,從哪入手,這也是一種潛移默化的過程
有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)
歡迎大家關(guān)注Java之道公眾號
好文章,我在看??
總結(jié)
以上是生活随笔為你收集整理的面试官问我平时怎么看源码的,我把这篇文章甩给他了。的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: uva 12508 - Triangle
- 下一篇: Oracle建立约束、删除约束