集合結合數據結構來看看
一、先來嘮一嘮?
上一篇泛型適宜本意還想繼續往下寫寫,來一下協變與逆變,奈何不知道怎麽該往下寫,等等我思考一下,在繼續寫~接下來聊聊數據結構這一話題,想到數據結構就想起了那一年我在工院上課就睡覺的時光,真實暴遣天物呀,不扯開始話題,數據結構可能在我們工作的時候用的不算太多,但是實際上不管java或者C#都將這些封裝到我們常用的類裏面,就比如說集合就是數據結構的真實寫照~
二、數組先來扯一扯?
數組不算是集合,但是還是想用來他的東西來說明數據結構的一些東西,不算是偏題哈~我們都知道數組的所有元素都是存儲在操作系統分配的內存塊中,是一塊連續的存儲空間,可以通過特定的元素的索引作為數組的下標,我使用一個圖來表示一下數組中在內存中的存儲方式,大家看了下圖基本就明白了,這裏要引入一個概念線性表,也是數據結構裏面最簡單的關系,一對一,零個或多個數據元素的有限序列,線性表分成2種存儲結構一種是順序表另外一種是鏈表,這裏我們先說順序表,數組也是我們順序表的具體體現。前面我們已經提到他的概念,重點一塊連續的空間排列,元素與其相鄰的元素是在物理上相鄰,這裏我們看出當我們訪問一個數組元素的話的只要知道下標那麽他的訪問速度是最快的,但是這裏也是有缺點的,數組大小固定,另外如果做插入和刪除的操作的時候每個位置還需要偏移,這樣在時間開銷上很大,這也是我們在使用數組的時候需要註意的問題,另外就是如果數組剛開始聲明的值比較大的時候會比較占用內存空間,造成空間浪費,既然出現了這個問題java是如何幫我們解決的?
在java裏面有專門的API支持擴容,如下所示,這樣我們只要指定合理的擴容長度那麽就不會在空間上照成浪費嘍,當然還有更好的,那就是集合,既然引進了動態數組的概念我們不得不談下ArryList,俺不才某天看了遍文章,有了自己看看源碼的沖動,最近也在看數據結構因此有了這篇和接下來的文章,集合也是程序中很重要的一部分,那就我們一起攜手探秘集合。
int[] arry={1,3,4};
int[] arry2=Arrays.copyOf(arry, arry.length+5);
看源碼第一步看繼承結構
public class ArrayList<E> extendsAbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable
AbstractList<E>是一個抽象的泛型類,是對List<E>接口的實現,RandomAccess這個是一個空接口,實現這個接口可以支持快速訪問;具體的定義可以跳進去看下Java的解釋,其實
我感覺就是單鏈表和順序表之間訪問元素的差距,不知道我理解的對不對,有問題希望大神給我指出來,小樹的成長還需要你們,我寫了一個demo比較二者的差距大家可以試著運行下,差距
還是很大,下面是我實現了大致上的對比,剩下的2個接口一個是實現淺拷貝,另外一個就是序列化;
實現了RandomAccess接口
實現了RandomAccess接口需要5秒
未實現了RandomAccess接口
未實現了RandomAccess接口需要100秒
package com.wtz.demo; import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.RandomAccess; public class DemotTest{ public static void main(String[] args) { // TODO Auto-generated method stub List arrayList=new ArrayList(); List linkeList=new LinkedList<>(); for (int i = 0; i < 10000000; i++) { arrayList.add(i); linkeList.add(i); } getAllTime(arrayList); getAllTime(linkeList); } private static void getAllTime(List list) { long startTime; long endTime; if(list instanceof RandomAccess){ System.out.println("實現了RandomAccess接口"); startTime=System.currentTimeMillis(); for (int i = 0; i < list.size(); i++) { list.get(i); } endTime=System.currentTimeMillis(); System.out.println("實現了RandomAccess接口需要"+(endTime-startTime)+"秒"); }else { System.out.println("未實現了RandomAccess接口"); startTime=System.currentTimeMillis(); for (Iterator iterator=list.iterator();iterator.hasNext();) { iterator.next(); } endTime=System.currentTimeMillis(); System.out.println("未實現了RandomAccess接口需要"+(endTime-startTime)+"秒"); } } }
接下來看看構造函數,常量那些應該結合方法看沒必要先就看,但是構造函數相對比較重要的一點,這是我們調用方法的第一步所以必須認識清楚;下面是我自己的一些理解,
public ArrayList(int initialCapacity) {
//初始化你提供的值大小的數組 if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {
//為0就為空數組 this.elementData = EMPTY_ELEMENTDATA; } else {
//小於0就拋出異常 throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList() {
//初始化為一個為10數組 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
//集合轉化為Object[] public ArrayList(Collection<? extends E> c) { elementData = c.toArray(); if ((size = elementData.length) != 0) { //如果返回的不是object[]數組轉化為object數組 if (elementData.getClass() != Object[].class) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { this.elementData = EMPTY_ELEMENTDATA; } }
接下來就是方法了主要是List接口的實現,一些什麽add,size這些方法我下面的單鏈表就有實現,在這裏我不做過多介紹,這裏我就說下一些比較有意思的方法
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; if (minCapacity - elementData.length > 0)
//擴容 grow(minCapacity); }
//這個地方就比較有意思這裏涉及到JVM內存方面的東西了下面這個連接搞定我們的問題
//https://www.ibm.com/developerworks/java/library/j-codetoheap/index.html private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; private void grow(int minCapacity) { int oldCapacity = elementData.length;
//這個也比較有意思這個地方會將老數組的位置減半,就是為了內存不浪費 int newCapacity = oldCapacity + (oldCapacity >> 1);
//擴容一半也不夠就直接最大 if (newCapacity - minCapacity < 0) newCapacity = minCapacity;
//如果最大超過最大值那麽就溢出 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
ArrayList基本就說到這裏,相信大家剩下基本都能讀懂,我這個我也參考了好多才有點明白,大家努力起來哈哈~~轉行還是有點小不容易,,等等基礎差不都以後還是要拜讀下JVM~~
三、鏈表來扯一扯?
什麽鏈表?初始化時分配一個元素的存儲空間,另外就是需要有指針指向另外的元素,這裏談一下鏈表的好處,這裏無需直接分配內存,克服了數組缺點,但是失去數組讀取快的優點,另外因為有指針的存在,空間開銷比較大。根據指針的類型可分為:單向鏈表,雙向鏈表以及循環鏈表。這裏是我自己的理解哈,沒有人這麽說,這裏說一下單鏈表的特性,還是先用圖表示什麽是單鏈表?這下面就是一個單鏈表,單鏈表的特點就是可以包含多個節點,每個節點指向後續的元素,最後一個節點指向為NULL,接下來我們用代碼表示一下
1.上面我們已經對單鏈表的一個節點進行聲明接下來的我們說一說鏈表的操作
1、遍歷鏈表
2、鏈表中添加一個元素
3、鏈表中刪除一個元素
4、查詢鏈表中的一個元素
2.單鏈表的遍歷
1.沿著指針遍歷
2.遍歷的時候顯示該節點的內容
3.直到指針為NULL的時候結束
3.單鏈表的插入
單鏈表的插入的情況可分為3種:
1.在元素的開始插入一個新的節點,新節點指向原來的第一個節點
2.在元素的中間隨機插入一個節點,上一個節點指向插入節點,本節點指向插入的下一個個節點
3.在元素的結尾插入一個節點,上一個節點的Next指向插入的這個節點,本節點指向Null
4.單鏈表中刪除一個元素
單鏈表的刪除的情況可分為3種:
1.在元素的開始刪除
2.在元素的中間刪除,next指向下一個節點
3.在元素的結尾刪除,next指向null
下面是主要用Java實現的主要代碼
1 package com.wtz.singleLinkedList; 2 3 /** 4 * Created by wangt on 2017/6/22. 5 */ 6 public class MySingleLinkedList<T> { 7 private Node<T> first;//第一節點 8 9 public MySingleLinkedList(){ 10 this.first=null; 11 } 12 13 /** 14 * 添加第一個子節點 15 * @param data 16 */ 17 public void addFirstNode(T data){ 18 Node<T> node=new Node<T>(data); 19 node.setNext(first); 20 first=node; 21 } 22 23 /** 24 * 移除第一個節點 25 * @return 26 */ 27 public Node removeFirstNode(){ 28 Node<T> tempNode=first; 29 first=tempNode.getNext(); 30 return tempNode; 31 } 32 33 /** 34 * 增加中間的節點 35 * @param index 36 * @param data 37 */ 38 public void add(int index,T data) 39 { 40 Node<T> node=new Node<T>(data); 41 Node<T> current=first.getNext(); 42 Node<T> previous=first.getNext(); 43 int count=1; 44 while (count<index){ 45 previous=current; 46 current=current.getNext(); 47 count++; 48 } 49 node.setNext(current); 50 previous.setNext(node); 51 } 52 53 /** 54 * 刪除中間的節點 55 * @param index 56 * @return 57 */ 58 public Node<T> remove(int index){ 59 Node<T> current=first.getNext(); 60 Node<T> previous=first.getNext(); 61 int count=1; 62 while (count<index){ 63 count++; 64 previous=current; 65 current=current.getNext(); 66 } 67 if(current==first){ 68 first=first.getNext(); 69 }else { 70 previous.setNext(current.getNext()); 71 } 72 return current; 73 } 74 75 /** 76 * 展示所有的節點 77 */ 78 public void showAllNode() 79 { 80 Node<T> current=first; 81 while (current!=null){ 82 System.out.print(current.getItem()); 83 current=current.getNext(); 84 } 85 } 86 87 /** 88 * 根據索引查詢節點的信息 89 * @param index 90 * @return 91 */ 92 public Node<T> findNode(int index){ 93 Node<T> current=first; 94 int count=0; 95 while (count<index){ 96 count++; 97 current=current.getNext(); 98 } 99 System.out.print(current.getItem()); 100 return current; 101 } 102 }
下面是子節點的定義
package com.wtz.singleLinkedList; /** * Created by wangt on 2017/6/22. */ public class Node<T> {//定義一個節點 public Node<T> getNext() { return next; } public void setNext(Node<T> next) { this.next = next; } private Node<T> next; public T getItem() { return item; } public void setItem(T item) { this.item = item; } private T item; public Node(T item) { this.item=item; } }
下面是測試代碼
package com.wtz.singleLinkedList; import org.junit.Test; /** * Created by wangt on 2017/6/22. */ public class TestNode { public static void main(String[] args){ MySingleLinkedList<Integer> mySingleLinkedList=new MySingleLinkedList<Integer>(); mySingleLinkedList.addFirstNode(1); mySingleLinkedList.addFirstNode(6); mySingleLinkedList.addFirstNode(5); // mySingleLinkedList.showAllNode(); // mySingleLinkedList.removeFirstNode(); // mySingleLinkedList.showAllNode(); // mySingleLinkedList.add(2,10); // mySingleLinkedList.showAllNode(); // mySingleLinkedList.remove(2); // mySingleLinkedList.showAllNode(); mySingleLinkedList.findNode(2); } }
四、說下心得
上面說的一些東西側面還是發現自己基礎還是不太好,還要努力,另外還是再推下我的java群,438836709歡迎大家,,,看著反應寫下一篇,我感覺寫一篇還是挺累的
集合結合數據結構來看看