日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

高手不得不知的Java集合List的细节

發布時間:2025/3/18 java 23 豆豆
生活随笔 收集整理的這篇文章主要介紹了 高手不得不知的Java集合List的细节 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

寫在前面

作為Android開發者,Java集合可能是開發中最常使用的類之一了。但很多人可能跟我一樣,對Java集合只停留在“使用”的層面上,而對其的實現、原理如何只是略知一二,所以有時可能忽略了一些小細節。這些細節可能對項目的整體性能影響不大,但我覺得,要成為一個好的程序員,必須要精益求精,對代碼性能“錙銖必較”。

舉個例子,各位在創建ArrayList實例時有沒有想過到底要不要指定其初始容量?指定了會怎樣?不指定又會怎樣?如果你跟博主我有同樣的困惑,那么本文一定能給你個滿意的答案!

正文

這篇文章是關于Java集合之一的List的,但不妨先祭上一張經典的Java集合框架圖,先大概了解下Java集合整體的框架:

如果之前沒見過圖2.1的童鞋緊張了,這么多類呀!別慌,圖2.1很多是接口和抽象類,并且我們常使用的集合類也就那么幾個,我們只關心我們經常使用的即可,不常用的就暫時忽略,等用到了再看就行了。

好了,上面關于Java集合List的類不多,我整理了下:

從圖2.2可以看到,我們經常使用的Arrayist、LinkedList繼承的關系挺復雜的,但繼承的都是接口或抽象類。而Collection和List是接口,Collection接口定義了集合的通用方法,和List接口是在Collection基礎上補充了專屬于List的通用方法。我們什么時候使用抽象類?很多情況是為子類提供共同的方法實現或屬性時會使用抽象類。所以就不難理解AbstractColection和AbstractList的作用了,當然,你也可以繼承于它們實現自己的List,而這是題外話了,這里就不加討論了,下面我們進入正題吧。

本文將介紹下面List子類的一些細節:

  • ArrayList
  • Vector和Stack
  • LinkedList
  • SynchronizedList

ArrayLIst的細節

細節1:ArrayList基于數組實現,訪問元素效率快,插入刪除元素效率慢

ArrayList是基于數組實現的,這個似乎不是什么秘密了,但為了文章的完整性,還是要介紹下。ArrayList內部維護一個數組elementData,用于保存列表元素,基于數組的數組這數據結構,我們知道,其索引元素是非常快的:

public E get(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));return (E) elementData[index]; // 索引無需遍歷,效率非常高! } 復制代碼public E set(int index, E element) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));E oldValue = (E) elementData[index];elementData[index] = element; // 索引無需遍歷,效率非常高!return oldValue; } 復制代碼

可以看到,get、set直接根據索引獲取了目標元素,中間不用做任何的遍歷操作,效率是非常快的。但是對于插入和刪除操作效率就不太理想了:

public void add(int index, E element) {if (index > size || index < 0)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));ensureCapacityInternal(size + 1); // 先判斷是否需要擴容System.arraycopy(elementData, index, elementData, index + 1, // 把index后面的元素都向后偏移一位size - index);elementData[index] = element;size++; } 復制代碼

從插入操作的源碼可以看到,插入前,要先判斷是否需要擴容(擴容后面會講,這里先跳過),然后把Index后面的元素都偏移一位,這里的偏移是需要把元素復制后,再賦值當前元素的后一索引的位置。顯然,這樣一來,插入一個元素,牽連到多個元素,效率自然就低了。再來看看刪除操作:

public E remove(int index) {if (index >= size)throw new IndexOutOfBoundsException(outOfBoundsMsg(index));modCount++;E oldValue = (E) elementData[index];int numMoved = size - index - 1;if (numMoved > 0) {// 把index后面的元素向前偏移一位,填補刪除的元素System.arraycopy(elementData, index + 1, elementData, index,numMoved);}elementData[--size] = null; // clear to let GC do its workreturn oldValue; } 復制代碼

同樣,刪除一個元素,需要把index后面的元素向前偏移一位,填補刪除的元素,也是牽連了多個元素。所以大家在使用時要謹慎了!

細節2:ArrayList支持快速隨機訪問

什么是隨機訪問?我們不防先來看看ArrayList的類定義:

public class ArrayList<E> extends AbstractList<E>implements List<E>, RandomAccess, Cloneable, java.io.Serializable 復制代碼

看到RandomAccess了嗎,這個就是支持快速隨機訪問的標記,我們再點進去看看其源碼:

/*** ...* <p>It is recognized that the distinction between random and sequential* access is often fuzzy. For example, some <tt>List</tt> implementations* provide asymptotically linear access times if they get huge, but constant* access times in practice. Such a <tt>List</tt> implementation* should generally implement this interface. As a rule of thumb, a* <tt>List</tt> implementation should implement this interface if,* for typical instances of the class, this loop:* <pre>* for (int i=0, n=list.size(); i &lt; n; i++)* list.get(i);* </pre>* runs faster than this loop:* <pre>* for (Iterator i=list.iterator(); i.hasNext(); )* i.next();* </pre>* ...*/ public interface RandomAccess { } 復制代碼

額,是一個接口,沒有任何的屬性或方法定義。其實它只是一個標記,繼承于它就相當于告訴別人,我支持快速隨機訪問,上面代碼我特意留下部分的注釋說明,其中關鍵的部分在說,通常情況下,使用索引訪問的效率比使用迭代器訪問的效率快!

我們把目光暫時轉移到Collections類下,其中有很多基于是否有繼承于RandomAccess的List做不同的算法選擇判斷,我們來看其中的二分查找算法:

public static <T> int binarySearch(List<? extends Comparable<? super T>> list, T key) {if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)// 當List實現了RandomAccess或小于一定閥值時,使用索引二分查找算法return Collections.indexedBinarySearch(list, key);elsereturn Collections.iteratorBinarySearch(list, key); } 復制代碼

所以快速隨機訪問是針對于Collections中的方法而言的(其他類是否也有?歡迎大神們補充),支持快速隨機訪問時,就選擇索引訪問,效率會很快。

另外,從上面的二分查找算法我們又能得到一個提高效率的小細節:我們知道List是提供了IndexOf和lastIndexOf方法來檢索元素的,它們分別是從頭和尾開始,一個一個比較的,那么顯然,使用Collections#binarySearch在大多數情況效率會比 IndexOf和lastIndexOf更快~

細節3:大多數情況下,我們都應該指定ArrayList的初始容量

如果說上面所介紹的細節大部分童鞋都知道,那這個細節相信很多人都不知道,包括在看源碼之前的我。在講為什么之前,我們需要先來了解ArrayList的擴容機制。

ArrayList每次擴容至少為原來容量大小的1.5倍,其默認容量是10,當你不為其指定初始容量時,它就會創建默認容量大小為10的數組:

// 默認最小容量 private static final int DEFAULT_CAPACITY = 10;// 空數組 private static final Object[] EMPTY_ELEMENTDATA = {};// 默認容量空數組,可以理解為一個標記 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};// 指定最小容量創建列表 public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);} }// 創建默認空列表 public ArrayList() {this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; // 默認容量空數組 } 復制代碼

我們經常使用ArrayList的默認構造函數來創建實例,等等,不是說不指定初始容量會創建默認容量大小為10的數組嗎?但這里只賦值了空數組。是的,還記得我們上面分析的add源碼有個擴容操作嗎?如果使用默認構造函數來創建實例,在第一次添加元素時,就會進行擴容,擴容到默認容量10的數組

// 每次添加元素都會調用 private void ensureCapacityInternal(int minCapacity) {if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {// 如果為默認容量空數組的話,添加元素時,至少擴容到默認最小容量minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}ensureExplicitCapacity(minCapacity); }private void ensureExplicitCapacity(int minCapacity) {modCount++;// overflow-conscious codeif (minCapacity - elementData.length > 0) // 大于當前容量就擴容grow(minCapacity); }// 擴容 private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); // 1.5倍原來大小// 先嘗試擴容到1.5倍原來容量的大小,如果比用戶指定的大,那么就擴容1.5倍// 否則擴容用戶指定的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); } 復制代碼

所謂“擴容”就是創建一個長度更大的數組,再把舊數組的元素全部賦值到新數組。顯然,這個操作效率也是不理想的。雖然使用默認構造函數創建的實例,在第一次添加元素的擴容并沒有元素復制,但還是要另外創建一個數組,并且是大小為10的數組,可能你并不需要這么大的數組,可能是3,可能是5,那么我們為何不一開始就指定其容量呢?

指定初始容量的方法也很簡單,我們使用帶int參數的構造函數就可以了:

// 指定最小容量創建列表 public ArrayList(int initialCapacity) {if (initialCapacity > 0) {this.elementData = new Object[initialCapacity];} else if (initialCapacity == 0) {this.elementData = EMPTY_ELEMENTDATA;} else {throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);} } 復制代碼

或者有童鞋會說,使用ensureCapacity指定容量也行,其實不然,為何ensureCapacity對容量大小有限制:

// 指定最小容量 public void ensureCapacity(int minCapacity) {int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)// any size if not default element table? 0// larger than default for default empty table. It's already// supposed to be at default size.: DEFAULT_CAPACITY;// 指定最小容量成功的情況// 1.使用 new ArrayList() 創建實例并添加元素前,指定容量大小不能小于默認容量10// 2.列表已存在元素,指定容量大小不能小于當前容量大小if (minCapacity > minExpand) {ensureExplicitCapacity(minCapacity);} } 復制代碼

所以講到這,相信大家有答案了,為什么創建ArrayList要指定其初始容量?顯然我們是不希望它進行耗時的擴容操作,并且能在我們預知的情況下盡量使用大小剛剛好的列表,而不浪費任何資源。 那么我們可以得到以下經驗:

  • 都不應該使用默認構造函數創建實例,以免自動擴容到默認最小容量(10)
  • 當列表容量確定,應該指定容量的方式創建實例
  • 當列表容量不確定時,可以預估我們將有會多少元素,指定稍大于預估值的容量

Vector和Stack的細節

Vector和Stack我們幾乎是不使用的了,所以并不打算用大篇幅來介紹,我們大概了解下就可以了。但我們可以探索下他們為何不受待見,從而引以為戒。

細節1:Vector也是基于數組實現,同樣支持快速訪問,并且線程安全

因為跟ArrayList一樣,都是基于數組實現,所以ArrayList具有的優勢和劣勢Vector同樣也有,只是Vector在每個方法都加了同步鎖,所以它是線程安全的。但我們知道,同步會大大影響效率的,所以在不需要同步的情況下,Vector的效率就不如ArrayList了。所以我們在不需要同步的情況下,優先選擇ArrayList;而在需要同步的情況下,也不是使用Vector,而是使用SynchronizedList(后面講到)。你看,Vector處于一個很尷尬的地步。但我個人覺得,Vector被遺棄的最大原因不在于它線程同步影響效率——因為這畢竟能在多線程環境下使用——而在于它的擴容機制上。

細節2:Vector的擴容機制不完善

Vector默認容量也是10,跟ArrayList不同的是,Vector每次擴容的大小是可以指定的,如果不指定,每次擴容原來容量大小的2倍:

protected Object[] elementData; // 元素數組protected int elementCount; // 元素數量protected int capacityIncrement; // 擴容大小public Vector(int initialCapacity, int capacityIncrement) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);this.elementData = new Object[initialCapacity];this.capacityIncrement = capacityIncrement; }public Vector(int initialCapacity) {this(initialCapacity, 0); // 默認擴容大小為0,那么擴容時會增大兩倍 }public Vector() {this(10); // 默認容量為10 }public synchronized void ensureCapacity(int minCapacity) {if (minCapacity > 0) {modCount++;ensureCapacityHelper(minCapacity);} }private void ensureCapacityHelper(int minCapacity) {// overflow-conscious codeif (minCapacity - elementData.length > 0) // 大于當前容量就擴容grow(minCapacity); }private void grow(int minCapacity) {// overflow-conscious codeint oldCapacity = elementData.length;int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity); // 默認擴容兩倍if (newCapacity - minCapacity < 0)newCapacity = minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);elementData = Arrays.copyOf(elementData, newCapacity); } 復制代碼

另外需要提醒注意的是,不像ArrayList,如果是用Vector的默認構造函數創建實例,那么第一次添加元素就需要擴容,但不會擴容到默認容量10,只會根據用戶指定或兩倍的大小擴容。所以使用Vector時指不指定擴容大小都很尷尬:

  • 如果容量大小和擴容大小都不指定,開始可能會頻繁地進行擴容
  • 如果指定了容量大小不指定擴容大小,以2倍的大小擴容會浪費很多資源
  • 如果指定了擴容大小,擴容大小就固定了,不管數組多大,都按這大小來擴容,那么這個擴容大小的取值總有不理想的時候

從Vector我們也可以反觀ArrayList設計巧妙的地方,這也許是Vector存在的唯一價值了哈哈。

細節3:Stack繼承于Vector,在其基礎上擴展了棧的方法

Stack我們也不使用了,它只是添加多幾個棧常用的方法(這個LinkedList也有,后面討論),簡單來看下它們的實現吧:

// 進棧 public E push(E item) {addElement(item);return item; }// 出棧 public synchronized E pop() {E obj;int len = size();obj = peek();removeElementAt(len - 1);return obj; }public synchronized E peek() {int len = size();if (len == 0)throw new EmptyStackException();return elementAt(len - 1); } 復制代碼

LinkedList的細節

再來看看我們熟悉的LinkedList的細節~

細節1:LinkedList基于鏈表實現,插入刪除元素效率快,訪問元素效率慢

LinkedList內部維護一個雙端鏈表,可以從頭開始檢索,也可以從尾開始檢索。同樣的,得益于鏈表這一數據結構,LinkedList在插入和刪除元素效率非常快。

插入元素只需新建一個node,再把前后指針指向對應的前后元素即可:

// 鏈尾追加 void linkLast(E e) {final Node<E> l = last;final Node<E> newNode = new Node<>(l, e, null);last = newNode;if (l == null)first = newNode;elsel.next = newNode;size++;modCount++; }// 指定節點前插入 void linkBefore(E e, Node<E> succ) {// assert succ != null;// 插入節點,succ為Index的節點,可以看到,是插入到index節點的前一個節點final Node<E> pred = succ.prev;final Node<E> newNode = new Node<>(pred, e, succ);succ.prev = newNode;if (pred == null)first = newNode;elsepred.next = newNode;size++;modCount++; }public void add(int index, E element) {checkPositionIndex(index);if (index == size)linkLast(element);elselinkBefore(element, node(index)); } 復制代碼

同樣,刪除元素只要把刪除節點的鏈剪掉,再把前后節點連起來就搞定了:

E unlink(Node<E> x) {// assert x != null;final E element = x.item;final Node<E> next = x.next;final Node<E> prev = x.prev;if (prev == null) {// 鏈頭first = next;} else {prev.next = next;x.prev = null;}if (next == null) {// 鏈尾last = prev;} else {next.prev = prev;x.next = null;}x.item = null;size--;modCount++;return element; }public E remove(int index) {checkElementIndex(index);return unlink(node(index)); } 復制代碼

但由于鏈表我們只知道頭和尾,中間的元素要遍歷獲取的,所以導致了訪問元素時,效率就不好了:

Node<E> node(int index) {// 使用了二分法if (index < (size >> 1)) { // 如果索引小于二分之一,從first開始遍歷Node<E> x = first;for (int i = 0; i < index; i++)x = x.next;return x;} else { // 如果索引大于二分之一,從last開始遍歷Node<E> x = last;for (int i = size - 1; i > index; i--)x = x.prev;return x;} }public E get(int index) {checkElementIndex(index);return node(index).item; } 復制代碼

所以,LinkedList和ArrayList剛好是互補的,所以具體場景,應考慮哪種操作最頻繁,從而選擇不同的List來使用。

細節2:LinkedList可以當作隊列和棧來使用

不知大家有沒注意到在圖2.2中,LinkedList非常“特立獨行地”繼承了Deque接口,而Deque又繼承于Queue接口,這隊列和棧的方法定義就是在這些接口中定義的,而LinkedList實現其方法,使自身具備了隊列的棧的功能。 當作隊列(先進先出)使用:

// 進隊 public boolean offerFirst(E e) {addFirst(e);return true; }// 出隊 public E pollLast() {final Node<E> l = last;return (l == null) ? null : unlinkLast(l); } 復制代碼

當作棧(后進又出)來使用:

// 進棧 public void push(E e) {addFirst(e); }// 出棧,如果為空列表,會拋出異常 public E pop() {return removeFirst(); } 復制代碼

SynchronizedList的細節

在Collections類中提供了很多線程線程的集合類,其實他們實現很簡單,只是在集合操作前,加一個鎖而已。

細節1:SynchronizedList繼承于SynchronizedCollection,使用裝飾者模式,為原來的List加上鎖,從而使List同步安全

先來看下SynchronizedCollection的定義:

static class SynchronizedCollection<E> implements Collection<E>, Serializable {private static final long serialVersionUID = 3053995032091335093L;final Collection<E> c; // 裝飾的集合final Object mutex; // 鎖SynchronizedCollection(Collection<E> c) {this.c = Objects.requireNonNull(c);mutex = this;}SynchronizedCollection(Collection<E> c, Object mutex) {this.c = Objects.requireNonNull(c);this.mutex = Objects.requireNonNull(mutex);} } 復制代碼

可以看到,可以指定一個對象作為鎖,如果不指定,默認就鎖了集合了。 再來看下我們關注的SynchronizedList:

static class SynchronizedList<E>extends SynchronizedCollection<E>implements List<E> {final List<E> list;SynchronizedList(List<E> list) {super(list);this.list = list;}SynchronizedList(List<E> list, Object mutex) {super(list, mutex);this.list = list;}...public E get(int index) {synchronized (mutex) {return list.get(index);}}public E set(int index, E element) {synchronized (mutex) {return list.set(index, element);}}public void add(int index, E element) {synchronized (mutex) {list.add(index, element);}}public E remove(int index) {synchronized (mutex) {return list.remove(index);}}... } 復制代碼

想不到SynchronizedList的實現是如此簡單,上面的源碼想必不用我多說了。

寫在最后

關于我們經常使用的List的細節到此就介紹完了,如果上面我有言論有誤或不嚴謹的,歡迎大家指正;如果有另外一些細節我沒談及到的,也歡迎大神們補充。

最后,我們來做一次總結:

  • ArrayList和LinkedList適用于不同使用場景,應根據具體場景從優選擇
  • 根據ArrayList的擴容機制,我們應該開始就指定其初始容量,避免資源浪費
  • LinkedList可以當作隊列和棧使用,當然我們也可以進一步封裝
  • 盡量不使用Vector和Stack,同步場景下,使用SynchronizedList替代

總結

以上是生活随笔為你收集整理的高手不得不知的Java集合List的细节的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

主站蜘蛛池模板: 91爱爱爱| 三级精品视频 | 五月婷婷操 | 99热97| 亚洲高清在线视频 | www.欧美激情| 国精品无码一区二区三区 | 2024国产精品 | 秋霞欧美一区二区三区视频免费 | 尤物videos另类xxxx | 久久亚洲无码视频 | 日本少妇性生活 | 人妻少妇精品视频一区二区三区 | 中文字幕在线观看日韩 | 97久久国产亚洲精品超碰热 | 久在线视频 | 免费网站在线高清观看 | 亚洲国产成人精品久久 | av免费高清| 北条麻妃久久精品 | 亚洲爽爽网 | 色综合综合网 | 亚洲AV成人无码久久 | 欧美四级在线观看 | 中文第一页 | 黄色尤物视频 | 五月激情网站 | 国产91久久精品一区二区 | 97国产精品久久 | 国产精品久久久久久影视 | 成人爽爽视频 | np视频 | 日本黄动漫 | 女女h百合无遮羞羞漫画软件 | 国产精品久久久 | 久草中文在线 | 青青草免费观看 | 羞羞动漫免费观看 | 黄色动漫在线免费观看 | 成人久久久久久久 | 亚洲自拍偷拍视频 | 在线观看黄色免费视频 | 精品人妻视频在线 | 涩涩天堂 | 99精品无码一区二区 | 中文字幕人妻丝袜二区 | av观看在线免费 | 一级香蕉视频在线观看 | 国产一级做a爱片久久毛片a | 精品123区 | 反差在线观看免费版全集完整版 | 毛片大片 | 国产日韩欧美专区 | 欧美大片免费播放器 | 黑人大群体交免费视频 | 日本不卡一二三区 | 一区二区三区在线播放视频 | 欧美综合一区 | 国产视频精品一区二区三区 | 久久综合成人网 | 变态另类ts人妖一区二区 | 国产人妻精品午夜福利免费 | 久色视频在线 | 毛片一级片 | 99自拍视频在线观看 | 四虎国产成人永久精品免费 | 在线免费观看视频 | 青娱乐在线视频观看 | 国产99精品视频 | 欧美色图888| 国产精品一区二区精品 | 欧美日韩成人免费 | 永久免费在线观看视频 | 韩日视频在线观看 | 日韩精品国产一区二区 | 99免费国产| 插入综合网 | 在线免费观看视频网站 | 亚洲交性网 | xxxxⅹxxxhd日本8hd | 男人天堂网在线观看 | 九九热在线视频观看 | 欧美cccc极品丰满hd | 久久国产精品无码一级毛片 | 黄黄视频在线观看 | 激情小说图片视频 | av天天射 | 欧美精品久久久久久久自慰 | 日本人六九视频 | 福利午夜视频 | 欧美成在线观看 | 亚洲欧美一区二区在线观看 | 国产精品第3页 | www.香蕉视频.com | 91蜜臀精品国产自偷在线 | 日本道中文字幕 | 久久综合亚洲色hezyo国产 | 欧美精品久久久久久久 | 91亚洲精品久久久久久久久久久久 |