數據結構特性解析 (二) ArrayList

前言

ArrayList可能是Java中使用次數最多的數據結構了,因此瞭解其特性能比較重要

描述

ArrayList是一個數組隊列,相當於動態數組.與Java中的數組相比,它的容量能動態增長.

並且ArrayList還有一些添加,遍歷和移除的操作

特點

1.ArrayList內部實現是利用Java的數組

這是內部存儲數據的Object數組

add方法的實現方式,根據源碼可以看到,先判斷了一下容量,然後在當前已存放數據的size+1的位置設置爲傳過來的數據,並且size+1

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 檢查容量(擴充機制)下面再說
        elementData[size++] = e;
        return true;
    }

2.ArrayList的空參構造默認長度不是10,而是0

ArrayList一共有三個構造方法

//1設置初始化容量
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

//2默認的無參構造
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

//3傳入一個Collection對象
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

首先第三個構造不多說了,是把別的集合轉換爲ArrayList內部的數組,然後如果有長度在設置一下size,跟本條關係不大

然後第一個構造,是指定默認的數組長度,小於0拋異常,等於零則設置內部數組爲靜態的length爲0的空數組

private static final Object[] EMPTY_ELEMENTDATA = {}; 

重點是第二個構造

 

3.ArrayList的擴容方式,是先創建一個更大的數組,然後把舊數組內容拷貝過去,在把內部數組設置爲新的大數組

我們看其add方法實現

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 檢查自身的可用容量
        elementData[size++] = e;
        return true;
    }

如果ensureCapacityInternal檢查到容量不夠,最終會調用如下代碼

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//記錄當前的長度
        int newCapacity = oldCapacity + (oldCapacity >> 1);// >> 符號(右位移),相當於除2,所以擴容是1+0.5=1.5倍
        if (newCapacity - minCapacity < 0)//如果擴容後,容量還小於指定容量,就直接設置爲指定容量,比如使用空參構造new,size爲0,乘1.5還是0,就會在第一次擴容的時候設置爲10
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)//長度過長檢查,如果長度溢出了int的值,就會變成負數導致拋異常(但說實話,它這個判斷還是有可能會int溢出)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//進行數組拷貝工作,容量爲新擴容的大小
    }

所以,如果事先知道元素的大小或大概的大小,可以在構造中傳入,就能減少擴容次數,能有效節省時間和減少內存消耗

4.ArrayList的indexOf實現是遍歷

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

可以看到,indexOf是從前向後遍歷,而lastIndexOf是從後向前遍歷,所以如果你大概知道想查找的元素在前或者後,使用對應的方法,可以更節省時間

5.因爲內部使用的是數組,所以其特性也和數組特性類似

ArrayList根據索引的方式去查找數據,會比較快,不用進行循環或多次尋址

而遍歷查詢則需要遍歷索引來循環從數組中取出

6.ArrayList是線程不安全的

內部沒有使用鎖或同步機制,如果想要使用線程安全的ArrayList(類似特性),可以使用Collections.synchronizedList

7.ArrayList不支持forEach時修改數據

forEach其實是java的語法糖,內部使用的是迭代器Iterator,通過hasNext()和next()方法,來遍歷數據

    private class Itr implements Iterator<E> {ArrayList的內部類迭代器
        protected int limit = ArrayList.this.size;

        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor < limit;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            int i = cursor;
            if (i >= limit)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

    }

而內部有一個modCount變量,標識的是自身修改的次數,會在擴容時,remove時和clear時進行+1操作,而在迭代器內next()方法取數據的時候,會判斷modCount變量,看看到底修沒修改過原數據,如果修改過就會拋異常(這是爲了數據的準確性)

如果想要避免上述問題,可以使用CopyOnWriteArrayList(線程安全,支持forEach時修改數據)

 

數據結構特性解析 (一) 數組

數據結構特性解析 (二) ArrayList

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