持久對象
定義:程序通常是會在程序運行的時候 根據某些條件來創建新對象,在編譯的時候並不知道要創建對象的類型 數量,數組只能解決java中基本數據類型的存儲,而java的實用類庫提供了一套相當完整的容器類來解決這個問題,四種基本的類型 list set map queue,這些容器類都提供了自動調整自己的長度的特性,又為了避免將錯誤類型對象的引用 放進容器中,又引進了泛型來創建某類型的容器類對象,
Collection:為了我們創建的代碼更加通用。通過接口接收參數的時候的向上轉型特性 我們可以傳入他子類的具體實現 它的常用子類有list set。所以他們的所有具體實現都可以向上轉型看作為Collection對象,
list的特性:必須按照插入的順序來保存元素,
第一種實現:最常用的arrylist:結構類似於數組,所以訪問元素的時候可以直接通過索引來訪問任何位置的數據,但是當插入元素的時候默認是追加到數組的最后,那么數組當中原有的元素的位置不變 只是申請開辟了一塊內存空間和新增加了一個索引,其他都沒有變化,但是當向其他位置插入元素的時候,會先申請開辟一塊內存空間和一個新索引 但是這個索引不是給新插入元素使用的,而是給數組當中最后一個元素使用的,新元素會插入到指定索引位置 代替原索引處的元素 並將該元素以及其后面的所有元素全部向后移動,所以這個是浪費時間的,而刪除呢就是一樣的原理,隊尾刪除很簡單,其他位置刪除。位於被刪除元素后面的元素的位置全部向前移動。所以一樣很浪費時間。所以該實現只是適用於隨機訪問元素或者遍歷元素,因為他的底層是由數組來實現的。
第二種常用的實現是:linkedlist,它是基於鏈表實現的,鏈表有很多種,
鏈表是一系列的節點組成的,這些點不需要在內存中相連,
單鏈表是由一個頭結點開始。然后依次插入新的節點。每個節點包含兩個部分一個是數據的引用或者基本類型的數據 和下一個節點的存儲地址。這樣一個鏈表向外暴露的只是第一個頭結點。所以只要知道頭結點就可以直接找到剩下其余的節點。
單鏈表的結構如下
增加
刪除節點
單鏈表的內存結構如下圖:

頭結點不存儲數據 其他節點存儲的結構看下圖 是數據加上下一個節點的地址
對比一下 arryList與鏈表實現的linkedlist之間的優劣勢
LinkedList集合中的元素也是有序的,有索引,為什么和ArrayList相比查找比較慢,增刪快呢?
我們打個比方:LinkedList和ArrayList中都裝了10個人。
在ArrayList集合中的10個人是這樣的:Arraylis由於是類似於數組它本身就有標號0,,,,2,3,,4........。每個人都站在一個標號上,比如我要找4號,我說4號出來,站在4號的人,一看自己的位置標號,就知道叫的是他,他就直接出來了,所以比較快。如果我要在4號位置加個人,那么從4號開始后面的的人都要往后移,把4號位置騰出來,所以比較麻煩。
而在LinkedList集合中的每個人都是隨便站的。但是他們中的每個人都認識一個人。並且每兩個認識的人之間都有一個鏈子把他們連接起來。所有人都連接完之后,順着鏈子看,就也有一個順序,每個人就也有一個序號,但是這個序號並沒有標出來。也就是說相當於有一個隱式的序號。所以:比如我要叫4號出來的時候,他們並不知道自己到底誰是4號,所以就要順着鏈子從頭開始查一下,查到4號是誰了,4號就出來了。也就是說,每次要找n號位置的人時,都要從頭查一遍,看誰是n號。所以查找比較麻煩。至於增刪效率高,就很容易理解了。
其實就是一點,就是相當於說:ArrayList集合中的索引是標示出來的,而LinkedList集合中的索引是隱式的只能一個接着一個的找,這樣就很容易理解,為什么LinkedList集合中也有索引為什么查找效率就比Arraylist低的原因了。
如果我們在表的第一項處插入或者刪除元素該操作花費的時間為常數,但是如果我們需要在最后一項插入或者刪除元素這是一個花費時間為O(N)的操作。
我們可以用雙向鏈表來解決這個問題。雙向鏈表的每一個結點都有一條指向其后繼結點的next鏈和一條指向其前結點的pre鏈。雙向鏈表既可以從第一項開始遍歷也可以從最后一項開始往前遍歷,雙向鏈表可以用下圖表示:
增加節點的時候
刪除節點
java的linkedlist實現了雙向鏈表 看下linkedlist的用法
package test; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Stack; import org.junit.Before; public class Test { public static LinkedList<String> dl; public static ArrayList<String> l; @Before public void init() { dl = new LinkedList<String>(); dl.add("N1"); dl.add("N2"); dl.add("N3"); dl.add("N4"); dl.add("N5"); l = new ArrayList<String>(); l.add("N1"); l.add("N2"); l.add("N3"); l.add("N4"); l.add("N5"); } @org.junit.Test public void test() { for (String str : dl) { System.out.println("雙向鏈表:" + str); } for (String str : l) { System.out.println("普通arrylist:" + str); } } @org.junit.Test public void add() { // 雙向鏈表獨有的向鏈表頭部添加元素 dl.addFirst("N6"); dl.addLast("N7"); } @org.junit.Test public void del() { dl.remove("N1"); for (String str : dl) { System.out.println("雙向鏈表:" + str); } l.remove("N1"); for (String str : l) { System.out.println("普通arrylist:" + str); } } /** * 模仿棧的后進后出的pop * * @return */ public static String pop() { return dl.removeLast(); } /** * */ @org.junit.Test public void stack() { System.out.println(Test.pop()); for (String str : dl) { System.out.println("雙向鏈表:" + str); } } @org.junit.Test public void t() { Stack<String> statck = new Stack<String>(); statck.push("N1"); statck.push("N2"); statck.push("N3"); statck.push("N4"); statck.push("N5"); System.out.println("pop:" + statck.pop()); for (String str : statck) { System.out.println(str); } } /** * 實現隊列 * 隊列的數據元素又稱為隊列元素。在隊列中插入一個隊列元素稱為入隊,從隊列中刪除一個隊列元素稱為出隊。因為隊列只允許在一端插入,在另一端刪除, * 所以只有最早進入隊列的元素才能最先從隊列中刪除,故隊列又稱為先進先出(FIFO—first in first out)線性表。[1] * 隊列隊首刪除 隊尾插入 */ @org.junit.Test public void quene(){ LinkedList<String> quene = new LinkedList<String>(); quene = (LinkedList<String>)Collections.synchronizedCollection(quene); Test.addQuene(quene, "n1"); Test.addQuene(quene, "n2"); Test.addQuene(quene, "n3"); Test.frecah(quene); System.out.println("---------------------------------"); Test.delQuene(quene); Test.frecah(quene); } public static void frecah(List<String> list) { for(String str:list){ System.out.println(str); } } /** * 每次插入都會插入隊尾 * @param quene * @param o */ public static void addQuene(LinkedList<String> quene,String o){ quene.add(o); } /** * 刪除 從隊首刪除 */ public static void delQuene(LinkedList<String> quene){ quene.removeFirst(); } }
下面展示一下使用java實現雙向鏈表的代碼
1 package test.link; 2 3 4 5 public class DoubleLink<T> { 6 7 private class Node<T> { 8 /** 9 * 節點值 10 */ 11 12 private T vlaue; 13 /** 14 * 前一個節點 15 */ 16 17 private Node<T> prev; 18 /** 19 * 后一個節點 20 */ 21 22 private Node<T> prex; 23 24 public Node(T value,Node<T> prev,Node<T> prex){ 25 this.vlaue = value; 26 this.prev = prev; 27 this.prex = prex; 28 } 29 } 30 /** 31 * 鏈表長度 32 */ 33 private int size ; 34 /** 35 * 頭節點 36 */ 37 private Node<T> head; 38 public DoubleLink(){ 39 /** 40 * 頭結點不存儲值 並且頭結點初始化時 就一個頭結點。 41 * 所以頭結點的前后節點都是自己 42 * 並且這個鏈表的長度為0; 43 */ 44 head = new Node<>(null, null, null); 45 head.prev = head.prex ; 46 head = head.prex; 47 size = 0; 48 } 49 public int getSize(){ 50 return this.size; 51 } 52 /** 53 * 判斷鏈表的長度是否為空 54 */ 55 public boolean isEmplty(){ 56 return size == 0; 57 } 58 /** 59 * 判斷索引是否超出范圍 60 */ 61 public void checkIndex(int index){ 62 if(index<0||index>=size){ 63 throw new IndexOutOfBoundsException(); 64 } 65 return; 66 } 67 /** 68 * 通過索引獲取鏈表當中的節點 69 * 70 */ 71 public Node<T> getNode(int index){ 72 /** 73 * 檢查該索引是否超出范圍 74 */ 75 checkIndex(index); 76 /** 77 * 當索引的值小於該鏈表長度的一半時,那么從鏈表的頭結點開始向后找是最快的 78 */ 79 if(index<size/2){ 80 Node<T> cur = head.prex; 81 for(int i=0;i<index;i++){ 82 cur = cur.prex; 83 } 84 return cur; 85 } 86 /** 87 * 當索引值位於鏈表的后半段時,則從鏈表的另端開始找是最快的 88 */ 89 /** 90 * 此 91 */ 92 Node<T> cur = head.prev; 93 int newIndex = size - (index+1); 94 for(int i=0;i<newIndex;i++){ 95 cur = cur.prev; 96 } 97 return cur; 98 } 99 /** 100 * 獲取節點當中的值 101 */ 102 public T getValue(Node<T> cur){ 103 return cur.vlaue; 104 } 105 /** 106 * 獲取第一個節點的值 107 */ 108 public T getFirst(){ 109 return getValue(getNode(0)); 110 } 111 /** 112 * 獲取最后一個節點的值 113 */ 114 public T getLast(){ 115 return getValue(getNode(size-1)); 116 } 117 /** 118 * 插入節點 119 */ 120 public void inesert(int index,T value){ 121 //如果這次插入時 鏈表是空的 122 if(index==0){ 123 //這個節點的 124 Node<T> cur = new Node<T>(value, head, head.prex); 125 head.prex.prev = cur; 126 head.prex = cur; 127 size++; 128 return; 129 } 130 /** 131 * 先根據給出的插入位置 找到該鏈表原來在此位置的節點 132 */ 133 Node<T> node = getNode(index); 134 /** 135 *放置的位置的前一個節點就是原節點的前置節點 而后節點就是原節點 136 */ 137 Node<T> cur = new Node<T>(value,node.prev,node); 138 /** 139 * 現將該位置也就是 原節點的前節點的后節點 賦值成為新節點 140 * 然后將新節點的后置節點的值賦值成為原節點 141 */ 142 node.prev.prex = cur; 143 node.prev = cur; 144 size++; 145 } 146 /** 147 * 向表頭插入數據 148 */ 149 public void insertTo(T Value) 150 { 151 inesert(0,Value); 152 } 153 /** 154 * 將元素插入到鏈表的尾部 155 */ 156 public void insertTotatil(T vlaue){ 157 Node<T> cur = new Node<>(vlaue,head.prev, head); 158 //head.prev 代表原來的尾部節點 159 //遵循兩個原則 一 新插入節點的前一個節點的后一個節點為新節點。新節點的后一個節點的前一個節點是新節點 160 head.prev.prex = cur; 161 head.prev = cur; 162 size++; 163 } 164 /** 165 * 刪除節點的方法 166 */ 167 public void del(int index){ 168 checkIndex(index); 169 Node<T> cur = getNode(index); 170 //記住此時的指針還沒斷開 賦值以后才相當於斷開 171 cur.prev.prex = cur.prex; 172 cur.prex.prev = cur.prev; 173 size--; 174 cur = null; 175 return; 176 } 177 /** 178 * 刪除第一個節點 179 */ 180 public void delFirst(){ 181 del(0); 182 } 183 /** 184 * 刪除最后一個節點 185 */ 186 public void delLast(){ 187 del(size-1); 188 } 189 }