摘要:常用集合使用場(chǎng)景分析過年前的最后一篇,本章通過介紹,,,底層實(shí)現(xiàn)原理和四個(gè)集合的區(qū)別。和都是線程安全的,不同的是前者使用類,后者使用關(guān)鍵字。面試官會(huì)認(rèn)為你是一個(gè)基礎(chǔ)扎實(shí),內(nèi)功深厚的人才到這里常用集合使用場(chǎng)景分析就結(jié)束了。
Java 常用List集合使用場(chǎng)景分析
過年前的最后一篇,本章通過介紹ArrayList,LinkedList,Vector,CopyOnWriteArrayList 底層實(shí)現(xiàn)原理和四個(gè)集合的區(qū)別。讓你清楚明白,為什么工作中會(huì)常用ArrayList和CopyOnWriteArrayList?了解底層實(shí)現(xiàn)原理,我們可以學(xué)習(xí)到很多代碼設(shè)計(jì)的思路,開闊自己的思維。本章通俗易懂,還在等什么,快來學(xué)習(xí)吧!
知識(shí)圖解:
技術(shù):ArrayList,LinkedList,Vector,CopyOnWriteArrayList
說明:本章基于jdk1.8,github上有ArrayList,LinkedList的簡(jiǎn)單源碼代碼
源碼:https://github.com/ITDragonBl...
ArrayList : 基于數(shù)組實(shí)現(xiàn)的非線程安全的集合。查詢?cè)乜欤迦耄瑒h除中間元素慢。
LinkedList : 基于鏈表實(shí)現(xiàn)的非線程安全的集合。查詢?cè)芈迦耄瑒h除中間元素快。
Vector : 基于數(shù)組實(shí)現(xiàn)的線程安全的集合。線程同步(方法被synchronized修飾),性能比ArrayList差。
CopyOnWriteArrayList : 基于數(shù)組實(shí)現(xiàn)的線程安全的寫時(shí)復(fù)制集合。線程安全(ReentrantLock加鎖),性能比Vector高,適合讀多寫少的場(chǎng)景。
ArrayList : 查詢數(shù)據(jù)快,是因?yàn)閿?shù)組可以通過下標(biāo)直接找到元素。 寫數(shù)據(jù)慢有兩個(gè)原因:一是數(shù)組復(fù)制過程需要時(shí)間,二是擴(kuò)容需要實(shí)例化新數(shù)組也需要時(shí)間。
LinkedList : 查詢數(shù)據(jù)慢,是因?yàn)殒湵硇枰?strong>遍歷每個(gè)元素直到找到為止。 寫數(shù)據(jù)快有一個(gè)原因:除了實(shí)例化對(duì)象需要時(shí)間外,只需要修改指針即可完成添加和刪除元素。
本章會(huì)通過源碼分析,驗(yàn)證上面的說法。
注:這里的塊和慢是相對(duì)的。并不是LinkedList的插入和刪除就一定比ArrayList快。明白其快慢的本質(zhì):ArrayList快在定位,慢在數(shù)組復(fù)制。LinkedList慢在定位,快在指針修改。
ArrayListArrayList 是基于動(dòng)態(tài)數(shù)組實(shí)現(xiàn)的非線程安全的集合。當(dāng)?shù)讓訑?shù)組滿的情況下還在繼續(xù)添加的元素時(shí),ArrayList則會(huì)執(zhí)行擴(kuò)容機(jī)制擴(kuò)大其數(shù)組長(zhǎng)度。ArrayList查詢速度非常快,使得它在實(shí)際開發(fā)中被廣泛使用。美中不足的是插入和刪除元素較慢,同時(shí)它并不是線程安全的。
我們可以從源碼中找到答案
// 查詢?cè)?public E get(int index) { rangeCheck(index); // 檢查是否越界 return elementData(index); } // 順序添加元素 public boolean add(E e) { ensureCapacityInternal(size + 1); // 擴(kuò)容機(jī)制 elementData[size++] = e; return true; } // 從數(shù)組中間添加元素 public void add(int index, E element) { rangeCheckForAdd(index); // 數(shù)組下標(biāo)越界檢查 ensureCapacityInternal(size + 1); // 擴(kuò)容機(jī)制 System.arraycopy(elementData, index, elementData, index + 1, size - index); // 復(fù)制數(shù)組 elementData[index] = element; // 替換元素 size++; } // 從數(shù)組中刪除元素 private void fastRemove(int index) { modCount++; int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work }
從源碼中可以得知,
ArrayList在執(zhí)行查詢操作時(shí):
第一步:先判斷下標(biāo)是否越界。
第二步:然后在直接通過下標(biāo)從數(shù)組中返回元素。
ArrayList在執(zhí)行順序添加操作時(shí):
第一步:通過擴(kuò)容機(jī)制判斷原數(shù)組是否還有空間,若沒有則重新實(shí)例化一個(gè)空間更大的新數(shù)組,把舊數(shù)組的數(shù)據(jù)拷貝到新數(shù)組中。
第二步:在新數(shù)組的最后一位元素添加值。
ArrayList在執(zhí)行中間插入操作時(shí):
第一步:先判斷下標(biāo)是否越界。
第二步:擴(kuò)容。
第三步:若插入的下標(biāo)為i,則通過復(fù)制數(shù)組的方式將i后面的所有元素,往后移一位。
第四步:新數(shù)據(jù)替換下標(biāo)為i的舊元素。
刪除也是一樣:只是數(shù)組往前移了一位,最后一個(gè)元素設(shè)置為null,等待JVM垃圾回收。
從上面的源碼分析,我們可以得到一個(gè)結(jié)論和一個(gè)疑問。
結(jié)論是:ArrayList快在下標(biāo)定位,慢在數(shù)組復(fù)制。
疑問是:能否將每次擴(kuò)容的長(zhǎng)度設(shè)置大點(diǎn),減少擴(kuò)容的次數(shù),從而提高效率?其實(shí)每次擴(kuò)容的長(zhǎng)度大小是很有講究的。若擴(kuò)容的長(zhǎng)度太大,會(huì)造成大量的閑置空間;若擴(kuò)容的長(zhǎng)度太小,會(huì)造成頻發(fā)的擴(kuò)容(數(shù)組復(fù)制),效率更低。
LinkedList 是基于雙向鏈表實(shí)現(xiàn)的非線程安全的集合,它是一個(gè)鏈表結(jié)構(gòu),不能像數(shù)組一樣隨機(jī)訪問,必須是每個(gè)元素依次遍歷直到找到元素為止。其結(jié)構(gòu)的特殊性導(dǎo)致它查詢數(shù)據(jù)慢。
我們可以從源碼中找到答案
// 查詢?cè)?public E get(int index) { checkElementIndex(index); // 檢查是否越界 return node(index).item; } Nodenode(int index) { if (index < (size >> 1)) { // 類似二分法 Node x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { Node x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } } // 插入元素 public void add(int index, E element) { checkPositionIndex(index); // 檢查是否越界 if (index == size) // 在鏈表末尾添加 linkLast(element); else // 在鏈表中間添加 linkBefore(element, node(index)); } void linkBefore(E e, Node succ) { final Node pred = succ.prev; final Node newNode = new Node<>(pred, e, succ); succ.prev = newNode; if (pred == null) first = newNode; else pred.next = newNode; size++; modCount++; }
從源碼中可以得知,
LinkedList在執(zhí)行查詢操作時(shí):
第一步:先判斷元素是靠近頭部,還是靠近尾部。
第二步:若靠近頭部,則從頭部開始依次查詢判斷。和ArrayList的elementData(index)相比當(dāng)然是慢了很多。
LinkedList在插入元素的思路:
第一步:判斷插入元素的位置是鏈表的尾部,還是中間。
第二步:若在鏈表尾部添加元素,直接將尾節(jié)點(diǎn)的下一個(gè)指針指向新增節(jié)點(diǎn)。
第三步:若在鏈表中間添加元素,先判斷插入的位置是否為首節(jié)點(diǎn),是則將首節(jié)點(diǎn)的上一個(gè)指針指向新增節(jié)點(diǎn)。否則先獲取當(dāng)前節(jié)點(diǎn)的上一個(gè)節(jié)點(diǎn)(簡(jiǎn)稱A),并將A節(jié)點(diǎn)的下一個(gè)指針指向新增節(jié)點(diǎn),然后新增節(jié)點(diǎn)的下一個(gè)指針指向當(dāng)前節(jié)點(diǎn)。
Vector 的數(shù)據(jù)結(jié)構(gòu)和使用方法與ArrayList差不多。最大的不同就是Vector是線程安全的。從下面的源碼可以看出,幾乎所有的對(duì)數(shù)據(jù)操作的方法都被synchronized關(guān)鍵字修飾。synchronized是線程同步的,當(dāng)一個(gè)線程已經(jīng)獲得Vector對(duì)象的鎖時(shí),其他線程必須等待直到該鎖被釋放。從這里就可以得知Vector的性能要比ArrayList低。
若想要一個(gè)高性能,又是線程安全的ArrayList,可以使用Collections.synchronizedList(list);方法或者使用CopyOnWriteArrayList集合
public synchronized E get(int index) { if (index >= elementCount) throw new ArrayIndexOutOfBoundsException(index); return elementData(index); } public synchronized boolean add(E e) { modCount++; ensureCapacityHelper(elementCount + 1); elementData[elementCount++] = e; return true; } public synchronized boolean removeElement(Object obj) { modCount++; int i = indexOf(obj); if (i >= 0) { removeElementAt(i); return true; } return false; }CopyOnWriteArrayList
在這里我們先簡(jiǎn)單了解一下CopyOnWrite容器。它是一個(gè)寫時(shí)復(fù)制的容器。當(dāng)我們往一個(gè)容器添加元素的時(shí)候,不是直接往當(dāng)前容器添加,而是先將當(dāng)前容器進(jìn)行copy一份,復(fù)制出一個(gè)新的容器,然后對(duì)新容器里面操作元素,最后將原容器的引用指向新的容器。所以CopyOnWrite容器是一種讀寫分離的思想,讀和寫不同的容器。
應(yīng)用場(chǎng)景:適合高并發(fā)的讀操作(讀多寫少)。若寫的操作非常多,會(huì)頻繁復(fù)制容器,從而影響性能。
CopyOnWriteArrayList 寫時(shí)復(fù)制的集合,在執(zhí)行寫操作(如:add,set,remove等)時(shí),都會(huì)將原數(shù)組拷貝一份,然后在新數(shù)組上做修改操作。最后集合的引用指向新數(shù)組。
CopyOnWriteArrayList 和Vector都是線程安全的,不同的是:前者使用ReentrantLock類,后者使用synchronized關(guān)鍵字。ReentrantLock提供了更多的鎖投票機(jī)制,在鎖競(jìng)爭(zhēng)的情況下能表現(xiàn)更佳的性能。就是它讓JVM能更快的調(diào)度線程,才有更多的時(shí)間去執(zhí)行線程。這就是為什么CopyOnWriteArrayList的性能在大并發(fā)量的情況下優(yōu)于Vector的原因。
private E get(Object[] a, int index) { return (E) a[index]; } public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } } private boolean remove(Object o, Object[] snapshot, int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); int len = current.length; ...... Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, newElements, index, len - index - 1); setArray(newElements); return true; } finally { lock.unlock(); } }總結(jié)
看到這里,如果面試官問你ArrayList和LinkedList有什么區(qū)別時(shí)
如果你回答:ArrayList查詢快,寫數(shù)據(jù)慢;LinkedList查詢慢,寫數(shù)據(jù)快。面試官只能算你勉強(qiáng)合格。
如果你回答:ArrayList查詢快是因?yàn)榈讓邮怯蓴?shù)組實(shí)現(xiàn),通過下標(biāo)定位數(shù)據(jù)快。寫數(shù)據(jù)慢是因?yàn)閺?fù)制數(shù)組耗時(shí)。LinkedList底層是雙向鏈表,查詢數(shù)據(jù)依次遍歷慢。寫數(shù)據(jù)只需修改指針引用。
如果你繼續(xù)回答:ArrayList和LinkedList都不是線程安全的,小并發(fā)量的情況下可以使用Vector,若并發(fā)量很多,且讀多寫少可以考慮使用CopyOnWriteArrayList。
因?yàn)镃opyOnWriteArrayList底層使用ReentrantLock鎖,比使用synchronized關(guān)鍵字的Vector能更好的處理鎖競(jìng)爭(zhēng)的問題。
面試官會(huì)認(rèn)為你是一個(gè)基礎(chǔ)扎實(shí),內(nèi)功深厚的人才!!!
到這里Java 常用List集合使用場(chǎng)景分析就結(jié)束了。過年前的最后一篇博客,有點(diǎn)浮躁,可能身在職場(chǎng),心在老家!最后祝大家新年快樂!!!狗年吉祥!!!大富大貴!!!可能都沒人看博客了 ⊙﹏⊙‖∣ 哈哈哈哈(?ω?)hiahiahia
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70966.html
摘要:近段時(shí)間在準(zhǔn)備實(shí)習(xí)的面試,在網(wǎng)上看到一份面試題,就慢慢試著做,爭(zhēng)取每天積累一點(diǎn)點(diǎn)。現(xiàn)在每天給自己在面試題編寫的任務(wù)是題,有時(shí)候忙起來可能就沒有時(shí)間寫了,但是爭(zhēng)取日更,即使當(dāng)天沒更也會(huì)在之后的更新補(bǔ)上。 ????近段時(shí)間在準(zhǔn)備實(shí)習(xí)的面試,在網(wǎng)上看到一份面試題,就慢慢試著做,爭(zhēng)取每天積累一點(diǎn)點(diǎn)。????暫時(shí)手頭上的面試題只有一份,題量還是挺大的,有208題,所以可能講的不是很詳細(xì),只是我自...
摘要:原文地址游客前言金三銀四,很多同學(xué)心里大概都準(zhǔn)備著年后找工作或者跳槽。最近有很多同學(xué)都在交流群里求大廠面試題。 最近整理了一波面試題,包括安卓JAVA方面的,目前大廠還是以安卓源碼,算法,以及數(shù)據(jù)結(jié)構(gòu)為主,有一些中小型公司也會(huì)問到混合開發(fā)的知識(shí),至于我為什么傾向于混合開發(fā),我的一句話就是走上編程之路,將來你要學(xué)不僅僅是這些,豐富自己方能與世接軌,做好全棧的裝備。 原文地址:游客kutd...
摘要:哪吒社區(qū)技能樹打卡打卡貼函數(shù)式接口簡(jiǎn)介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無意間聽到領(lǐng)導(dǎo)們的談話,現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡(jiǎn)而言之,不缺干 ? 哪吒社區(qū)Java技能樹打卡?【打卡貼 day2...
閱讀 3447·2023-04-26 01:45
閱讀 2222·2021-11-23 09:51
閱讀 3638·2021-10-18 13:29
閱讀 3428·2021-09-07 10:12
閱讀 698·2021-08-27 16:24
閱讀 1765·2019-08-30 15:44
閱讀 2192·2019-08-30 15:43
閱讀 2944·2019-08-30 13:11