詳解Java集合類之List篇
1.集合框架體系
集合框架被設(shè)計成要滿足以下幾個目標(biāo)。
- 該框架必須是高性能的?;炯希▌討B(tài)數(shù)組,鏈表,樹,哈希表)的實現(xiàn)也必須是高效的。
- 該框架允許不同類型的集合,以類似的方式工作,具有高度的互操作性。
- 對一個集合的擴展和適應(yīng)必須是簡單的。
為此,整個集合框架就圍繞一組標(biāo)準(zhǔn)接口而設(shè)計。你可以直接使用這些接口的標(biāo)準(zhǔn)實現(xiàn),諸如: LinkedList, HashSet, 和 TreeSet 等,除此之外你也可以通過這些接口實現(xiàn)自己的集合。
Collection接口的實現(xiàn)子類
Map接口的實現(xiàn)子類
體系圖
從上面的集合框架圖可以看到,Java 集合框架主要包括兩種類型的容器,一種是集合(Collection),存儲一個元素集合,另一種是圖(Map),存儲鍵/值對映射。Collection 接口又有 3 種子類型,List、Set 和 Queue,再下面是一些抽象類,最后是具體實現(xiàn)類,常用的有 ArrayList、LinkedList、HashSet、LinkedHashSet、HashMap、LinkedHashMap 等等。
2.Collection接口
該接口定義:
public interface Collection<E> extends Iterable<E>
Collection 是最基本的集合接口,一個 Collection 代表一組 Object,即 Collection 的元素, Java不提供直接繼承自Collection的類,只提供繼承于的子接口(如List和set)。
Collection 接口存儲一組不唯一,無序的對象。
Collection接口的常用方法:
import java.util.ArrayList; import java.util.List; /** * Collection接口的實現(xiàn)類 * Java不提供直接繼承自Collection的類,只提供繼承于的子接口 * 所以我們以ArrayList為例子演示Collection接口的抽象方法 */ public class CollectionTest { @SuppressWarnings({"all"}) public static void main(String[] args) { List list = new ArrayList(); // 添加 list.add("dahe"); list.add(521); list.add(true); System.out.println(list); // 刪除 // 刪除第一個元素 list.remove(0); // 刪除指定的元素 list.remove(true); System.out.println(list); // 查找 System.out.println(list.contains(521)); // 元素個數(shù) System.out.println(list.size()); // 判空 System.out.println(list.isEmpty()); // 清空 list.clear(); // 添加多個元素(可以添加另一個實現(xiàn)了Collection接口的對象) ArrayList arrayList = new ArrayList(); arrayList.add("tiktok直播"); arrayList.add("tiktok廣告投放"); list.addAll(arrayList); System.out.println(list); // 查找多個元素是否都存在 System.out.println(list.containsAll(arrayList)); // 刪除多個元素 list.removeAll(arrayList); System.out.println(list); } }
輸出:
[dahe, 521, true]
[521]
true
1
false
[tiktok直播, tiktok廣告投放]
true
[]
3.迭代器
Java Iterator(迭代器)不是一個集合,它是一種用于訪問集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。
Iterator 是 Java 迭代器最簡單的實現(xiàn),ListIterator 是 Collection API 中的接口, 它擴展了 Iterator 接口。
迭代器 it 的兩個基本操作是 next 、hasNext 和 remove。
- 調(diào)用 it.next() 會返回迭代器的下一個元素,并且更新迭代器的狀態(tài)。
- 調(diào)用 it.hasNext() 用于檢測集合中是否還有元素。
- 調(diào)用 it.remove() 將迭代器返回的元素刪除。
代碼DEMO示例:
import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; /** * 迭代器 */ public class IteratorTest { @SuppressWarnings({"all"}) public static void main(String[] args) { Collection col = new ArrayList(); col.add(new Book("三國演義","羅貫中",52.7)); col.add(new Book("小李飛刀","古龍",10.2)); col.add(new Book("紅樓夢","曹雪芹",34.6)); System.out.println(col); // 使用迭代器 Iterator iterator = col.iterator(); while (iterator.hasNext()) { // 返回下一個元素,類型是Object Object obj = iterator.next(); System.out.println(obj); } } } class Book { private String name; private String author; private Double price; public Book(String name, String author, Double price) { this.name = name; this.author = author; this.price = price; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public Double getPrice() { return price; } public void setPrice(Double price) { this.price = price; } @Override public String toString() { return "Book{" + "name='" + name + '\'' + ", author='" + author + '\'' + ", price=" + price + '}'; } }
輸出:
[Book{name='三國演義', author='羅貫中', price=52.7}, Book{name='小李飛刀', author='古龍', price=10.2}, Book{name='紅樓夢', author='曹雪芹', price=34.6}]
Book{name='三國演義', author='羅貫中', price=52.7}
Book{name='小李飛刀', author='古龍', price=10.2}
Book{name='紅樓夢', author='曹雪芹', price=34.6}
我們還可以使用增強型for循環(huán)來遍歷集合:(底層依然是迭代器,可以理解為簡化版的迭代器遍歷)
for (Object o : col) { System.out.println(o); }
4.List接口
List接口是Collection接口的子接口
List集合中元素有序(添加順序和取出順序一致),元素可以重復(fù)
List中的每個元素都有其順序的索引
常用方法代碼示例:
/** * List接口,Collection接口的子接口 */ import java.util.ArrayList; import java.util.List; public class ListTest { @SuppressWarnings({"all"}) public static void main(String[] args) { List list = new ArrayList(); List list1 = new ArrayList(); list1.add("ceshi"); list1.add("tangseng"); // 添加元素 list.add("dahe"); list.add("tom"); list.add("dahe"); // 可以重復(fù) // 指定位置插入元素 list.add(1,"qian"); // 加入多個元素,傳入一個Collection對象 list.addAll(1,list1); System.out.println(list); // 取出指定索引的元素 System.out.println(list.get(0)); // 查找元素第一次出現(xiàn)的位置 System.out.println(list.indexOf("dahe")); // 查找元素最后一次出現(xiàn)的位置 System.out.println(list.lastIndexOf("dahe")); // 刪除元素 list.remove(1); System.out.println(list); // 元素替換 list.set(1,"marry"); System.out.println(list); // 返回子集合,0到1的元素集合 List returnList = list.subList(0,2); System.out.println(returnList); } }
輸出:
[dahe, ceshi, tangseng, qian, tom, dahe]
dahe
0
5
[dahe, tangseng, qian, tom, dahe]
[dahe, marry, qian, tom, dahe]
[dahe, marry]
5.ArrayList
ArrayList是由數(shù)組來進(jìn)行數(shù)據(jù)存儲的,為線程不安全,效率較高多線程場景建議使用vector
例如ArrayList add方法的源碼:(并沒有synchronized進(jìn)行修飾)
public boolean add(E e) { modCount++; add(e, elementData, size); return true; }
ArrayList擴容機制
使用無參構(gòu)造器
當(dāng)我們使用ArrayList的無參構(gòu)造器的時候,進(jìn)入源碼分析:
public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
這里的elementData是ArrayList存放數(shù)據(jù)的數(shù)組,可以看出當(dāng)我們調(diào)用無參構(gòu)造的時候,數(shù)組初始化為DEFAULTCAPACITY_EMPTY_ELEMENTDATA,那這一長串代表什么呢?
繼續(xù)步入
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
1
該值為空,那么我們可以得出結(jié)論,當(dāng)調(diào)用ArrayList的無參構(gòu)造時,存儲數(shù)組的默認(rèn)空間大小為0,也就是空
同時,我們追入elementData數(shù)組,可以發(fā)現(xiàn)他是Object數(shù)組,這也就解釋了為什么ArrayList可以加入任意類型的元素,因為Object是萬物之父!
transient Object[] elementData; // non-private to simplify nested class access
添加數(shù)據(jù)之前的準(zhǔn)備
接下來,當(dāng)我們使用add方法,比如操作下面這行代碼:
arrayList.add(521);
首先,ArrayList會對521進(jìn)行裝箱操作:
@IntrinsicCandidate public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
繼續(xù)步入,進(jìn)入如下代碼:
public boolean add(E e) { modCount++; add(e, elementData, size); return true; }
我們來仔細(xì)看一下這段代碼,首先:
modCount++;
在所有的集合實現(xiàn)類中(Collection與Map中),都會有一個 modCount 的變量出現(xiàn),它的作用就是記錄當(dāng)前集合被修改的次數(shù)。此處將修改次數(shù) + 1,防止多線程操作異常
隨后,進(jìn)入真正添加數(shù)據(jù)的add重載方法:
add(e, elementData, size);
開始添加數(shù)據(jù)
在真正添加數(shù)據(jù)的部分,代碼如下:
可以看到,首先需要判斷elementData數(shù)組的容量是否充足,如果容量已經(jīng)滿了的話,就執(zhí)行g(shù)row方法進(jìn)行擴容,否則就加入數(shù)據(jù),size + 1
private void add(E e, Object[] elementData, int s) { if (s == elementData.length) elementData = grow(); elementData[s] = e; size = s + 1; }
那他是怎么進(jìn)行擴容的呢?
我們進(jìn)入grow方法看一下:
private Object[] grow() { return grow(size + 1); }
繼續(xù)步入到grow的重載方法:
private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { int newCapacity = ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, /* minimum growth */ oldCapacity >> 1 /* preferred growth */); return elementData = Arrays.copyOf(elementData, newCapacity); } else { return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; } }
獲取老的容量
如果一次也沒有擴容過,則擴容大小為DEFAULT_CAPACITY和minCapacity較大的一個,DEFAULT_CAPACITY被定義為10,而我們此時的minCapacity為1,所以第一次擴容的大小為10
如果老的容量大于0或者elementData數(shù)組不是初始化狀態(tài)的數(shù)組(也就是已經(jīng)第一次擴容過)
那么通過位運算進(jìn)行擴容到原容量的1.5倍
注意:IDEA在debug默認(rèn)情況下顯示的數(shù)據(jù)是簡化的,如果需要看完整的數(shù)據(jù),需要進(jìn)行設(shè)置
使用指定大小的構(gòu)造器
原理操作和上面類似,只不過它初始的容量為指定的容量,需要擴容時擴容為原容量的1.5倍
ArrayList使用實例
ArrayList arrayList = new ArrayList(); arrayList.add(521); arrayList.add(null); arrayList.add("武松"); arrayList.add(null); System.out.println(arrayList);
輸出:
[521, null, 武松, null]
6.Vector
vector底層同樣是一個對象數(shù)組
protected Object[] elementData;
和ArrayList不同的是,它是線程同步的,也就是線程安全的
Vector和ArrayList的區(qū)別:
7.LinkedList
鏈表(Linked list)是一種常見的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu),是一種線性表,但是并不會按線性的順序存儲數(shù)據(jù),而是在每一個節(jié)點里存到下一個節(jié)點的地址。
鏈表可分為單向鏈表和雙向鏈表。
一個單向鏈表包含兩個值: 當(dāng)前節(jié)點的值和一個指向下一個節(jié)點的鏈接。
一個雙向鏈表有三個整數(shù)值: 數(shù)值、向后的節(jié)點鏈接、向前的節(jié)點鏈接。
Java LinkedList(鏈表) 類似于 ArrayList,是一種常用的數(shù)據(jù)容器。
與 ArrayList 相比,LinkedList 的增加和刪除的操作效率更高,而查找和修改的操作效率較低。
LinkedList的底層是一個雙向鏈表
增加元素源碼分析
在LinkedList鏈表中,存在幾個必知的概念 --> First:鏈表頭節(jié)點;Last:鏈表尾節(jié)點;next:該節(jié)點下一個節(jié)點地址;prev:該節(jié)點前一個節(jié)點地址
LinkedList linkedList = new LinkedList(); // 增 linkedList.add(521); linkedList.add(1314); System.out.println(linkedList);
首先:調(diào)用LinkedList的無參構(gòu)造,這里什么也沒有捏
public LinkedList() { }
隨后,和ArrayList和Vector一樣,進(jìn)行裝箱操作
開始添加操作:
public boolean add(E e) { linkLast(e); return true; }
為了方便理解,我們需要知道一下Node的構(gòu)造器內(nèi)容:
Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; }
繼續(xù)步入:
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; else l.next = newNode; size++; modCount++; }
這里是關(guān)鍵,接下來我們來分析一下上面的這段代碼:
首先,將last的值賦給l,新建一個Node節(jié)點,將last指向新建的Node節(jié)點,隨后進(jìn)行分支判斷,將first也指向新建的Node節(jié)點
最后,鏈表大小 + 1 ,修改次數(shù) + 1
經(jīng)過了上面的修改,現(xiàn)在的first和last都指向新建的節(jié)點,該節(jié)點的next和prev都為null
以上是鏈表中只有一個元素的情況,那么再次向鏈表添加元素呢?我們再來淺淺看一下
第二次添加節(jié)點,l被置為上一個節(jié)點的地址,隨后會被添加到新節(jié)點的prev屬性中:
還需要更新一下last的值為新添加進(jìn)來的節(jié)點的地址:
隨后,將上一個節(jié)點的next值置為當(dāng)前新加的節(jié)點地址:
最后,更新size和modCount即可!
刪除元素源碼分析
// 刪除最后一個節(jié)點 linkedList.remove();
進(jìn)行源碼分析,步入:
public E remove() { return removeFirst(); }
繼續(xù)步入:
public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); }
Java的設(shè)計者在這里獲取了鏈表頭節(jié)點的地址,以備后續(xù)進(jìn)行刪除,隨后檢查了一個頭節(jié)點為空的異常,在unlinkFirst方法,將會真正執(zhí)行刪除的操作!
繼續(xù)步入:
private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null) last = null; else next.prev = null; size--; modCount++; return element; }
開頭,獲取頭節(jié)點的值,以備在后續(xù)返回:
final E element = f.item;
先獲取一下頭節(jié)點后面的節(jié)點的地址,保存下來,然后將頭節(jié)點的數(shù)據(jù)和next置空,請求GC進(jìn)行回收:
final Node<E> next = f.next; f.item = null; f.next = null; // help GC
頭節(jié)點更新為原頭節(jié)點的下一個節(jié)點,并作節(jié)點個數(shù)判斷:
first = next; if (next == null) last = null; else next.prev = null;
最后,更新size和modCount,返回element
LinkedList使用Demo
import java.util.LinkedList; /** * LinkedList */ public class LinkedListTest { @SuppressWarnings({"all"}) public static void main(String[] args) { LinkedList linkedList = new LinkedList(); // 增 linkedList.add(521); linkedList.add(1314); System.out.println(linkedList); // 刪除頭節(jié)點 linkedList.remove(); System.out.println(linkedList); // 修改 linkedList.set(0,999); System.out.println(linkedList); // 查找 System.out.println(linkedList.get(0)); } }
輸出:
[521, 1314]
[1314]
[999]
999
8.ArrayList和LinkedList的選擇
以下情況使用 ArrayList :
- 頻繁訪問列表中的某一個元素。
- 只需要在列表末尾進(jìn)行添加和刪除元素操作。
以下情況使用 LinkedList :
- 你需要通過循環(huán)迭代來訪問列表中的某些元素。
- 需要頻繁的在列表開頭、中間、末尾等位置進(jìn)行添加和刪除元素操作。
在實際開發(fā)中,ArrayList用的最多,因為大部分的業(yè)務(wù)是查詢業(yè)務(wù)!
另外,ArrayList和LinkedList都是線程不安全的,推薦在單線程場景下使用
到此這篇關(guān)于詳解Java集合類之List篇的文章就介紹到這了,更多相關(guān)Java集合類List內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
使用Java代碼進(jìn)行因數(shù)分解和求最小公倍數(shù)的示例
這篇文章主要介紹了使用Java代碼進(jìn)行因數(shù)分解和求最小公倍數(shù)的示例,都是基于最基礎(chǔ)的算法原理實現(xiàn),需要的朋友可以參考下2015-11-11使用Runnable實現(xiàn)數(shù)據(jù)共享
這篇文章主要為大家詳細(xì)介紹了如何使用Runnable實現(xiàn)數(shù)據(jù)共享,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-07-07elasticsearch中term與match的區(qū)別講解
今天小編就為大家分享一篇關(guān)于elasticsearch中term與match的區(qū)別講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-02-02Reactor 多任務(wù)并發(fā)執(zhí)行且結(jié)果按順序返回第一個
這篇文章主要介紹了Reactor 多任務(wù)并發(fā)執(zhí)行且結(jié)果按順序返回第一個,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,感興趣的小伙伴可以參考一下2022-09-09