數學知識巧學JCF(Java Collections framework)

ZYXS發表於2019-06-09

 

 不知你是否還記得高中我們學過的集合,對映,函式,數學確實很牛逼,拿它來研究java集合類,輕而易舉的就把知識理解了。本篇文章適合初學java集合類的小白,也適合補充知識漏缺的學習者,同時也是面試者可以參考的一份資料。

數學知識

回顧一下之前所學的知識,結合我多年的高中數學教學經驗,相信你會對某些知識有一些新的感悟。

集合:一般地,我們把研究物件統稱為元素(element),把一些元素組成的總體叫做集合(set)。

對於一個給定的集合,其具有的特徵:

確定性:集合中的元素都是確定的。

互異性:集合中的元素都是不同的。

無序性:集合中的元素的順序是無序的。

對映:一般地,我們有:

設A,B是兩個非空的集合,如果按照某一個確定的對應關係f.是對應集合A中的任意一個元素x,在集合B中都有唯一確定的元素y與之對應,那麼就稱對應f:A—>B為集合A到集合B的一個對映(mapping)。

其實簡單的來講,何謂對映,就是函式上將的關係對應,例如:

函式 f(x)=x^2  那麼每一個x都有唯一的y與之對應,這就是對映關係的一個模型。

而方程 x^2+y^2=1,這個很明顯是圓心為(0,0)的半徑為1的圓,任取一個x可能會有一個或者兩個y與之對應,這就不能稱為對映,進而不能稱為函式。(1,0)或者(-1,0)這時候的x只有唯一的確定的y和它對應。

集合類的學習

集合類產生的原因:在一般的情況下,我們在寫程式時並不知道將需要多少個物件,或者是否需要更加複雜的方式儲存物件,顯然使用具有固定長度的陣列已經不能解決這個問題了。所以java 實用類庫提供了一套相當完整的容器類來解決這個問題。

基本概念

java容器類類庫的用途是“儲存物件”,可將其劃分為兩個不同的概念:

1)collection.獨立元素的序列。主要包含List(序列),Set(集合),Queue(佇列)

List:按照插入的順序儲存元素;

Set:不能有重複的元素;

Queue:按照排隊規則來確定物件產生的順序(通常與它們被插入的順序相同);

2)Map:一組成對的“鍵值對”物件,允許我們使用鍵來查詢值。

針對經常使用的類庫,我們只列出List,Set,Map之間的繼承關係:

List

List介面在Collection的基礎上新增了大量的方法,使得可以在List的中間插入和刪除元素。

繼承自List的子類有ArrayList,   LinkedList ,Vector三類。

list的特徵:

1 有序的Collection
2 允許重複的元素,允許空的元素。
3 插入類似的資料:{1,2,4,{5,2},1,3};

ArrayList(類似於順序表)

其主要用於查詢,對於刪除和插入,耗時巨大。ArrayList是以陣列實現的列表,不支援同步。

優點:利用索引位置可以快速的定位訪問

   適合變動不大,主要用於查詢的資料

         和java的陣列相比較,其容量是可以動態調整的。

     缺點:不適合指定位置的插入,刪除操作。

--ArrayList在元素填滿容器是會自動擴充容器大小的50%

ArrayListTest 程式碼分析:

add()方法,新增元素,預設是在後面新增。

add(index,value),在指定索引處新增元素。會進行元素的移動。原始碼如下:

1 public void add(int index, E element) {
2         rangeCheckForAdd(index);
3         ensureCapacityInternal(size + 1);  // Increments modCount!!
4         System.arraycopy(elementData, index, elementData, index + 1,
5                          size - index);
6         elementData[index] = element;
7         size++;
8  }

remove(index)刪除指定位置上的元素。原始碼如下:

 1 public E remove(int index) {
 2         rangeCheck(index);
 3         modCount++;
 4         E oldValue = elementData(index);
 5         int numMoved = size - index - 1;
 6         if (numMoved > 0)
 7             System.arraycopy(elementData, index+1, elementData, index,
 8                              numMoved);
 9         elementData[--size] = null; // clear to let GC do its work
10         return oldValue;
11     }

  從原始碼可以分析出,在ArrayList進行插入和刪除的時候,會進行類似順序表的操作,移動元素,空出位置,然後插入元素。刪除:依次移動後面的元素覆蓋指定位置的元素。這就會大大減慢ArrayList插入和刪除的效率。

舉一個應用的例子,更好的理解ArrayList:

 1 public class ArrayListTest {
 2   public static void main(String[] args) {
 3      //泛型的用法,只允許Integer型別的元素插入。
 4     ArrayList<Integer> arrayList =new ArrayList<Integer>();
 5   //增加元素 
 6            arrayList.add(2);
 7     arrayList.add(3);
 8     arrayList.add(4);
 9     arrayList.add(5);
10     arrayList.add(4);
11     arrayList.add(null);//ArrayList允許空值插入,
12     arrayList.add(new Integer(3));
13     System.out.println(arrayList);//   [2, 3, 4, 5, 4, null, 3]
14   //檢視元素的個數
15            System.out.println(arrayList.size());// 7
16     arrayList.remove(0);
17     System.out.println(arrayList);//  [3, 4, 5, 4, null, 3]
18     arrayList.add(1, new Integer(9)); 
19     System.out.println(arrayList);//   [3, 9, 4, 5, 4, null, 3]
20     System.out.println("-----------遍歷方法-------");
21     ArrayList<Integer> as=new ArrayList<Integer>(100000);
22     for(int i=0;i<100000;i++){
23       as.add(i);
24     }
25     traverseByIterator(as);
26     traverseByFor(as);
27     traverseByForEach(as);
28   }
29   public static void traverseByIterator(ArrayList<Integer>al){
30     System.out.println("---------迭代器遍歷-------------");
31     long startTime=System.nanoTime();//開始時間
32     Iterator it=al.iterator();
33     while(it.hasNext()){//
34       it.next();
35     }
36     long endTime=System.nanoTime();//結束時間
37     System.out.println((endTime-startTime)+"納秒");
38   }
39   public static void traverseByFor(ArrayList<Integer>al){
40     System.out.println("---------索引遍歷-------------");
41     long startTime=System.nanoTime();//開始時間
42     for(int i=0;i<al.size();i++) al.get(i);
43     long endTime=System.nanoTime();//結束時間
44     System.out.println((endTime-startTime)+"納秒");
45   }
46   public static void traverseByForEach(ArrayList<Integer>al){
47     System.out.println("---------Foreach遍歷-------------");
48     long startTime=System.nanoTime();//開始時間
49     for(Integer temp:al);
50     long endTime=System.nanoTime();//結束時間
51     System.out.println((endTime-startTime)+"納秒");
52   }
53 }
54 -----------遍歷方法-------
55 ---------迭代器遍歷-------------
56 10407039納秒
57 ---------索引遍歷-------------
58 7094470納秒
59 ---------Foreach遍歷-------------
60 9063813納秒
61 可以看到利用索引遍歷,相對來說是快一些。
View Code

迭代器 Iterator

1 hasNext()  判斷是否有下一個元素
2 next()    獲取下一個元素
3 remove ()  刪除某個元素

LinkedList:(主要用於增加和修改!)

    --以雙向連結串列實現的列表,不支援同步。

    --可以被當做堆疊、佇列和雙端佇列進行操作

    --順序訪問高效,隨機訪問較差,中間插入和刪除高效

    --適合經常變化的資料

    addFirst()在頭部新增元素

    add(3,10);將10插入到第四個位置上

    remove(3)刪除第四個位置的元素

程式碼詳解:

 1 public class LinkedListTest {
 2   public static void main(String[] args) {
 3     LinkedList<Integer> linkedList=new LinkedList<Integer>();
 4     linkedList.add(2);
 5     linkedList.add(3);
 6     linkedList.add(9);
 7     linkedList.add(6);
 8     linkedList.add(7);
 9     System.out.println(linkedList);
10     //linkedList.addFirst(1);
11     //linkedList.addLast(10);
12     //System.out.println(linkedList);
13     linkedList.add(3, 4);
14     System.out.println(linkedList);
15     System.out.println(linkedList.get(4));
16     LinkedList<Integer> as=new LinkedList<Integer>();
17     for(int i=0;i<100000;i++){
18       as.add(i);
19     }
20     traverseByIterator(as);
21     traverseByFor(as);
22     traverseByForEach(as);
23   }
24   public static void traverseByIterator(LinkedList<Integer>al){
25     System.out.println("---------迭代器遍歷-------------");
26     long startTime=System.nanoTime();//開始時間
27     Iterator it=al.iterator();
28     while(it.hasNext()){
29       it.next();
30     }
31     long endTime=System.nanoTime();//結束時間
32     System.out.println((endTime-startTime)+"納秒");
33   }
34   public static void traverseByFor(LinkedList<Integer>al){
35     System.out.println("---------索引遍歷-------------");
36     long startTime=System.nanoTime();//開始時間
37     for(int i=0;i<al.size();i++) al.get(i);
38     long endTime=System.nanoTime();//結束時間
39     System.out.println((endTime-startTime)+"納秒");
40   }
41   public static void traverseByForEach(LinkedList<Integer>al){
42     System.out.println("---------Foreach遍歷-------------");
43     long startTime=System.nanoTime();//開始時間
44     for(Integer temp:al);
45     long endTime=System.nanoTime();//結束時間
46     System.out.println((endTime-startTime)+"納秒");
47   }
48 }
49 ---------迭代器遍歷-------------
50 6562423納秒
51 ---------索引遍歷-------------
52 4565606240納秒
53 ---------Foreach遍歷-------------
54 4594622納秒
55 可以看出使用索引遍歷,對於linkedList真的很費時間!

add(index,value)原始碼分析:我們可以看到,這就是雙引用(雙指標)的賦值操作。

 1 void linkBefore(E e, Node<E> succ) {
 2         // assert succ != null;
 3         final Node<E> pred = succ.prev;
 4         final Node<E> newNode = new Node<>(pred, e, succ);
 5         succ.prev = newNode;
 6         if (pred == null)
 7             first = newNode;
 8         else
 9             pred.next = newNode;
10         size++;
11         modCount++;
12     }

remove(index)原始碼分析:同樣,這也是對引用的更改操作,方面多了!

 1 E unlink(Node<E> x) {
 2         // assert x != null;
 3         final E element = x.item;
 4         final Node<E> next = x.next;
 5         final Node<E> prev = x.prev;
 6 
 7         if (prev == null) {
 8             first = next;
 9         } else {
10             prev.next = next;
11             x.prev = null;
12         }
13 
14         if (next == null) {
15             last = prev;
16         } else {
17             next.prev = prev;
18             x.next = null;
19         }
20 
21         x.item = null;
22         size--;
23         modCount++;
24         return element;
25     }

get(index)原始碼分析:利用指標挨個往後查詢,直到找到位置為index的元素。當然了,找的時候也是要注意方法的,比如說利用二分查詢。

 1 Node<E> node(int index) {
 2         // assert isElementIndex(index);
 3 
 4         if (index < (size >> 1)) {
 5             Node<E> x = first;
 6             for (int i = 0; i < index; i++)
 7                 x = x.next;
 8             return x;
 9         } else {
10             Node<E> x = last;
11             for (int i = size - 1; i > index; i--)
12                 x = x.prev;
13             return x;
14         }
15     }

Vector

-和ArrayList類似,可變陣列實現的列表

    -Vector同步,適合在多執行緒下使用

    -原先不屬於JCF框架,屬於java最早的資料結構,效能較差

    -從JDK1.2開始,Vector被重寫,並納入JCF中

    -官方文件建議在非同步的情況下,優先採用ArrayList

    其實vector類似於ArrayList,所以在一般情況下,我們能優先使用ArrayList,在同步的情況下,是可以考慮使用Vector

程式碼例子:

 1 public class VectorTest {
 2   public static void main(String[] args) {
 3     Vector<Integer> vs=new Vector<Integer>();
 4     vs.add(1);
 5     vs.add(4);
 6     vs.add(3);
 7     vs.add(5);
 8     vs.add(2);
 9     vs.add(6);
10     vs.add(9);
11     System.out.println(vs);
12     System.out.println(vs.get(0));
13     vs.remove(5);
14     System.out.println(vs);
15     /*Integer []a=new Integer[vs.size()];
16     vs.toArray(a);
17     for(Integer m:a){
18       System.out.print(m+" ");
19     }*/
20     Vector <Integer> as=new Vector <Integer>(100000);
21     for(int i=0;i<1000000;i++){
22       as.add(i);
23     }
24     traverseByIterator(as);
25     traverseByFor(as);
26     traverseByForEach(as);
27     traverseEm(as);
28   }
29   public static void traverseByIterator(Vector<Integer>al){
30     System.out.println("---------迭代器遍歷-------------");
31     long startTime=System.nanoTime();//開始時間
32     Iterator it=al.iterator();
33     while(it.hasNext()){
34       it.next();
35     }
36     long endTime=System.nanoTime();//結束時間
37     System.out.println((endTime-startTime)+"納秒");
38   }
39   public static void traverseByFor(Vector<Integer>al){
40     System.out.println("---------索引遍歷-------------");
41     long startTime=System.nanoTime();//開始時間
42     for(int i=0;i<al.size();i++) al.get(i);
43     long endTime=System.nanoTime();//結束時間
44     System.out.println((endTime-startTime)+"納秒");
45   }
46   public static void traverseByForEach(Vector<Integer>al){
47     System.out.println("---------Foreach遍歷-------------");
48     long startTime=System.nanoTime();//開始時間
49     for(Integer temp:al){
50       temp.intValue();
51     }
52     long endTime=System.nanoTime();//結束時間
53     System.out.println((endTime-startTime)+"納秒");
54   }
55   public static void traverseEm(Vector<Integer>al){
56     System.out.println("---------Enumeration遍歷-------------");
57     long startTime=System.nanoTime();//開始時間
58     for(Enumeration <Integer> ei=al.elements();ei.hasMoreElements();){
59       ei.nextElement();
60     }
61     long endTime=System.nanoTime();//結束時間
62     System.out.println((endTime-startTime)+"納秒");
63   }
64 }
65 ---------迭代器遍歷-------------
66 28927404納秒
67 ---------索引遍歷-------------
68 32122768納秒
69 ---------Foreach遍歷-------------
70 25191768納秒
71 ---------Enumeration遍歷-------------
72 26901515納秒
73 可以看到Foreach遍歷要快於其他的遍歷方法。

add(index,value)原始碼剖析:這個和ArrayList類似,需要進行元素的複製,所以很慢

 1 public synchronized void insertElementAt(E obj, int index) {
 2         modCount++;
 3         if (index > elementCount) {
 4             throw new ArrayIndexOutOfBoundsException(index
 5                                                      + " > " + elementCount);
 6         }
 7         ensureCapacityHelper(elementCount + 1);
 8         System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
 9         elementData[index] = obj;
10         elementCount++;
11     }

get(index)原始碼剖析:可以看到,直接根據元素的下表返回陣列元素。非常快!

1 public synchronized E get(int index) {
2         if (index >= elementCount)
3             throw new ArrayIndexOutOfBoundsException(index);
4 
5         return elementData(index);
6     }
7 E elementData(int index) {
8         return (E) elementData[index];
9     }

其實List這部分內容用的數學知識不是很多,但是set和Map確實是類似於數學模型的概念。期待後續Set,Map的學習。

個人微信公眾號

 

 

相關文章