java集合原始碼解析(二)--AbstractCollection
阿新 • • 發佈:2018-11-17
今天帶來的是java單列頂層介面的第一個輕量級實現:AbstractCollection
我們直接進入正題,先來看看它的宣告:
package java.util;
//可以從名字上同樣看到 AbstractCollection 是一個抽象類,所以並不能例項化,
//這個類只是作為輕量級實現存在,讓實現Collection的子類不需要實現太多的方法
public abstract class AbstractCollection<E> implements Collection<E> //同樣使用了泛型E
再來,我們先看看有哪些方法 AbstractCollection並沒有負責實現:
有三個方法:
為什麼這三個方法不能在AbstractCollection裡實現呢?// Query Operations 查詢操作 /** * 返回一個迭代器 */ public abstract Iterator<E> iterator(); /** * 返回集合的大小 */ public abstract int size(); <span style="white-space:pre"> </span>// Modification Operations 修改操作 /** * 新增元素 */ public boolean add(E e) { throw new UnsupportedOperationException(); }
大家都知道單列集合的種類有很多種,而有些集合各自底層實現的方法,資料結構是完全不同的,比如大家知道的ArrayList底層使用陣列實現的,HashSet是雜湊結構,LinkedList則是雙向連結串列結構,所以這邊三個方法,在程式碼實現上不同,所以AbstractCollection 仍然以宣告abstract的方式或者丟擲 不支援本操作的異常方式,讓子類必須得重寫這三個方法。
那下面繼續看,
/** * 判斷集合是否為空,這裡則是呼叫獲取集合大小的方法, * 並判斷大小是否等於0來判斷集合是否為空的 */ public boolean isEmpty() { return size() == 0; }
/**
* 判斷集合中是否包含引數物件
*/
public boolean contains(Object o) {
Iterator<E> it = iterator();//這裡用到了迭代器物件,我把迭代器物件貼在了下面
if (o==null) {
//首先判斷引數是否為空
while (it.hasNext())
//迭代器的方法,意思是 迭代器是否 有下一個元素
if (it.next()==null)
//如果有 是否為空
return true;//返回true
} else {
// 傳入引數不為空
while (it.hasNext())
if (o.equals(it.next()))
//這裡使用了equals來比較 迭代器中的元素與傳入引數
// 上一期說過equals來自Object類,Object預設equals是比較兩物件引用是否一致
// 就是是否指向同一個地址值,所以這裡如果引數並沒有實現equals就會直接拿兩個地址值進行比較
// 一致則返回true
return true;
}
//都沒有則返回false
return false;
}
下面看看 迭代器Iterator的宣告
package java.util;
/**
* 可以看到迭代器是和集合一起出現的都是1.2版本以後
* 而且這裡的迭代器宣告只能單向向後迭代
* @since 1.2
*/
public interface Iterator<E> {
/**
* 判斷迭代器中是否含有下一個元素
*/
boolean hasNext();
/**
* 返回迭代器的下一個元素,並且把指標往下移動
*/
E next();
/**
* 刪除迭代器當前的元素
*/
void remove();
}
看到這裡,可以知道,如果要自己實現一個集合類, 那首先需要實現的方法就是獲取一個迭代器,
到後面就可以看到,那些集合子類實現,都是通過內部類的形式實現獲取一個迭代器這個方法的。
我們繼續:
/**
* 將集合轉化成陣列
*/
public Object[] toArray() {
// 宣告一個當前集合長度的Object陣列
Object[] r = new Object[size()];
// 可以看到集合中很多重要的方法都要用到 獲取迭代器 這個方法
// 集合內部都是使用這個迭代器去遍歷,集合中的元素的
Iterator<E> it = iterator();
for (int i = 0; i < r.length; i++) {
//這裡的length ,線上程安全的情況下,其實和size() 是一致的
if (! it.hasNext()) //迭代其中沒有下一個元素了
// 直接返回陣列,這邊呼叫的是Arrays.copyof()方法,我們下面看看這個方法的實現
return Arrays.copyOf(r, i);
//如果有下一個元素,則陣列的當前索引位置則被賦值為集合的下一個元素
r[i] = it.next();
}
// 當遍歷完整個陣列的長度後,集合仍然有下一個元素,線上程不安全的情況下
// 很可能就是這邊在轉化陣列,但是集合另一邊還在新增元素導致的。
// 如果仍然有下一個元素,則呼叫finishToArray()方法,下面我們也會看看,
// 如果沒有了,則把當前的Object陣列給直接返回
return it.hasNext() ? finishToArray(r, it) : r;
}
可以看到jdk大神,考慮的是相當縝密的,這才是大神啊~膜拜
下面我們看看Arrays.copyOf()這個方法
/**
*
* Arrays類是運算元組的工具類,所以很多方法都是靜態的
* 方便使用者直接呼叫
*/
public static <T> T[] copyOf(T[] original, int newLength) {
//原始陣列 , 新的陣列長度
// jdk直接呼叫了另一個過載方法,後面可以看到這樣的呼叫是非常多的
// 這麼呼叫充分的利用了java的過載特性,將程式碼的複用最大化
return (T[]) copyOf(original, newLength, original.getClass());
}
我們繼續:
/**
* 這裡的copyof比上一個copyof多一個代表陣列中元素型別的Class引數 這裡還使用了萬用字元泛型
*/
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
// 如果傳入的Class型別是Object的話,則直接建立一個Object陣列
? (T[]) new Object[newLength]
// 如果不是Object型別的話,則用Array.newInstance方法去建立一個新的陣列
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
//這裡呼叫的則是系統類中的陣列複製,後面可以看到該方法是native的
// java中宣告為native的方法,意思是底層實現並非java語言實現,應該是C語言
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
/**
* 這裡jdk沒有宣告版本,可見該方法是從java誕生開始就有的
*/
// 源陣列 源陣列起始位置 目標陣列 目標陣列起始位置 單位長度
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
//這個方法大致意思是 把 源陣列src 從 源陣列起始位置srcPos 開始的元素
//複製到 目標陣列dest 從 目標陣列起始位置destPos 開始 單位長度length 個的元素
// 親測以後,知道 如果 length > src的長度或者destPos + length > dest
// 或者srcPos,destPos兩個索引位置都超過了對應陣列的長度,
// 就會丟擲 ArrayIndexOutOfBoundsException
這個方法不止jdk原始碼呼叫,現實中,我們也可以用這個方法實現陣列的賦值哦~
突然發現剛剛忘記分析,Arrays.copyof()呼叫這個方法時傳遞的長度引數了
Math.min(original.length, newLength))
/**
* 就是返回兩個傳入引數,小的那個
*/
public static int min(int a, int b) {
return (a <= b) ? a : b;
}
我們繼續: Array.newInstance()方法
/**
* 建立一個與傳入引數相同型別,並且長度為傳入引數長度的陣列
* 底層呼叫的還是native方法
*/
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}
//可以看到這個方法是私有的,是不允許外面訪問的,區別在於不再有泛型指定了,意思是你想要建立陣列必須傳遞給我一個明確的型別
private static native Object newArray(Class componentType, int length)
throws NegativeArraySizeException;
還有個finishToArray()
//這個方法就是剛剛的陣列如果長度賦值完畢但是集合中還有剩餘元素的話
private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
// 記錄當前陣列的長度
int i = r.length;
while (it.hasNext()) {
//如果迭代器有下一個元素
int cap = r.length;//建立容量變數
if (i == cap) {
//如果和之前的陣列長度一致,說明當前陣列長度不夠,需要擴容
//新的容量 等於 原長度 + 原長度的50% + 1
// 這裡可以看到jdk底層很多2次冪的運算都是用位移符號去做的
// 並且擴容是按照 50%比例去做的
int newCap = cap + (cap >> 1) + 1;
// 這裡的 MAX_ARRAY_SIZE 是一個靜態常量,為 Ieteger最大值 - 8
// 而Integer的最大值是 2147483647
if (newCap - MAX_ARRAY_SIZE > 0)
// 如果新的容量 大於 MAX_ARRAY_SIZE
newCap = hugeCapacity(cap + 1);
//通過前面說的 Arrays.copyOf 建立陣列
r = Arrays.copyOf(r, newCap);
}
//走到這裡陣列r 擴容完畢了
// 所以通過迭代器進行賦值,這裡用的索引是之前記錄的陣列長度,
// 也是進這個方法賦值後的起點位置
r[i++] = (T)it.next();
}
// 如果過度分配的話,就會走Arrays.copyof重新對陣列長度進行賦值
// 走到這裡的時候i 可以說就是代表 陣列中最後一個有效位置了,因為後面迭代器已經沒有元素了
// 但是可能存在陣列長度比i要大,所以需要通過重新分配長度,對後面的空白位置進行去除
// jdk原來註釋就是這個意思 trim if overallocated
return (i == r.length) ? r : Arrays.copyOf(r, i);
}
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // 正常校驗
throw new OutOfMemoryError
("Required array size too large");
//如果傳入的容量大於 MAX_ARRAY_SIZE 則返回 Integer的最大值,否則就返回 MAX_ARRAY_SIZE
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
/**
* The maximum size of array to allocate.
* Some VMs reserve some header words in an array.
* Attempts to allocate larger arrays may result in
* OutOfMemoryError: Requested array size exceeds VM limit
*/
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
下面繼續帶引數的 toArray:
/**
*
*/
public <T> T[] toArray(T[] a) {
// 把當前的集合的大小記錄下來
int size = size();
//如果當前 陣列的長度 大於 集合的長度
T[] r = a.length >= size ? a : //直接返回引數陣列
//小於就用當前集合的長度建立一個新的陣列
(T[])java.lang.reflect.Array
.newInstance(a.getClass().getComponentType(), size);
Iterator<E> it = iterator();//獲得當前集合迭代器
for (int i = 0; i < r.length; i++) {
if (! it.hasNext()) { // 迭代器沒有下一個元素了
if (a == r) {
r[i] = null; // 把剩餘元素設為null
} else if (a.length < i) {//引數陣列的長度小於 集合的長度
return Arrays.copyOf(r, i);//呼叫copyof複製一個數組
} else {
System.arraycopy(r, 0, a, 0, i);
if (a.length > i) {
a[i] = null;
}
}
return a;
}
//有元素則對陣列的當前索引進行賦值
r[i] = (T)it.next();
}
// 這裡同無參 toArray()方法
return it.hasNext() ? finishToArray(r, it) : r;
}
看到這裡大概瞭解到,jdk底層集合轉化成陣列就是首先根據集合的長度引數建立一個新的陣列,然後通過迭代把集合中的元素一個個的賦值給陣列中的索引位置
/**
* 刪除元素
* 和contains 非常類似
*/
public boolean remove(Object o) {
Iterator<E> it = iterator();
if (o==null) {
while (it.hasNext()) {
if (it.next()==null) {
it.remove();
return true;
}
}
} else {
while (it.hasNext()) {
if (o.equals(it.next())) {
//contains的區別就是在這裡,通過呼叫迭代器的remove方法來去除元素
//不同集合迭代器實現可能不同,所以這裡只需要委託給具體實現就行了
it.remove();
return true;
}
}
}
return false;
}
剩下的方法就簡單了:一目瞭然
/**
* 之前在Collection介面中介紹過了,
* 本集合是否包含 引數集合的所有元素
*/
public boolean containsAll(Collection<?> c) {
for (Object e : c)//通過迭代 引數的集合
if (!contains(e)) //只要有一個元素不包含在本集合中就返回false
return false;
return true;//全都包含就返回true
}
/**
* 就算add沒有實現,這裡addAll也是可以實現的,
* 通過迭代引數集合,並且要求泛型是 本集合泛型的子類
* 將引數集合中的每一個元素都新增入本集合
*/
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
/**
* 基本同addAll
*/
public boolean removeAll(Collection<?> c) {
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
/**
* 求交集的方法
*/
public boolean retainAll(Collection<?> c) {
boolean modified = false;
Iterator<E> it = iterator();
while (it.hasNext()) {
if (!c.contains(it.next())) {//如果本集合中的元素不包含在引數集合中,就把它remove掉
it.remove();
modified = true;
}
}
//所以最後剩下的就是本集合和引數集合交集的那部分元素
return modified;
}
/**
* 就是迭代本集合,把元素全部remove掉
*/
public void clear() {
Iterator<E> it = iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
}
終於到最後一個方法了:
/**
* 重寫了Object的toString方法,
* 所以集合是直接可以通過System.out.println()列印在控制檯的
* 使用的正是這個方法中定義的格式 [xxx, xxxx, xxx, xxxx]
*/
public String toString() {
Iterator<E> it = iterator();
if (! it.hasNext())
return "[]";//集合中沒有元素則列印一對中括號,表示空集合
//這裡使用的是StringBuilder去拼接字串
StringBuilder sb = new StringBuilder();
sb.append('[');
for (;;) {// 同 while(true)
E e = it.next();
sb.append(e == this ? "(this Collection)" : e);
if (! it.hasNext())
//如果迭代器沒有下一個元素了,就用中括號收尾
return sb.append(']').toString();
// 不然就拼接上 逗號 和 空格
sb.append(',').append(' ');
}
}
說到集合列印,就順便提一下 陣列的列印,陣列如果直接列印在控制檯的話,是一串地址值,沒有什麼意義,
所以要列印陣列的話,可以用陣列工具類的方法Arrays.toString(陣列) 把想要列印的陣列放進去就行了。
AbstractCollection看完了,正所謂輕量級實現,所以還有很多方法是沒有實現的,
下一個分析就從List 開始吧~因為大家用的最多嘛~