java實現ArrayList

在java中,集合的操作 可以說是在平常不過了。對於集合可能大部分情況下都只是掌握它們的使用,其實對於它們的內部實現還是有必要了解的。這樣對於學習java是一種提升。那麼下面我們來學習一下ArrayList,Stack,linkedlist,hashMap四種集合框架的內部實現。
首先我們從最簡單的開始ArrayList,顧名思義是數組集合,它的內部實現是基於數組的,也就是說內存空間地址是連續的,這中線性結構對於我們隨機訪問是非常高效的。觀看源碼可以發現,這個類的設計先繼承一個抽象類,然後抽象類繼承List接口,這裏抽象類的對於就是可以先實現一些簡單的方法,不想實現的可以繼續交給子類來實現。但是本節重點在於怎麼實現,所以我們將抽象類這個環節拋棄,直接定義接口,實現接口重而實現ArrayList。代碼如下:詳細註釋。

package com.yxc.util;

/**
 * 自己實現ArrayList集合
 * 定義一個頂層接口,定義各種方法
 * 我們這裏使用泛型編程,方便後面的書寫
 * 這裏我們繼承Iterable接口,這樣我們就可以實現遍歷
 */
public interface MyList<E> extends Iterable<E>{
    /**
     * 在集合末尾添加一個元素
     * @param elem
     */
    public void add(E elem);

    /**
     * 在結合特定的位置添加一個元素
     * @param index
     * @param elem
     */
    public void add(int index,E elem);

    /**
     * 獲取指定位置的元素
     * @param index
     * @return E
     */
    public E get(int index);

    /**
     * 設置指定位置的值,然後返回原來的元素
     * @param index
     * @param e
     * @return
     */
    public E set(int index,E e);

    /**
     * 從前往後遍歷查找元素,返回下標
     * @param elem
     * @return index
     */
    public int indexOf(E elem);

    /**
     * 從後往前索引元素,返回下標
     * @param elem
     * @return index
     */
    public int lastIndexOf(E elem);

    /**
     * 判斷集合中是不是包含某一對象
     * @return
     */
    public boolean Contain(E e);

    /**
     * 刪除指定小標的元素 返回被刪除元素
     * @param index
     * @return
     */
    public E remove(int index);

    /**
     * 刪除指定元素,返回是不是成功
     * @param elem
     * @return
     */
    public boolean remove(E elem);

    /**
     * 集合的長度
     * @return
     */
    public int size();

    /**
     * 清空集合
     */
    public void clear();

    /**
     * 判斷集合是不是爲空
     * @return
     */
    public boolean isEmpty();

    /**
     * 將容量和真實大小統一,只用在數組上面。
     */
    public void trimToSize();

}

實現類:

package com.yxc.util;

import java.util.Iterator;

/**
 * 具體的實現類,在java源碼中,其實還有一個抽象類的,抽象類的作用就是先完成一些簡單的實現,
 * 不想實現的方法可以先不實現,這裏我們爲了學習簡單點,直接實現所以的方法
 */
public class MyArrayList<E> implements MyList<E>{
    //首先線性表是基於數據實現的,所以要定義數據的長度和初始化數組
    public static final int INIT_CAPACITY=15;  //默認數組的空間大小
    //初始化集合的大小爲0
    public int size=0;
    //因爲我們在類上面都是用了泛型,所以這裏定義數據我們可以直接使用泛型
    public E elements[]=(E[])new Object[INIT_CAPACITY];

    /**
     * 對於數組的操作,肯定就會有容量的問題,所以肯定要有一個方法來實現擴容的操作
     * 傳入的參數是新的容量
     */
    public void ensureCapacity(int newCapacity){
        //如果我們擴容的數量小於數據的長度,那麼顯然擴容失敗
        if(elements.length>=newCapacity){
            return;
        }
        //如果正常情況,那麼就需要擴容
        //擴容就是創建一個新的數組,容量比之前的大,而且要將原來數組的元素複製進去
        //首先要將原來的數組保存一下
        E[] oldElement=elements;
        //創建一個新的數組,容量就是擴容以後的長度
        E[] newElements=(E[])new Object[newCapacity];
        //接着就是就是將原數組中的元素複製過來 我們可以使用for循環,但是這邊我們有一個方法
        System.arraycopy(oldElement,0,newElements,0,size);
        //將新的數據賦值引用給elements
        elements=newElements;
        //釋放對象,這個引用只是爲了暫時保存變量,使用以後就可以釋放
        oldElement=null;
    }

    //對於新增,插入都會有一個index檢查,如果越界那就是非法操作
    public void checkIndex(int index){
        if(index<0||index>size){
            throw new IndexOutOfBoundsException("下標越界 請檢查下標的合法"+index);
        }
    }

    @Override
    public void add(E elem) {
        //有了下面的方法以後,那麼這個方法就很好獲取了
        //直接在尾部插入元素
        add(size,elem);
    }

    @Override
    public void add(int index, E elem) {
        //首先檢查下標的合法性
        checkIndex(index);

        //如果容量不夠,擴容 默認大小兩倍
        if(size==elements.length){
            ensureCapacity(INIT_CAPACITY*2+1);
        }
        //添加一個元素,首先就是將要添加位置的元素往後移動一位,然後將要添加的元素直接複製給index位置
        //可以使用循環賦值,這裏還是使用數組的複製方法
        System.arraycopy(elements,index,elements,index+1,size-index);
        //然後將值賦值給指定位置
        elements[index]=elem;
        //最後長度要++
        size++;
    }

    @Override
    public E get(int index) {
        checkIndex(index);
        return elements[index];
    }

    @Override
    public E set(int index, E e) {
        checkIndex(index);
        //用一個變量保存好index位置的數據
        E temp=elements[index];
        //將新元素添加進去
        elements[index]=e;
        //最後返回原來的數據
        return temp;
    }

    @Override
    public int indexOf(E elem) {
        //循環查找元素
        for(int i=0;i<elements.length;i++){
            //如果找到直接返回序下標,不然返回-1
            if(elem.equals(elements[i])){
                return i;
            }
        }
        return -1;
    }

    @Override
    public int lastIndexOf(E elem) {
        //從集合的最後開始遍歷
        for(int i=elements.length-1;i>=0;i--){
            if(elem.equals(elements[i])){
                return i;
            }
        }
        return -1;
    }

    @Override
    public boolean Contain(E e) {
        int i = indexOf(e);
        return i!=-1;
    }

    @Override
    public E remove(int index) {
        //刪除一個元素,返回刪除的元素
        checkIndex(index);
        //將要刪除的元素保存起來
        E temp=elements[index];
        //然後移動數組元素,我們還是使用arraycopy函數,可以使用for循環
        System.arraycopy(elements,index+1,elements,index,size-index-1);
        //最後記得size--
        size--;
        //返回我們刪除了的元素
        return temp;
    }

    @Override
    public boolean remove(E elem) {
        //先將元素查找到,如果不存在,那麼直接刪除失敗,如果存在,調用它的重載方法直接刪除就可以
        int index = indexOf(elem);
        if(index!=-1){
            remove(index);
            return true;
        }
        return false;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        //清空集合操作就是將集合長度設置爲0,然後對集合的容量重新設置
      size=0;
      elements=(E[])new Object[INIT_CAPACITY];
    }

    @Override
    public boolean isEmpty() {
        //判斷集合長度是不是爲空就可以
        return size==0;
    }

    @Override
    public void trimToSize() {
        //這個方法是讓集合的容量和真實大小統一
        ensureCapacity(size);
    }

    //MyList中實現了迭代器的接口,所以這裏會實現這個方法
    //對於這個方法的實現我們需要創建一個內部類
    //使用內部類的好處就是外部類中的變量可以直接使用
    @Override
    public Iterator<E> iterator() {
        //需要返回一個Iterator,所以我們這裏寫一個內部類
        return new MyIterator();
    }
    //內部類實現Iterator接口,實現裏面的兩個方法
    class MyIterator implements Iterator<E>{
        //當前迭代器指向0
        private int curIndex=0;
        @Override
        public boolean hasNext() {
            //是不是還有下一個元素
            //只要當前下標的值小於集合的長度,那麼說明還有下一個元素
            return curIndex<size;
        }
        //返回下一個元素
        @Override
        public E next() {
            //如果有下一個元素,那麼將值返回 否則拋出異常。
            if(!hasNext()){
                throw new IndexOutOfBoundsException("越界了");
            }
            //返回以後下標要自增
            return elements[curIndex++];
        }
    }
}


測試類:

package com.yxc.util;

import java.util.Iterator;

/**
 * 測試自己手寫的ArrayList是不是有效
 */
public class ArrayListTest {
    public static void main(String[] args) {
        MyList<Integer> myList=new MyArrayList<Integer>();
        for(int i=0;i<20;i++){
            myList.add(i);
        }
        for (int i=0;i<myList.size();i++){
            System.out.println(myList.get(i));
        }
        //檢查checkIndex的用處
        // System.out.println(myList.get(10));
        //下面測試一些方法
//        System.out.println(myList.size());
//        System.out.println(myList.isEmpty());
//        System.out.println(myList.indexOf(3));
//        System.out.println(myList.lastIndexOf(1));
//        System.out.println(myList.Contain(3));

//        Integer elem = myList.remove(3);
//        System.out.println(elem);
//        System.out.println(myList.size());
//        myList.remove(1);
//        System.out.println(myList.size());

        //下面我們測試一下自己寫的迭代器
        Iterator<Integer> iterator = myList.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }


    }

}

其實如果學過數據結構,那麼這一個實現是很簡單的,比較困難的地方可能就是擴容以及有一個函數的使用System.arraycopy()因爲參數有點多
這是一個複製數組的函數 。我們這裏舉個例子:假設有數組
int[] a=new int[]{1,4,5,8,2,6,7};
int[] b=new int[7];
我們現在想要把數組a中的元素複製到數組 b中,那麼函數就可以這樣使用
System.arraycopy(a,0,b,0,a,length)
數組a中的元素從第零號開始複製到數組b中0號開始,複製的長度是a數組的長度。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章