不知你是否還記得高中我們學過的集合,對映,函式,數學確實很牛逼,拿它來研究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 可以看到利用索引遍歷,相對來說是快一些。
迭代器 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的學習。
個人微信公眾號