1 手寫ArrayList核心原始碼
手寫ArrayList核心原始碼
ArrayList是Java中常用的資料結構,不光有ArrayList,還有LinkedList,HashMap,LinkedHashMap,HashSet,Queue,PriorityQueue等等,我們將手寫這些常用的資料結構的核心原始碼,用盡量少的程式碼來揭示核心原理。
下面我們來手寫ArrayList的核心原始碼
首先我們定義一個QArrayList,不要問為什麼叫QArrayList,因為我之前寫過Qt,僅此而已。原始碼 public class<T> QArrayList
,Java中的ArrayList的底層就是用一個Object[] 結構來儲存資料的。我們也要定義一個Object[] 屬性。
而且我們還要定義一個預設的資料的大小,以便在呼叫預設建構函式的情況下使用。
private final int DEFAULT_LIST_SIZE = 8;
還要定義一個 int mSize
變數,mSize 預設為0
,代表下一個可以存放資料的陣列的索引,代表下一個可以存放資料的陣列的索引,代表下一個可以存放資料的陣列的索引
重要的事情說三遍
到現在為止我們的類如下:
public class QList<T> { //預設的陣列的大小 private final int DEFAULT_LIST_SIZE = 8; //存放資料的地方 private Object[] mData; //下一個可以存放資料的當前陣列的索引 private int mSize; ...... }
好了,存放資料的陣列也有了,下一個可以存放資料的當前的陣列的索引也有了
ArrayList 底層是用陣列存放資料,那麼會有一個問題,如果此時陣列滿了我們再往裡面存放資料的時候,怎麼辦呢?ArrayList是再新建一個數組,新陣列的大小是原來陣列大小的2倍,那麼我們也這樣做。
此時,我們實現 add,get,remove,resize等這幾個核心方法,QArrayList完整的程式碼如下 :
public class QArrayList<T> { //預設的陣列的大小 private final int DEFAULT_LIST_SIZE = 8; //存放資料的地方 private Object[] mData; //下一個可以存放資料的當前陣列的索引 private int mSize; public QArrayList() { //new 一個數組,用來存放 mData = new Object[DEFAULT_LIST_SIZE]; //下一個可以存放資料的當前陣列的索引為0 mSize = 0; } public QArrayList(int capacity){ if(capacity <= 0 || capacity > Integer.MAX_VALUE){ throw new RuntimeException("invalid capacity"); } mData = new Object[capacity]; mSize = 0; } //返回當時陣列的已經存放了多少個元素 public int size() { return mSize; } //返回陣列的總大小,其實這個介面沒有必要對外提供,這裡我們只是為了演示用 public int capacity() { return mData.length; } //新增一個元素 public void add(T e) { //規定不允許新增一個空元素 if(e == null){ return; } //如果當前陣列已經滿了,擴容為原來陣列的2倍 if (mSize >= mData.length) { //擴容 resize(); } //將新增的元素新增到陣列中 mData[mSize] = e; //同時 mSize++ 指向下一個可以存放資料的位置 mSize++; } //獲取指定位置的元素,如果position不合法,直接丟擲異常 //這樣做是有必要的,我們提供的是一個庫 // 直接丟擲異常讓使用知道用錯了,沒有必要 return null // 因為這是個庫,不是業務,就算return null,也是業務層的事 public T get(int position) { if (position < 0 || position >= mData.length) { throw new RuntimeException("position is invalid"); } // position 大於 mSize 也沒有關係,因為也是返回null,證明沒有獲取到 return (T) mData[position]; } //刪除指定位置的元素 public T remove(int position) { //和上面一樣,位置不合法直接丟擲異常 if (position < 0 || position >= mData.length) { throw new RuntimeException("position is invalid"); } //把當前要刪除的元素儲存下來,最後返回要刪除的元素 T e = (T) mData[position]; //刪除後,把後面的所有元素都往前移位 for (int i = position + 1; i < mData.length; i++) { mData[i - 1] = mData[i]; } //別忘了 mSize 要 -- mSize--; //返回刪除的元素 return e; } //刪除指定的元素 public boolean remove(T e) { //因為陣列可能沒有滿,如果刪除的是null,沒有必要,我們不允許 if (e == null) { return false; } //找到刪除元素的位置 int position = -1; for (int i = 0; i < mData.length; i++) { if (e == mData[i] || e.equals(mData[i])) { position = i; break; } } //沒有找到就返回 if (position == -1) { return false; } //刪除 return remove(position) != null; } //擴容,我們都以2倍的容量擴容 private void resize() { Object[] old = mData; mData = new Object[mData.length * 2]; for (int i = 0; i < old.length; i++) { mData[i] = old[i]; } old = null; } }
註釋都有相關的解釋
我們來測試,測試程式碼如下:
public static void main(String[] args) {
QArrayList<String> list = new QArrayList<>();
list.add("tom");
list.add("jim");
list.add("lilei");
list.add("hanmeimei");
System.out.println("list.get(2)=" + list.get(2));
System.out.println("list.size()=" + list.size());
for (int i = 0; i < list.size(); i++) {
System.out.println("list.get(" + i + ") = " + list.get(i));
}
System.out.println("=======================");
System.out.println("演示刪除操作");
list.remove("jim");
for (int i = 0; i < list.size(); i++) {
System.out.println("list.get(" + i + ") = " + list.get(i));
}
}
輸出如下:
list.get(2)=lilei
list.size()=4
list.get(0) = tom
list.get(1) = jim
list.get(2) = lilei
list.get(3) = hanmeimei
============
演示刪除操作
list.get(0) = tom
list.get(1) = lilei
list.get(2) = hanmeimei
但是最重要的擴容功能還沒有演示,下面是擴容演示的測試程式碼:
擴容前 : list.capacity()=2
擴容後 : list.capacity()=4
list.get(0) = tom
list.get(1) = jim
list.get(2) = lilei
list.get(3) = hanmeimei
可以看到,我們新建了一個底層只有2個元素的陣列,但是我們添加了4個元素,我們打印出擴容後的陣列的容量是 4 ,可見我們的擴容機制是沒有問題的。
以上就是QArrayList的核心原理,我們下節手寫LinkedList的核心原理