集合的理解和好處 數組一旦定義,長度即固定,不能修改。要添加新元素需要新建數組,然後迴圈拷貝,非常麻煩 集合可以動態保存任意多個對象,使用比較方便 提供餓了一系列方便的操作對象的方法:add、remove、set、get等 使用集合添加、刪除新元素的示意代碼,簡潔明瞭 集合主要是兩組(單列集合,雙列 ...
集合的理解和好處
數組一旦定義,長度即固定,不能修改。要添加新元素需要新建數組,然後迴圈拷貝,非常麻煩
- 集合可以動態保存任意多個對象,使用比較方便
- 提供餓了一系列方便的操作對象的方法:add、remove、set、get等
- 使用集合添加、刪除新元素的示意代碼,簡潔明瞭
集合主要是兩組(單列集合,雙列集合)
Collection 介面有兩個重要的子介面,List 和 Set,他們的實現子類都是單列集合,直接存放值
Map介面的實現子類 是雙列集合,存放的是K-V鍵值對
這是Collection介面下體系的主要介面和類體系:
這是Map介面下體系的主要介面和類體系:
1. Collection介面和常用方法
1.1 Collection介面實現類的特點
public interface Collection<E> extends Iterable<E>
- collection實現子類可以存放多個元素,每個元素可以是Object
- 有些Collection的實現類,可以存放重覆的元素,有些不可以
- 有些Collection的實現類,有些事有序的(List),有些不是有序的(Set)
- Collection介面沒有直接的實現子類,是通過他的子介面Set和List來實現的
1.2 Collection介面和常用方法
以實現子類ArrayList來演示
void add(E e); //添加單個元素, E是泛型
E remove(int index); //刪除並返回指定元素
boolean contains(Object o); //查找某個元素是否存在
int size(); //獲取元素個數
boolean isEmpty(); //判斷是否為空
void clear(); //清空
boolean addAll(Collection<? extends E> c); //添加E集合中的所有元素
boolean containsAll(Collection <?> c); //查找E集合中的元素是否都存在於該集合中
boolean removeAll(Collection<?> c); //移除該集合中所有同時存在於E集合中的元素
1.3 Collection介面遍歷元素方式
1.3.1 使用Iterator(迭代器)
- Iterator對象稱為迭代器,主要用於遍歷Collection集合中的元素
- 所有實現了Collection介面的集合類多有一個iterator()方法,用於返回一個實現了Iterator介面的對象,即可以返回一個迭代器
- Iterator的方法和執行原理:
//hasNext(); 判斷是否還有下一個元素
//next(); 1.指針下移,2.將下移以後集合位置上的元素返回
//remove(); 從底層集合中移移除此迭代器返回的最後一個元素,每次調用next()時,只能調用此方法一次。並且如果在迭代過程中,調用了除該方法意外的任何方式修改基礎集合,都會破壞迭代器,從而終止迭代,這是為了應對併發問題,因此可以通過實現此方法的時候指定併發修改策略來避免破壞迭代器。
Iterator iterator = coll.iterator();//得到一個集合的迭代器
while(iterator.hasNext()){
//next():
System.out.println(iterator.next());
}
- Iterator僅用於遍歷集合,Iterator本身並不存放對象
提示:在調用iterator.next()方法之前,必須要調用iterator.hasNext()進行檢測。否則當下一條記錄無效時,調用iterator.next()會拋出NoSuchElementException異常。如果需要再次遍歷,需要重置迭代器:iterator=coll.iterator();
1.3.2 使用增強for迴圈
增強for迴圈的底層仍然是迭代器,所以可以理解成簡化版的迭代器
增強for迴圈可以用來遍曆數組或者Collection集合
for(element:list){
System.out.println(element);
}
2. List介面和常用方法
List介面和Set介面都繼承了Collection介面,因此Collection介面的所有方法,實現了List介面和Set介面的類都擁有,但List介面和Set介面是平級的,因此List介面的方法,Set介面不一定擁有
2.1 List集合基本介紹
- List集合類中元素有序(即添加順序和取出順序一致)、且元素可重覆。
- List集合中每個元素都有器對應的順序索引,即支持索引。
- List容器中的元素都對應一個整數型的序號記載器在容器中的位置,可以根據序號存取容器中的元素
- JDK API中List介面的實現類有:AbstractList,AbstractSequentialList , ArrayList , AttributeList , CopyOnWriteArrayList , LinkedList , RoleList , RoleUnresolvedList , Stack , Vector
2.2 List介面的常用方法
List集合里添加了一些根據索引來操作集合元素的方法:
1. void add(int index, E element); //在index位置插入E類型的元素,E是泛型
2. boolean addAll(int index, Collection<? extends E> c); //將c中所有的元素插入index位置,c是實現了Collection介面的E類型的子類
3. E get(int index); //獲取並返回index位置的元素
4. int indexOf(Object o); //返回obj在集合中首次出現的位置
5. int lastIndexOf(Object o); //返回obj在集合中最後出現的位置
6. boolean remove(Object o); //移除指定元素
7. E remove(int index); //移除並返回指定位置的元素
8. E set(int index, E element); // 返回index位置的元素,並將此位置元素替換為element
9. List<E> subList(int fromIndex, int toIndex);//返回從fromIndex到toIndex位置的前閉後開子集合[fromIndex,toIdex)
2.3 ArrayList的註意事項
ArrayList類定義說明:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Aloneable, java.io.Serializable
- ArrayList 可以放入null,並且可以放入多個
- ArrayList是由數組來實現數據存儲的
- ArrayList基本等同於Vector,ArrayList的執行效率更高,但是線程不安全的,因此多線程情況下,不建議使用ArrayList
//ArrayList源碼中,沒有關於線程式控制制的代碼
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
//Vector源碼中使用Synchronized關鍵字修飾,是線程安全的
public synchronized boolean add(E e) {
modCount++;
add(e, elementData, elementCount);
return true;
}
//LinkedList也是線程不安全的
public boolean add(E e) {
linkLast(e);
return true;
}
2.4 ArrayList的底層操作機制源碼分析
- ArrayList中維護了一個Object類型的數組elementData
//transient 關鍵字表示此數組不會被序列化
transient Object[] elementData; // non-private to simplify nested class access
- 當創建對象時,如果使用的是無參構造器,則初始elementData容量為0.
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- 當添加元素時:先判斷是否需要擴容,如果需要擴容,則調用grow方法,否則直接添加元素到適當位置
- 如果使用的是無參構造器,則第一次添加,需要擴容的話,則擴容elementData為10,如果需要再次擴容的話,則擴容elementData為1.5倍
- 如果使用的時指定容量capacity的構造器,則初始elementData的容量為capacity
- 如果使用的是指定容量capacity的構造器,如果需要擴容,則直接擴容elementData為1.5倍
2.5 ArrayList無參構造器
1. 使用無參構造器初始化一個數組:
ArrayList<Object> list = new ArrayList<Object>();
-->初始化過程調用過程:
-->ArrayList.java
這一步涉及的成員變數:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//首先將elementData初始化為空數組
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2. 迴圈添加數據:
for(int i = 1; i <= 10; i++){
list.add(i);
}
-->調用過程:
-->ArrayList.java
這一步涉及的成員變數:
private int size; //成員變數,用來記錄數組列表的大小(包含元素的數量)
private static final int DEFAULT_CAPACITY = 10;
//首先發生int->Integer自動裝箱
//然後調用 boolean add(E e)方法,modCount記錄該集合被修改的次數
public boolean add(E e) {
modCount++;
add(e, elementData, size);//size = 0
return true;
}
--> private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)//如果當前數組已滿
elementData = grow();//調用grow函數擴容至10
elementData[s] = e;
size = s + 1;
}
--> Object[] grow(){
return grow(size+1);
}
--> Object[] grow(int minCapacity){//將數組拷貝到長度為10的新數組中
return elementData = Arrays.copyOf(elementData, newCapacity(minCapacity));
}
--> int newCapacity(int minCapacity){ //return 10;
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity>>1);//新容量為舊容量的(1+0.5)倍
if(newCapacity - minCapacity <= 0){//即如果舊容量為0或1
//如果當前數組為就是那個空的成員數組,就返回10和(當前容量+1)中較大的一個
if(elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDTA){
return Math.max(DEFAULT_CAPACITY,minCapacity);
}
//block of code unrelated to the operation
}
}
3. 再次迴圈添加數據:
for (int i = 11; i <= 15; i++){
list.add;
}
-->調用過程:
-->ArrayList.java
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
--> private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)//此時數組已經存滿了
elementData = grow();//調用grow方法再次擴容至10*(1+0.5)=15
elementData[s] = e; //在第10位存入這一輪的數據
size = s + 1;
}
--> private Object[] grow() {
return grow(size + 1);//size = 10
}
--> private Object[] grow(int minCapacity) {//minCapacity = 11,即最小也得11個位置才可以,新數組不能小於這個數
return elementData = Arrays.copyOf(elementData,
newCapacity(minCapacity));
}
--> private int newCapacity(int minCapacity) {//minCapacity = 11
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//15
if (newCapacity - minCapacity <= 0) {//false
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
return Math.max(DEFAULT_CAPACITY, minCapacity);
if (minCapacity < 0) // overflow //false
throw new OutOfMemoryError();
return minCapacity; //return 15
}
return (newCapacity - MAX_ARRAY_SIZE <= 0)
? newCapacity
: hugeCapacity(minCapacity);
}
4. 再次添加數據:
list.add(100);//此時由於list.length = 15,容量已經滿了,會再次擴容至22
//
2.5 Vector的基本介紹
- Vector類的定義說明
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
- Vector 底層也是一個對象數組,protected Object[] elementData;
- Vector是線程同步的,即線程安全,Vector類的操作方法帶有synchronized
public synchronized void setSize(int newSize) {
modCount++;
if (newSize > elementData.length)
grow(newSize);
final Object[] es = elementData;
for (int to = elementCount, i = newSize; i < to; i++)
es[i] = null;
elementCount = newSize;
}
- 在開發中,需要線程同步安全時,考慮使用Vector
2.5.1 Vector和ArrayList的比較
集合 | 底層結構 | 版本 | 線程安全(同步)效率 | 擴容 |
---|---|---|---|---|
ArrayList | 可變數組 | jdk1.2 | 不安全,效率高 | 如果有參構造,1.5倍;如果是無參構造,第一次10,從第二次開始按1.5倍擴容 |
Vector | 可變數組 | jdk1.0 | 安全,效率不高 | 如果是無參,預設10,滿後按2倍擴容;有參構造則按指定大小創建,並每次按2倍擴容,也可以手動指定增量capacityIncrement,每次增加capacityIncrement |
2.6 LinkedList
LinkedList類定義說明
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
- LinkedList實現了雙向鏈表和雙端隊列特點
- 可以天界任意元素(元素可以重覆),包括null
- 線程不安全,沒有實現同步
2.6.1 LinkedList底層架構
LinkedList的底層操作機制
- LinkedList底層維護了一個雙向鏈表。
- LinkedLsit中維護了兩個屬性first和last分別指向首節點和尾節點
- 每個節點(Node對象)裡面又維護了prev、next、item三個屬性,其中通過prev指向前一個,通過next指向後一個節點,最終實現雙向鏈表。
- 所以LinkedList的元素的添加和刪除,不是通過數組完成的,相對來說效率較高
2.6.1 ArrayList和LinkedList比較
集合 | 底層結構 | 增刪的效率 | 改查的效率 |
---|---|---|---|
ArrayList | 可變數組 | 較低 | 較高 |
LinkedList | 雙向鏈表 | 較高,通過鏈表追加 | 較低 |
2.6.2 如何選擇ArrayList和LinkedList
- 如果改查的操作多,選擇ArrayList
- 如果增刪的操作多,選擇LinkedList
- 一般來說,在程式中,80%~90%的操作都是查詢,因此大部分情況下會選擇ArrayList
- 在一個項目中,根據業務靈活選擇,也可能這樣,一個模塊使用的是ArrayList,另一個模塊使用的是LinkedList,要根據業務來選擇。
- 這兩個都是線程不安全的,在單線程程式中使用