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