在面試過程中,會遇到ArrayList實現原理是怎麼樣,在什麼情況會對集合進行擴容等問題;ArrayList相對於其它的集合是比較簡單的了。在後面會將手寫的ArrayList代碼附上,在這之前我們需要了解ArrayList一些主要的成員變量以及原理。
1、主要的成員變量解釋
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* Shared empty array instance used for empty instances.
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int 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;
- DEFAULT_CAPACITY :集合默認的初始大小
- EMPTY_ELEMENTDATA: 默認一個空的數組對象;在調用有參的ArrayList時會使用的,比如new ArrayList(int initialCapacity)時,initialCapacity等於0的時候,就會賦值給elementData了。
- DEFAULTCAPACITY_EMPTY_ELEMENTDATA :默認一個空的數組對象,從使用程度來看,主要是區分在集合實例化的方式不一樣,在使用默認無參的構造方法時,會將DEFAULTCAPACITY_EMPTY_ELEMENTDATA賦值給elementData;這個跟EMPTY_ELEMENTDATA都是一個空的數組對象,不是很明白爲什麼要搞兩個空的數組對象
- elementData :元素存放的位置;從這裏可以看出ArrayList實際是用數組存儲元素的
- size :集合存放的元素大小
2、集合擴容原理
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
這是大家熟悉的ArrayList對象中的add方法;在這個方法中會分一下步驟來進行:
- 計算當前最新的容量;會拿當前即將要添加的元素下標去ensureCapacityInternal(minCapacity)方法中做數組擴容判斷。我們去看跟數組擴容相關的方法,不必要的代碼就直接忽略
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
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);
}
在ensureExplicitCapacity方法中"minCapacity - elementData.length"這段代碼纔是擴容的條件。當計算出來的最新下標值減去當前數組的長度大於0時,就滿足擴容條件了。
而在grow方法中,就是進行擴容的邏輯int newCapacity = oldCapacity + (oldCapacity >> 1);主要代碼,這一段代碼就是決定數組要擴容到多少,簡單來說就是:當前的數組長度+(當前數組長度右移1位);結合前面說的DEFAULT_CAPACITY變量,集合默認的數組大小是10,對號入座之後應該是:int newCapacity = 10 + (10 >> 1) 最終結果是15,最後通過Arrays.copyOf對數組進行拷貝擴容。在Arrays.copyOf方法中底層實際是調用管理System.arraycopy方法的,這裏不繼續講解,就當是額外的話題。最終將擴容後的數組賦值給elementData,最後通過elementData[size++] = e;將元素存放到數組中。
總結:ArrayList實際是用數組存儲元素,這也決定的了對隨機獲取元素時,效率是很高的;但是ArrayList對元素的增刪改性能損耗是比較大的因爲增加或者刪除元素,都需要移動集合裏面的元素,因此ArrayList不適合頻繁的增刪改操作,比較適合元素的獲取。
以上就是對ArrayList擴容的講解,其它的方法也相對簡單,這裏就直接忽略了,有興趣的同學可以自行去研究。接下來就是依葫蘆畫瓢,簡單自己實現一個ArrayList的代碼編寫了。
3、手寫ArrayList
import java.util.Arrays;
/**
* @Description: 自定義ArrayList
* @CreatedDate: 2018/12/10 15:17
* @Author:
*/
public class CustomArrayList<E> {
/**
* 初始容量大小
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 默認一個空的數組
*/
private static final Object[] DEFAULT_EMPTY_ELEMENTDATA = {};
/**
* 數組最大的一個臨界值
*/
private static final int MAX_SIZE = Integer.MAX_VALUE - 8;
/**
* 實際存放的元素值
*/
private Object[] elementData;
/**
* 集合大小
*/
private int size;
public CustomArrayList() {
this.elementData = DEFAULT_EMPTY_ELEMENTDATA;
}
/**
* @Description: 添加元素
* @Author:
* @CreatedDate: 2018/12/10 15:58
* @param
* @return
*/
public boolean add(E e) {
//計算容量,是否需要擴容
checkExpansionCapacity(size + 1);
elementData[size++] = e;
return true;
}
/**
* @Description: 獲取集合元素:獲取元素之前,先檢查是否會出現數組下標越界
* @Author:
* @CreatedDate: 2018/12/10 16:57
* @param
* @return
*/
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
/**
* @Description: 根據下標刪除元素
* @Author:
* @CreatedDate: 2018/12/10 16:57
* @param
* @return
*/
public E remove(int index) {
rangeCheck(index);
E oldValue = elementData(index);
resetEmpty(index);
return oldValue;
}
/**
* @Description: 根據對象刪除元素
* @Author:
* @CreatedDate: 2018/12/10 18:10
* @param
* @return
*/
public boolean remove(E e) {
if (e == null) {
//null,則查詢是否有null的數據,有則刪除
for (int i =0 ;i <elementData.length; i++) {
if (elementData[i] == null) {
resetEmpty(i);
return true;
}
}
}else{
for (int i =0 ;i <elementData.length; i++) {
if (e.equals(elementData[i])) {
resetEmpty(i);
return true;
}
}
}
return false;
}
/**
* @Description: 集合大小
* @Author:
* @CreatedDate: 2018/12/10 16:57
* @param
* @return
*/
public int size(){
return this.size;
}
/**
* @Description: 刪除某一個元素 or 將某一個元素置空
* System.arraycopy方法參數解釋:
* Object src : 原數組
* int srcPos : 從元數據的起始位置開始
* Object dest : 目標數組
* int destPos : 目標數組的開始起始位置
* int length : 要copy的數組的長度
*
* @Author:
* @CreatedDate: 2018/12/10 18:17
* @param
* @return
*/
private void resetEmpty(int index){
int movedIndex = size - index - 1;
//利用底層提供的數組拷貝api來實現拷貝功能
if (movedIndex > 0)
System.arraycopy(elementData, index + 1, elementData, index, movedIndex);
elementData[--size] = null;
}
/**
* @Description: 獲取數組中元素
* @Author:
* @CreatedDate: 2018/12/10 16:57
* @param
* @return
*/
private E elementData(int index) {
return (E) this.elementData[index];
}
/**
* @Description: 範圍檢查
* @Author:
* @CreatedDate: 2018/12/10 16:37
* @param
* @return
*/
private void rangeCheck(int index) {
if (index >= size)
throw new IndexOutOfBoundsException("index:" + index + ", size:" + size);
}
/**
* @Description: 檢查數組是否需要擴容
* @Author:
* @CreatedDate: 2018/12/10 16:05
* @param
* @return
*/
private void checkExpansionCapacity(int minCapacity){
int capacity = calculateCapacity(elementData, minCapacity);
if (capacity - elementData.length > 0)
expansionCapacity(capacity);
}
/**
* @Description: 擴容數組:
* @Author:
* @CreatedDate: 2018/12/10 16:05
* @param
* @return
*/
private void expansionCapacity(int minCapacity){
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_SIZE > 0)
newCapacity = MAX_SIZE;
//進行數組擴容
elementData = Arrays.copyOf(elementData, newCapacity);
}
/**
* @Description: 計算容量
* @Author:
* @CreatedDate: 2018/12/10 15:46
* @param
* @return
*/
private int calculateCapacity(Object[] elementData, int minCapacity){
if (elementData.getClass() == DEFAULT_EMPTY_ELEMENTDATA.getClass()) {
//一開始數組是空的,則計算出最大的容量值
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
}
public class ArrayListDemo {
public static void main(String[] args) {
CustomArrayList<String> list = new CustomArrayList<String>();
list.add("test1");
list.add("test2");
list.add("test3");
list.add("test4");
list.add("test5");
for (int i= 0; i< 8; i ++ ) {
if(i == 5) {
System.out.println("start...");
}
list.add("demo_" + i);
}
System.out.println(list.size());
list.remove(0);
list.remove("test2");
String result = list.get(1);
System.out.println(result);
System.out.println(list.size());
}
}
以上內容如有問題,希望可以留言給我,我可以及時糾正。很高興與您相遇,希望對閱讀完後對您有所幫助;
學習永無止境,不進則退~!