Java集合-Collection

Java集合-Collection

一、Collection繼承關(guān)系

image-20200920144019585.png

圖片來源

由上圖可知Collection有三個(gè)子類,分別是Set、List、Queue。
特點(diǎn):
Set:無序且值唯一
List:有序、值可重復(fù)
Queue:先進(jìn)先出的線性表

二、Collection提供的方法

8F9BF2C9-EC1B-4461-951A-A9F5DA1E9B07.png

Collection提供了對(duì)集合的通用操作

三、Collection子類

1、Set

無序且值唯一。

Set子類有:

HashSet

底層數(shù)據(jù)結(jié)構(gòu)是哈希表(實(shí)際是hashMap),從構(gòu)造函數(shù)可以看出在創(chuàng)建實(shí)例時(shí)會(huì)創(chuàng)建一個(gè)HashMap,該HashMap就是用來實(shí)際存儲(chǔ)元素的,除此之外在創(chuàng)建HashSet實(shí)例時(shí)我們可以指定其內(nèi)部HashMap的容量和加載因子(默認(rèn)大小為16,加載因子為0.75)

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

再來看下增刪查數(shù)據(jù)是如何實(shí)現(xiàn)的:

  • add操作
    public boolean add(E e) {
       //add是調(diào)用HashMap的put操作
        return map.put(e, PRESENT)==null;
    }
  • remove操作
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }
  • contains操作
public boolean contains(Object o) {
    return map.containsKey(o);
}

HashSet如何來保證元素唯一性? 1.依賴兩個(gè)方法:hashCode()和equals()。

可選鏈接

TreeSet

TreeSet是一個(gè)非同步的非線程安全的二叉樹,底層數(shù)據(jù)結(jié)構(gòu)是紅黑樹。(唯一,排序),其add , remove和contains操作的時(shí)間復(fù)雜度為log(n)

來看下默認(rèn)構(gòu)造函數(shù):

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
    
    private transient NavigableMap<E,Object> m;
    private static final Object PRESENT = new Object();
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }

可以看出其內(nèi)部默認(rèn)是使用TreeMap存儲(chǔ)元素的,因?yàn)槠鋬?nèi)部元素是有序的,對(duì)于元素的排序有兩種方式自然排序和比較器排序,自然排序就是當(dāng)comparator為空的時(shí)候,構(gòu)建無參構(gòu)造函數(shù)的時(shí)候默認(rèn)的一種排序方式,比較器排序就是在構(gòu)造函數(shù)中傳入comparator從而指定排序方式。

        treeSet = new TreeSet<>(new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length()-o2.length();
            }
        });

TreeSet保證元素唯一性的是通過比較的返回值是否是0來決定

可選閱讀鏈接

LinkedHashSet

Set接口的哈希表和鏈接列表實(shí)現(xiàn)即保證插入順序,(FIFO插入有序,唯一)由鏈表保證元素有序由哈希表保證元素唯一。linkedHashSet是一個(gè)非線程安全的集合。如果有多個(gè)線程同時(shí)訪問當(dāng)前l(fā)inkedhashset集合容器,并且有一個(gè)線程對(duì)當(dāng)前容器中的元素做了修改,那么必須要在外部實(shí)現(xiàn)同步

來看下其構(gòu)造函數(shù)

    public LinkedHashSet() {
        super(16, .75f, true);
    }
    
     HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

LinkedHashSet父類為HashSet,然后在HashSet的構(gòu)造函數(shù)中創(chuàng)建了LinkedHashMap實(shí)例,也就是說LinkedHashSet最終是使用LinkedHashMap來存儲(chǔ)元素。

Set小結(jié)

我們簡(jiǎn)紹了三種Set在實(shí)際使用時(shí)可以根據(jù)需求選擇合適的,同時(shí)我們也看到這三種Set的實(shí)現(xiàn)最終都是通過Map來存儲(chǔ)元素的。

2、List

List鏈表是一種線性結(jié)構(gòu),其內(nèi)部元素有序(插入有序)、不唯一,可以根據(jù)索引來查找獲取數(shù)據(jù)。

ArrayList

底層通過數(shù)組實(shí)現(xiàn),查找快增刪慢,線程不安全。來看下默認(rèn)構(gòu)造函數(shù)

    transient Object[] elementData;
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

可以看到存儲(chǔ)元素的是一個(gè)叫做elementData的數(shù)組。

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

我們看到在增加元素前會(huì)先調(diào)用 ensureCapacityInternal來確保數(shù)組elementData有足夠的空間,如果空間不足會(huì)進(jìn)行擴(kuò)容操作。

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //判斷是否需要擴(kuò)容
        ensureExplicitCapacity(minCapacity);
    }
    
        private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // 需要擴(kuò)容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    
        private void grow(int minCapacity) {
        // 當(dāng)前數(shù)組大小
        int oldCapacity = elementData.length;
        //擴(kuò)容為原來的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);     //擴(kuò)容后還不滿足所需最小容量則把容量設(shè)置為所需最小容量
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
            //MAX_ARRAY_SIZE的值為Integer.MAX_VALUE - 8表示最大可設(shè)置的值
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // 真正擴(kuò)容操作是通過Arrays.copyOf來完成的
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // 溢出
            throw new OutOfMemoryError();
            //所需最小容量大于MAX_ARRAY_SIZE則擴(kuò)容為Integer.MAX_VALUE
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

再來看下在指定位置插入元素的操作

    public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        //檢查是否需要擴(kuò)容
        ensureCapacityInternal(size + 1);  
        //把插入位置后面所有元素后移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
         //插入元素                
        elementData[index] = element;
        size++;
    }
  • remove操作
    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;
    }

可以看到remove中是根據(jù)equals來判斷元素是否是要?jiǎng)h除的,具體移除操作是通過fastRemove來完成。

    private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //把移除位置之后所有元素向前移動(dòng)一位
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

總體來說ArrayList底層采用數(shù)組存儲(chǔ)元素在元素增刪時(shí)通過copy數(shù)組來實(shí)現(xiàn)元素移動(dòng),其增刪操作的時(shí)間復(fù)雜度為O(n)。

可選閱讀鏈接

Vector

底層數(shù)組實(shí)現(xiàn),查找快增刪慢,線程安全。構(gòu)造函數(shù)

    public Vector() {
        this(10);
    }
    public Vector(int initialCapacity) {
        this(initialCapacity, 0);
    }
    //這里的capacityIncrement是指擴(kuò)容時(shí)增加的容量
    public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

因?yàn)閂ector底層也是數(shù)組實(shí)現(xiàn),所以在增刪數(shù)據(jù)時(shí)會(huì)涉及到數(shù)組容量的變化,這跟ArrayList類似下面是Vector擴(kuò)容的核心內(nèi)容,可以看出其在容量不足時(shí)會(huì)增加capacityIncrement的容量,如果capacityIncrement<0則直接增加一倍的容量。

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

Vector實(shí)現(xiàn)跟ArrayList類似最大的不同在于Vector是線程安全的。

可選閱讀鏈接

stack

先進(jìn)后出的結(jié)構(gòu),stack中peek函數(shù)是查看棧頂元素但并不移除,pop是彈出棧頂元素。

其構(gòu)造函數(shù)是空實(shí)現(xiàn)

    public Stack() {
    }
  • push操作
    public E push(E item) {
        addElement(item);
        return item;
    }
    
        public synchronized void addElement(E obj) {
        modCount++;
        //檢查是否需要擴(kuò)容
        ensureCapacityHelper(elementCount + 1);
        //存入數(shù)據(jù)
        elementData[elementCount++] = obj;
    }
  • pop操作
    public synchronized E pop() {
        E       obj;
        int     len = size();
        obj = peek();
        removeElementAt(len - 1);
        return obj;
    }
        public synchronized void removeElementAt(int index) {
        modCount++;
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " +
                                                     elementCount);
        }
        else if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(index);
        }
        int j = elementCount - index - 1;
        if (j > 0) {
           //移動(dòng)數(shù)據(jù)
            System.arraycopy(elementData, index + 1, elementData, index, j);
        }
        elementCount--;
        //將刪除位置置空
        elementData[elementCount] = null; /* to let gc do its work */
    }
  • peek操作
    public synchronized E peek() {
        int     len = size();
        if (len == 0)
            throw new EmptyStackException();
        return elementAt(len - 1);
    }
    
    public synchronized E elementAt(int index) {
        if (index >= elementCount) {
            throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
        }
        return elementData(index);
    }
    
        E elementData(int index) {
        return (E) elementData[index];
    }

LinkedList

底層雙鏈表實(shí)現(xiàn),查找慢增刪快,線程不安全,LinkedList同時(shí)實(shí)現(xiàn)了List, Deque兩個(gè)接口也就是說它既可以作為list也可作為deque使用。

既然是雙鏈表則會(huì)有節(jié)點(diǎn)的概念,我們來看下它的Node,這是LinkedList的一個(gè)內(nèi)部類。

       private static class Node<E> {
        E item;
        Node<E> next;
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
  • add操作
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    //在表尾插入一個(gè)Node
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }
  • add(index,obj)
    public void add(int index, E element) {
        //檢查插入位置是否合法
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }
    //插入鏈表
     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++;
    }
  • remove操作

        public boolean remove(Object o) {
            if (o == null) {
                for (Node<E> x = first; x != null; x = x.next) {
                    if (x.item == null) {
                        unlink(x);
                        return true;
                    }
                }
            } else {
               //先查找到要?jiǎng)h除節(jié)點(diǎn)
                for (Node<E> x = first; x != null; x = x.next) {
                    if (o.equals(x.item)) {
                       //移除節(jié)點(diǎn)
                        unlink(x);
                        return true;
                    }
                }
            }
            return false;
        }
        
         E unlink(Node<E> x) {
            // assert x != null;
            final E element = x.item;
            final Node<E> next = x.next;
            final Node<E> prev = x.prev;
            //修改前驅(qū)指針
            if (prev == null) {
                first = next;
            } else {
                prev.next = next;
                x.prev = null;
            }
            //修改后繼指針
            if (next == null) {
                last = prev;
            } else {
                next.prev = prev;
                x.next = null;
            }
    
            x.item = null;
            size--;
            modCount++;
            return element;
        }
    

    可以看出LinkedList的數(shù)據(jù)操作大多都是鏈表的操作所以其特點(diǎn)是增刪快查找慢,在類內(nèi)部LinkedList維護(hù)了first和last兩個(gè)指針,這也是其能實(shí)現(xiàn)deque功能的基礎(chǔ)。在作為deque時(shí)offer表示在隊(duì)尾入隊(duì)一個(gè)元素,poll是出隊(duì)隊(duì)首一個(gè)元素,peek是查看隊(duì)首元素但并不出隊(duì)。在作為deque時(shí)無法調(diào)用list相關(guān)接口方法。

3、Queue

隊(duì)列是一種先進(jìn)先出的線性結(jié)構(gòu),不支持隨機(jī)訪問數(shù)據(jù)。

PriorityQueue

優(yōu)先隊(duì)列是基于堆實(shí)現(xiàn)的,對(duì)內(nèi)元素是有序的,offer,poll,remove和add等方法提供了O(log(n))的時(shí)間復(fù)雜度 ,而remove(obj)和contains方法的時(shí)間復(fù)雜度是O(n),peek時(shí)間復(fù)雜度為O(1)。排序是通過自然排序和比較器排序?qū)崿F(xiàn)的,采用哪種排序是通過構(gòu)造函數(shù)確定的,其中自然排序要求元素實(shí)現(xiàn)compare函數(shù),比較排序則需要在構(gòu)造函數(shù)中指明排序規(guī)則。

默認(rèn)構(gòu)造函數(shù)

private static final int DEFAULT_INITIAL_CAPACITY = 11;

public PriorityQueue() {
    this(DEFAULT_INITIAL_CAPACITY, null);
}

 public PriorityQueue(int initialCapacity,
                         Comparator<? super E> comparator) {
        //可以看出內(nèi)部采用數(shù)組存儲(chǔ)
        if (initialCapacity < 1)
            throw new IllegalArgumentException();
        this.queue = new Object[initialCapacity];
        this.comparator = comparator;
 }
  • offer
    public boolean offer(E e) {
        if (e == null)
            throw new NullPointerException();
        modCount++;
        int i = size;
        if (i >= queue.length)
            //擴(kuò)容
            grow(i + 1);
        size = i + 1;
        if (i == 0)
            queue[0] = e;
        else
            //入隊(duì)
            siftUp(i, e);
        return true;
    }
    
    private void siftUp(int k, E x) {
        if (comparator != null)
            siftUpUsingComparator(k, x);
        else
            //這里以分析siftUpComparable為例
            siftUpComparable(k, x);
    }
    
    private void siftUpComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>) x;
        while (k > 0) {
            //找k位置的父節(jié)點(diǎn)的index
            int parent = (k - 1) >>> 1;
            //k位置的父節(jié)點(diǎn)
            Object e = queue[parent];
            //調(diào)整堆,大于父節(jié)點(diǎn)的就不動(dòng),小于父節(jié)點(diǎn)的就上浮
            if (key.compareTo((E) e) >= 0)
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = key;
    }
  • poll操作
    public E poll() {
        if (size == 0)
            return null;
        int s = --size;
        modCount++;
        E result = (E) queue[0];
        E x = (E) queue[s];
        queue[s] = null;
        if (s != 0)
            //調(diào)整堆
            siftDown(0, x);
        return result;
    }
    
        private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }
    
        private void siftDownComparable(int k, E x) {
        Comparable<? super E> key = (Comparable<? super E>)x;
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1; // assume left child is least
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
                c = queue[child = right];
            if (key.compareTo((E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = key;
    }

可選閱讀鏈接

ArrayDeque

雙端隊(duì)列,底層數(shù)組實(shí)現(xiàn)。

默認(rèn)構(gòu)造函數(shù)

    public ArrayDeque() {
        //數(shù)組大小默認(rèn)16
        elements = new Object[16];
    }

因?yàn)榭梢噪p端操作數(shù)據(jù)所以其內(nèi)部采用head和tail來存儲(chǔ)頭尾元素的index這樣就可以快鎖找到頭尾元素。ArrayDeque還規(guī)定elements的size必須是2的整數(shù)次冪,當(dāng)我們?cè)O(shè)置容量大小不是2的整數(shù)次冪時(shí)會(huì)進(jìn)行調(diào)整

    public ArrayDeque(int numElements) {
        allocateElements(numElements);
    }
    
        private void allocateElements(int numElements) {
        int initialCapacity = MIN_INITIAL_CAPACITY;
        // Find the best power of two to hold elements.
        // Tests "<=" because arrays aren't kept full.
        if (numElements >= initialCapacity) {
            initialCapacity = numElements;
            initialCapacity |= (initialCapacity >>>  1);
            initialCapacity |= (initialCapacity >>>  2);
            initialCapacity |= (initialCapacity >>>  4);
            initialCapacity |= (initialCapacity >>>  8);
            initialCapacity |= (initialCapacity >>> 16);
            initialCapacity++;

            if (initialCapacity < 0)    // Too many elements, must back off
                initialCapacity >>>= 1; // Good luck allocating 2^30 elements
        }
        elements = new Object[initialCapacity];
    }

allocateElements實(shí)現(xiàn)思路如下:

1.要明確2整數(shù)次冪使用二進(jìn)制的表現(xiàn)形式如下:0...010...0,中間有一個(gè)1,其它的都是0。

2.根據(jù)1的形式,計(jì)算使輸入任意的X,等式成立的Y。X的二進(jìn)制形式為????????,是一個(gè)未知數(shù),這樣如何求得Y呢?方法很簡(jiǎn)單,找到X最高位為1的位置:那么X就是0..001???,這種形式了。那么所求的Y就是0..010...0,其值就是比X最高位為1再高一位為1,其它位為0的值。

3.X的最高為1的那一位是未知的,如何求更高一位為1的Y呢?直接求是沒有辦法的,但是可以通過將X最高位為1后面所有位都變成1,再加1進(jìn)位的方式辦到。就是0..001???變成0.001..1,使用這個(gè)+1就會(huì)變成所要的Y:0.010...0了。

4.如何保證X最高位為1后面都是1呢?這個(gè)就是上面位運(yùn)算所實(shí)現(xiàn)的內(nèi)容了。假設(shè)X是0..01???,左移一位就是0.001??,做或運(yùn)算就變成了0..011??,是不是很巧妙,出現(xiàn)了兩位為1的就移動(dòng)2位,獲得四位為1的值,這樣移動(dòng)到16的時(shí)候就涵蓋了32位整數(shù)的所有范圍了。這個(gè)時(shí)候+1可能發(fā)生整數(shù)溢出,所以再左移一位保證在整數(shù)范圍內(nèi)。

參考

  • addFirst
    public void addFirst(E e) {
        if (e == null)
            throw new NullPointerException();
         //(head - 1) & (elements.length - 1)的作用是確定head的index
        elements[head = (head - 1) & (elements.length - 1)] = e;
        //首尾指向同一位置 擴(kuò)容至原先兩倍大小
        if (head == tail)
            doubleCapacity();
    }
    
       private void doubleCapacity() {
        assert head == tail;
        int p = head;
        int n = elements.length;
        int r = n - p; // number of elements to the right of p
        int newCapacity = n << 1;
        if (newCapacity < 0)
            throw new IllegalStateException("Sorry, deque too big");
        Object[] a = new Object[newCapacity];
        System.arraycopy(elements, p, a, 0, r);
        System.arraycopy(elements, 0, a, r, p);
        elements = a;
        head = 0;
        tail = n;
    }
  • pollFirst
    public E pollFirst() {
        final Object[] elements = this.elements;
        final int h = head;
        @SuppressWarnings("unchecked")
        E result = (E) elements[h];
        // Element is null if deque empty
        if (result != null) {
            elements[h] = null; // Must null out slot
            head = (h + 1) & (elements.length - 1);
        }
        return result;
    }

在addFirst中(head - 1) & (elements.length - 1)操作主要是確定入隊(duì)的隊(duì)首元素的位置,該操作相當(dāng)于取模操作同時(shí)還很好的處理了head-1是-1的情況(head-1是-1時(shí)該操作的結(jié)果是elements.length - 1)

最后編輯于
?著作權(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ù)。

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