Java集合框架-List和Set

Java集合框架1-List和Set

集合框架簡(jiǎn)介

Java提供了一系列的集合,主要包括util包下邊的ArrayList,LinkedList,HashMap,HashTable,HashSet,LinkedHashMap,LinkedHashSet,TreeMap,TreeSet,ArrayDeque,PriorityQueue, EnumMap,Vector,Stack

Concurrent包中的ConcurrentHashMap,CopyOnWriteArrayList,CopyOnWriteArraySet,ArrayBlockingQueue,ConcurrentLinkedDequeue,ConcurrentLinkedQueue

List.png

從上邊的結(jié)構(gòu)構(gòu)可以看出來,Set和List最底層都是實(shí)現(xiàn)了Collection接口的,Collection接口z主要方法如下:


    int size();
    boolean isEmpty();
    boolean contains(Object o);
    Iterator<E> iterator();
    Object[] toArray();
    <T> T[] toArray(T[] a);
    boolean add(E e);
    boolean remove(Object o);
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
    boolean removeAll(Collection<?> c);
    boolean retainAll(Collection<?> c);
    void clear();
    default boolean removeIf(Predicate<? super E> filter)

可以看到,除了常見方法之外,由于Collection繼承了Iterable,所以還有一個(gè)iterator()方法,也就是說,Set和List都可以通過Iterator來遍歷。

ArrayList

ArrayList可以說是最常用的集合了。它的構(gòu)造方法如下:


    private static final int DEFAULT_CAPACITY = 10;

    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    private static final Object[] EMPTY_ELEMENTDATA = {};

    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);
        }
    }

    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }


    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)
            //c.toArray()返回的可能不是Object[],而ArrayList存儲(chǔ)數(shù)據(jù)用的就是Object[],這時(shí)候就需要將elementData轉(zhuǎn)換成Object[]
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }

三個(gè)構(gòu)造方法,第一二兩個(gè),實(shí)際上是創(chuàng)建了一個(gè)可以指定容量的空的Object數(shù)組。區(qū)別在于,無參構(gòu)造函數(shù)將存儲(chǔ)數(shù)組elementData指向了DEFAULTCAPACITY_EMPTY_ELEMENTDATA,它和EMPTY_ELEMENTDATA的區(qū)別馬上我們就可以看到。

第三個(gè)不常用的構(gòu)造函數(shù),可以接受一個(gè)實(shí)現(xiàn)了Collection接口的集合作為參數(shù),并將它的值全部復(fù)制進(jìn)來

List創(chuàng)建完成以后,添加數(shù)據(jù)使用的add方法有以下幾個(gè):

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

兩個(gè)方法基本相同,第二個(gè)方法可以指定位置插入元素,插入之后,后邊的元素index全部增加+1,長(zhǎng)度同時(shí)增加,不指定位置的時(shí)候,在數(shù)組最后一位插入數(shù)據(jù),插入數(shù)據(jù)之前,需要通過ensureCapacityInternal方法來擴(kuò)容,源碼如下:

private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    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);
    }

ensureCapacityInternal方法中,第一步先判斷了是不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA,如果是的話,說明list是通過無參構(gòu)造方法創(chuàng)建的,擴(kuò)容值取DEFAULT_CAPACITYsize+1中的較大者,DEFAULT_CAPACITY值為10,這里就清楚了:

通過new ArrayList(0)new ArrayList()創(chuàng)建的list,初始容量都是0,但是在add一個(gè)元素的時(shí)候,前者容量會(huì)變成0+1=1,而后者會(huì)直接直接將容量變成默認(rèn)值:10

擴(kuò)容最終是通過grow方法實(shí)現(xiàn)的:新的容量先確定為原來的1.5倍,同時(shí)確保擴(kuò)容的容量不能比原來小,也不能超過最大值,確定了長(zhǎng)度之后,再把數(shù)據(jù)拷貝進(jìn)來。

再來看一下移除數(shù)據(jù)的remove方法:

    public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        E oldValue = (E) elementData[index];

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

相比add方法,remove就比較簡(jiǎn)單了,傳入int參數(shù),表示按索引移除,傳入Object表示按值移除,按值移除時(shí),需要先判斷值是不是null,null用 == 來判斷,否則用 equals 方法判斷,最終的移除是通過System.arraycopy(elementData, index+1, elementData, index,numMoved);來實(shí)現(xiàn)的:保留index之前的數(shù)據(jù)不變,從 index+1 開始復(fù)制數(shù)據(jù)到 index 的位置,將最后一位賦值為null(釋放對(duì)象)

LinkedList

LinkedList內(nèi)部實(shí)現(xiàn)使用鏈表的形式,構(gòu)造方法如下:

    public LinkedList() {
    }
    public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }

非常簡(jiǎn)單,除了復(fù)制數(shù)據(jù)之外,沒有任何初始化工作。,下邊看add方法:

    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    void linkLast(E e) {
        final Node<E> l = last;//新建對(duì)象 l 指向原來的鏈表尾部數(shù)據(jù)(last是指向尾部數(shù)據(jù)的成員變量)
        final Node<E> newNode = new Node<>(l, e, null);//新建一個(gè)節(jié)點(diǎn),pre指針指向原來的尾部 l 
        last = newNode;//成員變量last指向新的尾部:newNode
        if (l == null)//如果 l 是空的,說明原來沒有數(shù)據(jù),所以,新建的節(jié)點(diǎn)是頭部
            first = newNode;
        else //不是空的說明原來有數(shù)據(jù),將原來的尾部數(shù)據(jù) l 的next指針指向新的尾部: newNode
            l.next = newNode;
        size++;
        modCount++;
    }

    void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }

如果不指定index,直接將元素添加在鏈表尾部,否則添加在指定位置。

接下來看取值方法get:

    public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }

    Node<E> node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

采用鏈表形式存儲(chǔ)數(shù)據(jù),不能像數(shù)組一樣直接取值了,最終通過node方法實(shí)現(xiàn),實(shí)現(xiàn)方式是二分法,遍歷取值。

HashSet和LinkedHashSet

HashSet構(gòu)造方法如下:

    public HashSet() {
        map = new HashMap<>();
    }

初始化了一個(gè)HashMap,也就是說,HashSet存儲(chǔ)數(shù)據(jù)實(shí)際上使用了HashMap。

對(duì)數(shù)據(jù)的操作方法如下:

    private static final Object PRESENT = new Object();

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

可以說是非常簡(jiǎn)單,全部調(diào)用了HashMap的方法,數(shù)據(jù)作為map的key,value統(tǒng)一為PRESENT。

再看一下LinkedHashSet:

public LinkedHashSet() {
        super(16, .75f, true);
    }

public LinkedHashSet(int initialCapacity) {
        super(initialCapacity, .75f, true);
    }
public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }


HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

實(shí)際上LinkedHashSet創(chuàng)建的時(shí)候,調(diào)用了HashSet的構(gòu)造方法,創(chuàng)建了一個(gè)指定初始容量(默認(rèn)16)和負(fù)載因子(默認(rèn)0.75)的LinkedHashMap。

其他方法全部都是調(diào)用父類HashSet,所以他們兩個(gè)唯一的區(qū)別就是:HashSet是使用HashMap,而LinkedHashSet使用LinkedHashMap。

同樣的,TreeSet數(shù)據(jù)存儲(chǔ)使用的是TreeMap。而ArraySet則是Android提供的,和ArrayMap一樣,都是采用了數(shù)組的形式存儲(chǔ)數(shù)據(jù),占用內(nèi)存更小一些。

List 和 Set 的遍歷以及數(shù)據(jù)操作

List 和 Set 都實(shí)現(xiàn)了Iteratorable接口,使用Iterator遍歷方法如下:

        ArrayList<String> s = new ArrayList<>();
        s.add("a");
        s.add("b");
        s.add("c");
        s.add("d");
        s.add("e");
        Iterator<String> it = s.iterator();

        while (it.hasNext()){
            String s1 = it.next();
            System.out.println(s1);
        }

如果在遍歷的過程中對(duì)數(shù)據(jù)進(jìn)行操作,看下邊的例子:

  Iterator<String> it = s.iterator();

    while (it.hasNext()){
        String s1 = it.next();
        if("a".equals(s1)){
            s.remove(s1);
        }
    }


    new Thread(new Runnable() {
            @Override
            public void run() {
                Iterator<String> it = s.iterator();
                while (it.hasNext()) {
                    String s1 = it.next();
                    System.out.println(s1);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                Iterator<String> it = s.iterator();
                while (it.hasNext()) {
                    String s1 = it.next();
                    if ("e".equals(s1)) {
                        it.remove();
                    }
                }
            }
        }).start();

運(yùn)行以后會(huì)拋出下邊的錯(cuò)誤:

Exception in thread "main" java.util.ConcurrentModificationException

第一種情況是在使用Iterator的時(shí)候?qū)线M(jìn)行操作,使用集合的remove或者add方法,第二種情況是在不同線程同時(shí)使用Iterator遍歷,使用Iterator的remove方法,這兩種情況都會(huì)拋出ConcurrentModificationException異常。

下面看ArrayList的源碼:

    int expectedModCount = modCount;
    public Iterator<E> iterator() {
        return new Itr();
    }
    private class Itr implements Iterator<E> {
        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];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
                limit--;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }
     }

可以看到,每次使用next取值之前,都會(huì)檢查modCountexpectedModCount是否相等,expectedModCount是Itr的成員變量,在創(chuàng)建Iterator對(duì)象時(shí)它的值就確定了,Itr類內(nèi)部也不會(huì)對(duì)它進(jìn)行操作,它的值時(shí)不變的,就是創(chuàng)建對(duì)象時(shí)的modCount,拋出異常說明modCount的值變了。再回頭去看一下ArrayList的add,remove方法,就會(huì)發(fā)現(xiàn),每次操作modCount的值都會(huì)改變,所以才導(dǎo)致異常。

如果是單線程中,可以使用Iterator的remove方法,可以看到,在remove方法刪除元素之后,又對(duì)expectedModCount進(jìn)行了復(fù)制,所以并不會(huì)報(bào)錯(cuò)。

如果是多線程,Iterator對(duì)象是不同的,一個(gè)對(duì)象調(diào)用remove之后,僅僅能保證本對(duì)象中的expectedModCount正確,其他線程中的Iterator對(duì)象是不會(huì)更新的,所以仍然會(huì)報(bào)錯(cuò)。多線程可以用concurrent包下的并發(fā)容器,

另外,數(shù)組實(shí)現(xiàn)的ArrayList,查找會(huì)比較快,但是數(shù)據(jù)量大的時(shí)候插入數(shù)據(jù)會(huì)比較慢,因?yàn)槊看味家截悢?shù)據(jù),而鏈表實(shí)現(xiàn)的LinkedList,查找時(shí)需要對(duì)鏈表進(jìn)行遍歷嗎,所以會(huì)比較慢,而插入數(shù)據(jù)很快。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 友情提示:文章可能有點(diǎn)長(zhǎng),各種集合的實(shí)現(xiàn)原理我用自己的理解去解釋了,配合源碼食用更佳。 一、Java集合框架簡(jiǎn)述 ...
    Marker_Sky閱讀 1,431評(píng)論 1 2
  • 概要 上一章,我們學(xué)習(xí)了Collection的架構(gòu)。這一章開始,我們對(duì)Collection的具體實(shí)現(xiàn)類進(jìn)行講解;首...
    廢棄的root閱讀 990評(píng)論 0 4
  • Collection接口 Collection接口是所有集合的祖先類。他有兩個(gè)構(gòu)造方法,一個(gè)無參構(gòu)造,一個(gè)是帶Co...
    夜幕繁華閱讀 682評(píng)論 0 0
  • 原文地址 Java集合 Java集合框架:是一種工具類,就像是一個(gè)容器可以存儲(chǔ)任意數(shù)量的具有共同屬性的對(duì)象。 Ja...
    gyl_coder閱讀 1,034評(píng)論 0 8
  • 集合類框架的介紹: ![Java 集合類框架](https://upload-images.jianshu.io/...
    LynnGuo閱讀 799評(píng)論 0 1

友情鏈接更多精彩內(nèi)容