Java集合Collection之實現原理解讀(ArrayList)
一、簡介
在專案中,相信大家都已經用過集合List,它提供了一系列的API,方便我們使用。今天有空去看了下ArrayList的原始碼,本章將會模仿原始碼實現一個簡單的ArrayList,只是幫助理解ArrayList底層是怎麼實現的,並沒有必要去自定義ArrayList。
二、實現原理
ArrayList底層是通過陣列實現的,所有對資料的操作其實底層都是對Object[]陣列的操作, 具體的增刪查改等功能都是依據這個陣列進行陣列相關的操作,如複製陣列、陣列擴容、刪除陣列中某個元素等。
特點:
* 1. ArrayList查詢快(索引),新增、修改、刪除慢(需要修改下標) * 2. ArrayList預設建立大小為10的物件陣列,如果超過十個元素會進行擴容。
三、自定義ArrayList
package com.wsh.arraylist; /** * 自定義一個簡單的ArrayList,幫助理解ArrayList底層原理 * * @author weishihuai * @date 2018/9/25 * <p> * 說明: * 1. ArrayList底層實現是陣列,所有對資料的操作其實底層都是對Object[]陣列的操作。 * 2. ArrayList查詢快(索引),新增、修改、刪除慢(需要修改下標) * 3. ArrayList預設建立大小為10的物件陣列,如果超過十個元素會進行擴容。 * 4. 本類只實現了部分常見的方法,幫助自己理解而已。 */ public class CustomArrayList { /** * 陣列初始化大小(預設為10) */ private static final int DEFAULT_INIT_NUM = 10; /** * 陣列的大小 */ private int size; /** * 存放資料的物件陣列 */ private Object[] data; /** * 無參構造方法 */ public CustomArrayList() { this(DEFAULT_INIT_NUM); } /** * 有參構造方法 * * @param initNum 指定大小 */ public CustomArrayList(int initNum) { //如果初始化大小小於0 if (initNum < 0) { throw new IllegalArgumentException("陣列初始化大小不能小於0"); } //建立指定大小的物件陣列 data = new Object[initNum]; } /** * 新增元素 * * @param object 物件 */ public void add(Object object) { //陣列擴容處理 //建立新陣列,複製原陣列到新陣列,返回新的陣列 if (size == data.length) { Object[] newData = new Object[size * 2 + 1]; // for(int i = 0 ; i < data.length ; i++) { // newData[i] = data[i]; // } //拷貝陣列 System.arraycopy(data, 0, newData, 0, data.length); //替換原陣列 data = newData; } data[size] = object; //每次新增完元素大小需要加1 size++; } /** * 獲取List的大小 * * @return List的大小 */ public int size() { return size; } /** * List是否為空 * * @return */ public boolean isEmpty() { return size == 0; } /** * 根據下標獲取對應的值 * * @param index 下標索引 * @return */ public Object get(int index) { //判斷索引是否合法 if (index < 0 || index >= size) { throw new IllegalArgumentException("陣列下標索引非法,下標越界"); } return data[index]; } /** * 根據索引刪除指定位置的物件 * 原理: 被刪除下標以後的元素依次往前移 * * @param index 下標索引 * @return 被刪除的舊資料 */ public Object remove(int index) { Object old = data[index]; //判斷索引是否合法 if (index < 0 || index >= size) { throw new IllegalArgumentException("陣列下標索引非法,下標越界"); } //原理:陣列copy int num = size - index - 1; if (num > 0) { System.arraycopy(data, index + 1, data, index, num); } data[--size] = null; // data[size - 1] = null; // size--; //返回被刪除的舊資料 return old; } /** * 刪除指定值的元素 * 原理: 迴圈遍歷陣列,使用equals比較值是否相等,找到第一個匹配的陣列元素的下標,根據該下標進行刪除 * * @param object 指定值 * @return */ public boolean remove(Object object) { for (int i = 0; i < size; i++) { if (get(i).equals(object)) { remove(i); return true; } } return false; } /** * 給指定索引下標賦值 * 原理:替換原陣列下標的值 * 下標索引 * * @param index * @param object 物件 */ public Object set(int index, Object object) { //判斷索引是否合法 if (index < 0 || index >= size) { throw new IllegalArgumentException("陣列下標索引非法,下標越界"); } Object oldValue = data[index]; data[index] = object; return oldValue; } /** * 指定位置新增值 * 原理: 陣列copy,依次往後移,空出當前index位置存放object物件 * * @param index 下標索引 * @param object 物件 */ public void add(int index, Object object) { //判斷索引是否合法 if (index < 0 || index >= size) { throw new IllegalArgumentException("陣列下標索引非法,下標越界"); } //陣列的擴容 if (size == data.length) { Object[] newData = new Object[size * 2 + 1]; //拷貝陣列 System.arraycopy(data, 0, newData, 0, data.length); //替換原陣列 data = newData; } System.arraycopy(data, index, data, index + 1, size - index); data[index] = object; size++; } /** * 根據指定值查詢下標,未找到返回-1 * 原理: 迴圈遍歷陣列,使用equals方法判斷值是否相等,返回第一個匹配的下標索引,未找到返回-1 * * @param object 物件 * @return 下標索引 */ public int indexOf(Object object) { if (null == object) { for (int i = 0; i < size; i++) { if (null == data[i]) { return i; } } } else { for (int i = 0; i < size; i++) { if (object.equals(data[i])) { return i; } } } return -1; } /** * 從後往前找指定值對應的下標索引 * 原理: 倒序迴圈遍歷陣列,使用equals方法判斷是否相等,找到返回對應的下標索引,未找到返回-1 * * @param object 指定值 * @return 下標索引 */ public int lastIndexOf(Object object) { if (null == object) { for (int i = size - 1; i >= 0; i--) { if (data[i] == null) { return i; } } } else { for (int i = size - 1; i >= 0; i--) { if (object.equals(data[i])) { return i; } } } return -1; } }
以上就是一個簡單的ArrayList,提供了簡單的增刪查改功能,可能會有一些問題,不過幫助理解底層實現已經夠了。
四、方法詳解
【a】構造方法:
1. 無參構造方法: 預設建立長度為10的Object[]陣列。
2. 有參構造方法:建立指定大小的Object[]陣列。
【b】public void add(Object object) {} 新增元素
1. 新增元素之前需要判斷當前Object[]陣列的長度是否等於size,如果相等了說明陣列需要擴容了,簡單理解就是數組裡面不能存放這麼多元素了,需要擴大陣列的長度,一般是2n+1。
2. 新增實現原理: 實際上是建立一個新的陣列,然後將原來Object[]陣列中的元素拷貝到新陣列中,然後把新陣列賦值給存放資料的陣列。
3. 每新增一個元素,size需要加1
【c】size(){} 返回list的大小
1. size
【d】isEmpty() {} 判斷list是否為空,
1. 即size == 0
【e】get(int index){} 根據索引取出對應list中的元素
1. 需要判斷索引是否越界以及是否合法
2. 實現原理: 根據索引,這個索引對應陣列的下標,即直接返回data[index]陣列下標對應的值
【f】remove(int index) {} 移出list中對應索引的元素
1. 判斷索引index引數是否合法
2.實現原理: 陣列拷貝,即將對應index索引的元素後面的元素都前移一位,然後把index索引的元素置空,利於垃圾回收器進行無用物件回收。
【g】remove(Object object){} 移出list中指定物件的元素
1. 實現原理: 迴圈遍歷陣列,使用equals比較值是否相等,找到第一個匹配的陣列元素的下標,根據該下標進行刪除。
【h】set(int index, Object object) {} 給list中對應索引的元素賦新值
1. 實現原理: 通過data[index]找到對應索引的元素,直接將原物件的值賦新值
2. 判斷索引是否合法
【i】add(int index, Object object) {} list中指定位置新增元素
1. 實現原理: 陣列copy,依次往後移,空出當前index位置存放object物件
2. 陣列擴容問題
3. 判斷索引是否合法
【j】indexOf(Object object){} 判斷list中是否含有某個元素
1. 實現元素: 迴圈遍歷陣列,使用equals方法判斷值是否相等,返回第一個匹配的下標索引,未找到返回-1
2. 注意object為空的情況
【k】lastIndexOf(Object object) {} 從list後面開始查詢是否包含某個元素。
1. 實現原理: 倒序迴圈遍歷陣列,使用equals方法判斷是否相等,找到返回對應的下標索引,未找到返回-1
2. 注意object為空的情況
五、測試
package com.wsh.arraylist;
/**
* 測試自定義ArrayList
*
* @author weishihuai
* @date 2018/9/25
*/
public class TestCustomArrayList {
public static void main(String[] args) {
CustomArrayList customArrayList = new CustomArrayList();
/***********************************add(object)***************************************/
customArrayList.add("aaa");
customArrayList.add("bbb");
customArrayList.add("ccc");
customArrayList.add("ddd");
/***********************************size()********************************************/
//4
System.out.println(customArrayList.size());
/***********************************isEmpty()*****************************************/
//false
System.out.println(customArrayList.isEmpty());
/***********************************get(index)****************************************/
//Exception in thread "main" java.lang.IllegalArgumentException: 陣列下標索引非法,下標越界
//System.out.println(customArrayList.get(10));
//ccc
System.out.println(customArrayList.get(2));
/***********************************remove(index)*************************************/
//ccc
System.out.println(customArrayList.remove(2));
//3
System.out.println(customArrayList.size());
//true
System.out.println(customArrayList.remove("bbb"));
//2
System.out.println(customArrayList.size());
/***********************************set(index,object)*********************************/
//ddd
System.out.println(customArrayList.get(1));
//ddd
System.out.println(customArrayList.set(1, "1111"));
//1111
System.out.println(customArrayList.get(1));
/***********************************add(index,object)*********************************/
customArrayList.add(1, "2222");
//2222
System.out.println(customArrayList.get(1));
for (int i = 0; i < customArrayList.size(); i++) {
//aaa=====2222=====1111=====
System.out.print(customArrayList.get(i) + "=====");
System.out.println();
}
/***********************************indexOf(object)************************************/
//1
System.out.println(customArrayList.indexOf("2222"));
//-1
System.out.println(customArrayList.indexOf("abcdef"));
/***********************************lastIndexOf(object)********************************/
//0
System.out.println(customArrayList.lastIndexOf("aaa"));
}
}
至此,我們已經通過模擬ArrayList原始碼自定義了一個簡單的List,雖然只實現了一部分方法,但對於我們理解ArrayList底層是怎麼實現的應該會有一定的幫助。
六、總結
ArrayList集合類底層是通過陣列實現的,具體的操作都是圍繞陣列展開,涉及陣列拷貝、陣列新增、陣列刪除元素等。本文是作者在看原始碼之後仿照原始碼實現的一個ArrayList,不能保證沒有問題,只是幫助理解實現原理而已。僅供大家學習參考,一起學習一起進步。