JAVA基礎第四章-集合框架Collection篇 JAVA基礎第一章-初識java JAVA基礎第二章-java三大特性:封裝、繼承、多態 JAVA基礎第三章-類與對象、抽象類、接口 記一次list

 業內常常說的一句話是不要重複造輪子,可是有時候,只有本身造一個輪子了,纔會深入明白什麼樣的輪子適合山路,什麼樣的輪子適合平地!html

我將會持續更新java基礎知識,歡迎關注。java

 

往期章節:node

JAVA基礎第一章-初識java面試

JAVA基礎第二章-java三大特性:封裝、繼承、多態數組

JAVA基礎第三章-類與對象、抽象類、接口

 


 

提及集合框架,不少面試官在面試初級javaer的時候也是很喜歡問的一個知識點安全

咱們先上一張圖看看app

從上面的關係圖中,咱們能夠看到從上往下分呢~最上面的是接口,中間是抽象類,最下面就是各個具體的實現類,這個在咱們上一章節中說到的抽象類與接口之間的關係的時候有提到過。框架

再從左往右看呢,也是大體分三塊,Iterator,Collection,Map 三個最頂級的接口,可是最主要的部分仍是Collection,Map 2個接口,而Iterator更多的像是附加產品。less

 

Collection函數

咱們先看看Collection接口,從這個接口往下,他的子接口有List、Set、Queue。

List

List的具體實現類有 ArrayList、LinkedList,Vector。

ArrayList 

顧名思義,是一個「數組型」的集合,對於數組,咱們應該知道的是,數組在定義的時候就肯定了大小,不可更改。那優勢就是數組的元素是經過索引訪問的,效率較高,由於數組是一塊連續的存儲空間。

因此呢,ArrayList 就是在數組的基礎上,增長了能夠改變大小的接口(方法),如 add 、remove 等方法,方便咱們去操做修改當前集合中的數據元素,當集合中新添加的數據超過了當前的存儲空間大小時

會申請一個新的存儲空間,而後將這些已有的數據拷貝過去,再添加新的數據 ,擴容後的集合的大小等於擴容前集合的1.5倍。

當咱們刪除一個元素的時候,會將當前被刪除元素以後的元素統一貫前移動一位,而後將最後的一位元素置爲null,以便於gc回收。

因此,若是咱們對一個ArrayList 有頻繁的增刪操做,這樣對性能是一個極大的損耗。

ArrayList 的數據存儲結構示意圖以下:

假設上圖中每個黃色的格子都表明一個ArrayList 的存儲空間

步驟1:在咱們第一次調用add方法增長一個元素1的時候,那麼list會直接擴容爲默認的大小10,咱們也能夠在調用ArrayList 構造函數的時候傳入參數,指定初始化空間大小;

步驟2:咱們再繼續添加數據,直到添加到11時,會判斷當前的存儲空間放不下要增長的數據了,這個時候會繼續擴容,以後再放入數據11;

步驟3:在這一步,咱們決定刪除數據2,2的下標爲1(數組的下標都是從0開始),也就是調用remove方法;

注意:當咱們調用size方法獲取到的是實際的存儲的數據大小,而不是整個ArrayList 得到的存儲空間大小,例如 ,步驟2中調用size方法返回的會是11,而不是15。

 

LinkedList

從這個名字上,咱們也能夠大概知道,link是關聯的意思。LinkedList 和ArrayList 不一樣的一點是,他實現了Deque接口 這是一個雙向鏈表的接口。

咱們先看下存儲結構示意圖:

 

 如上圖中所示,每個節點都是一個Node對象,其中每一個Node都有三個屬性,item 實際存儲的數據元素,如上圖中的綠色格子,next和prev,這樣就構成了一個鏈表結構。

而要注意的是next 和prev 也是一個Node對象,而Node是LinkedList 中的靜態內部類。以下圖中代碼所示:

在這個鏈表中還存在2個屬性 first 和 last,分別用於存放整個集合鏈表中的頭和尾,若是隻有一個元素,那麼first 和last就指向同一個元素。

數據的添加

當咱們在鏈表中添加一個元素的時候,最後一個元素的null位置會設置引用新的Node節點,而後新添加的節點的prev會指向前一個元素的位置

咱們從LinkedList 源碼中作一些簡單的分析

 1     /**
 2      * Appends the specified element to the end of this list.
 3      *
 4      * <p>This method is equivalent to {@link #addLast}.
 5      *
 6      * @param e element to be appended to this list
 7      * @return {@code true} (as specified by {@link Collection#add})
 8      */
 9     public boolean add(E e) {
10         linkLast(e);
11         return true;
12     }

如上所示,從 「Appends the specified element to the end of this list.」 這句註釋中,咱們就大體能夠明白其意,當咱們調用add方法增長元素的時候,默認是在末尾追加數據。

這個時候add方法中會調用linkLast方法,具體代碼以下:

 1   /**
 2      * Links e as last element.
 3      */
 4     void linkLast(E e) {
 5         final Node<E> l = last;
 6         final Node<E> newNode = new Node<>(l, e, null);
 7         last = newNode;
 8         if (l == null)
 9             first = newNode;
10         else
11             l.next = newNode;
12         size++;
13         modCount++;
14     }

上述代碼中,首先會將當前的last賦給l,而後新建一個Node對象,傳入新添加的數據,以及將當前集合中的last賦值給新添加節點的prev屬性。

而後將新建的對象賦值給last,以後再判斷最開始的last,也就是當前的l是否爲null,若是是null,也就表明集合是空的,這是第一個元素,那麼就把它賦給frist,不然,那麼就說明已經有元素存在了,讓上一個元素的next指向當前新建的對象。

最後再進行調整大小等操做。

數據添加的操做示意圖以下:

 

數據的刪除

下面咱們再來看一下,LinkedList 的刪除操做,也就是咱們默認調用remove方法。

源碼以下所示:

 1  /**
 2      * Retrieves and removes the head (first element) of this list.
 3      *
 4      * @return the head of this list
 5      * @throws NoSuchElementException if this list is empty
 6      * @since 1.5
 7      */
 8     public E remove() {
 9         return removeFirst();
10     }

一樣,從「Retrieves and removes the head (first element) of this list.」註釋中,咱們大體能夠明白,大意是檢索並移除list頭部的元素。

在這個方法中直接調用了removeFirst方法,下面咱們看一下removeFirst代碼:

 1    /**
 2      * Removes and returns the first element from this list.
 3      *
 4      * @return the first element from this list
 5      * @throws NoSuchElementException if this list is empty
 6      */
 7     public E removeFirst() {
 8         final Node<E> f = first;
 9         if (f == null)
10             throw new NoSuchElementException();
11         return unlinkFirst(f);
12     }

如上所示,在這個代碼中,直接判斷是否是存在first,也就是集合是否是空的,不是那就繼續調用unlinkFirst方法,

unlinkFirst代碼以下所示:

 1   /**
 2      * Unlinks non-null first node f.
 3      */
 4     private E unlinkFirst(Node<E> f) {
 5         // assert f == first && f != null;
 6         final E element = f.item;
 7         final Node<E> next = f.next;
 8         f.item = null;
 9         f.next = null; // help GC
10         first = next;
11         if (next == null)
12             last = null;
13         else
14             next.prev = null;
15         size--;
16         modCount++;
17         return element;
18     }

如上所示,從「Unlinks non-null first node f」註釋中,可知,解開非空的第一個節點的關聯。首先將first節點的f.item 以及f.next設置爲null,以便於gc回收。在將f.next置爲null以前賦值給了臨時的next。

而後判斷next是否爲null,若是是,則說明後面沒有元素了,這是集合中的惟一一個元素,將last也設置爲null;不然,將next中的prev設置爲null。

數據刪除操做示意圖以下:

因此呢,當咱們對linkedList進行增刪操做的時候只須要對2個節點進行修改,而對其餘節點沒有任何影響。

 

 

Vector

這個類和ArrayList基本類似,不一樣的點在於他是線程安全的,也就是在同一個時刻,只能有一個線程訪問Vector;另外Vector擴容不一樣於ArrayList,他每次擴容默認都是按2倍,而ArrayList是1.5倍。

ArrayList、LinkedList、 Vector三者之間的異同

ArrayList與LinkedList相比查詢速度快,增刪速度慢。

因此若是隻是查詢,建議用前者,反之建議用後者,由於後者再增刪的時候,只須要修改2個節點的 prev和next ,而不存在複製當前已有的元素到新的存儲空間。

Vecor和ArrayList基本類似,區別是前者是線程安全的,後者不是。可是2個底層實現都是數組,LinkedList底層實現是鏈表。

集合的遍歷

常常用到有三種方式,代碼示意以下:

 1    /* 第一種遍歷方式 
 2         for循環的遍歷方式
 3    */
 4         for (int i = 0; i < lists.size(); i++) {
 5             System.out.print(lists.get(i));
 6         }
 7  8         
 9         /* 第二種遍歷方式 
10           foreach的遍歷方式
11         */
12         for (Integer list : lists) {
13             System.out.print(list);
14         }
15 16         
17         /* 第三種遍歷方式
18          Iterator的遍歷方式
19       */
20         for (Iterator<Integer> list = lists.iterator(); list.hasNext();) {
21             System.out.print(list.next());
22         }

for循環效率高於Iterator循環,高於foreach循環,由於咱們都知道他們的底層實現都是數組,而for循環是經過下標查詢是最適合的遍歷方式; 而foreach循環是在Iterator基礎上進行的,因此最慢。

另外,迭代器遍歷方式, 適用於連續內存存儲方式,好比數組、 ArrayList,Vector。 缺點是隻能從頭開始遍歷, 優勢是能夠一邊遍歷一邊刪除。

for循環這種方式遍歷比較靈活,能夠指定位置開始遍歷。性能最高,但有一個缺點就是遍歷過程當中不容許刪除元素,不然會拋ConcurrentModificationException。

注:可是曾經發如今刪除倒數第2個元素的時候,並不會拋出異常,詳見 記一次list循環刪除元素的突發事件!

 

 

Set

Set不容許包含相同的元素,若是試圖把兩個值相同元素加入同一個集合中,add方法返回false。

Set判斷兩個對象相同不是使用==運算符,而是根據equals方法。也就是說,只要兩個對象用equals方法比較返回true,Set就不會再存儲第二個元素。

set的實現類有HashSet、TreeSet、LinkedHashSet

HashSet

不能保證元素的排列順序;不是線程安全的;集合元素能夠是null,但只能放入一個null,其餘相同數據也只能有一份存在;

對於HashSet咱們要知道的是,他是依靠HashMap中的key去維護存放的數據,因此HashSet的這些特性都是和HashMap的key相關的。

hashSet默認構造函數代碼以下:

1    /**
2      * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
3      * default initial capacity (16) and load factor (0.75).
4      */
5     public HashSet() {
6         map = new HashMap<>();
7     }

如上代碼所示,他是調用了HashMap的默認構造函數。

LinkedHashSet

LinkedHashSet和HashSet的區別是前者是有序的,也就是當你插入數據的時候會按順序排放,這樣咱們遍歷時就能夠按照以前插入的順序獲取數據。

和HashSet類似也是在構造函數中調用了LinkedHashMap構造方法,代碼以下所示:

1   /**
2      * Constructs a new, empty linked hash set with the default initial
3      * capacity (16) and load factor (0.75).
4      */
5     public LinkedHashSet() {
6         super(16, .75f, true);
7     }

由於LinkedHashSet繼承了HashSet,因此他調用super,就是調用的HashSet的構造器,在HashSet中再調用了LinkedHashMap,代碼以下所示:

 1  /**
 2      * Constructs a new, empty linked hash set.  (This package private
 3      * constructor is only used by LinkedHashSet.) The backing
 4      * HashMap instance is a LinkedHashMap with the specified initial
 5      * capacity and the specified load factor.
 6      *
 7      * @param      initialCapacity   the initial capacity of the hash map
 8      * @param      loadFactor        the load factor of the hash map
 9      * @param      dummy             ignored (distinguishes this
10      *             constructor from other int, float constructor.)
11      * @throws     IllegalArgumentException if the initial capacity is less
12      *             than zero, or if the load factor is nonpositive
13      */
14     HashSet(int initialCapacity, float loadFactor, boolean dummy) {
15         map = new LinkedHashMap<>(initialCapacity, loadFactor);
16     }

 

TreeSet

TreeSet是SortedSet接口的惟一實現類,TreeSet能夠確保集合元素處於排序狀態。

TreeSet支持兩種排序方式,天然排序 和定製排序,其中天然排序爲默認的排序方式。向TreeSet中加入的應該是同一個類的對象。

天然排序不用多說,那定製排序的意思就是,咱們能夠本身經過實現Comparator接口覆寫其中比較方法,而後按照咱們意願進行排序,好比天然排序是升序,咱們經過覆寫這個排序方法,能夠修改爲降序。

TreeSet帶比較器的構造函數代碼以下:

 1  /**
 2      * Constructs a new, empty tree set, sorted according to the specified
 3      * comparator.  All elements inserted into the set must be <i>mutually
 4      * comparable</i> by the specified comparator: {@code comparator.compare(e1,
 5      * e2)} must not throw a {@code ClassCastException} for any elements
 6      * {@code e1} and {@code e2} in the set.  If the user attempts to add
 7      * an element to the set that violates this constraint, the
 8      * {@code add} call will throw a {@code ClassCastException}.
 9      *
10      * @param comparator the comparator that will be used to order this set.
11      *        If {@code null}, the {@linkplain Comparable natural
12      *        ordering} of the elements will be used.
13      */
14     public TreeSet(Comparator<? super E> comparator) {
15         this(new TreeMap<>(comparator));
16     }

TreeSet默認構造函數代碼以下:

 1   /**
 2      * Constructs a new, empty tree set, sorted according to the
 3      * natural ordering of its elements.  All elements inserted into
 4      * the set must implement the {@link Comparable} interface.
 5      * Furthermore, all such elements must be <i>mutually
 6      * comparable</i>: {@code e1.compareTo(e2)} must not throw a
 7      * {@code ClassCastException} for any elements {@code e1} and
 8      * {@code e2} in the set.  If the user attempts to add an element
 9      * to the set that violates this constraint (for example, the user
10      * attempts to add a string element to a set whose elements are
11      * integers), the {@code add} call will throw a
12      * {@code ClassCastException}.
13      */
14     public TreeSet() {
15         this(new TreeMap<E,Object>());
16     }

從上面的源碼註釋中,咱們大體能夠明白,其意是構造一個新的空的樹形set,排序按照元素的天然順序排序,全部要插入到set中的元素必須實現Comparable接口,同時,這些元素還必須是互相能夠比較的。

若是使用者嘗試添加一個string類型的數據到integer類型的set中,那麼會拋出ClassCastException 異常。

 

 

關於Map接口,咱們將在下一章節中作一個詳細的分析

 

 

 

 

 


 

文中如有不正之處,歡迎批評指正!

相關文章
相關標籤/搜索