(一)ArrayList和LinkedList的原理、Java程式碼實現、效能比較
一、ArrayList
1.1、陣列和集合的區別
動態大小,即陣列的大小不可變,集合的大小可變。
ArrayList從名字上來講是陣列列表,表面上是動態大小,其底層實現原理其實還是一個數組。
1.2、簡單模擬ArrayList
模擬過程中要注意Array和ArrayList的區別,陣列在乎的是能裝多少,而ArrayList在乎的是已經裝了多少,因為ArrayList要讓使用者覺得長度是可以變化的,不用擔心越界什麼的,但是我們寫程式碼時,是用陣列實現的,(比如要超過length時就給陣列“搬家”),注意區分下面index、size和length三個變數!!!
package com.review07; public class MyArrayList { Object[] obj = new Object[4]; int size = 0;//集合的大小 public int getSize() { return size; } //新增 public void add(Object value) { //判斷size是否達到陣列的長度,若已經達到了,則要搬家,即搬到一個比現在陣列長的新數組裡面去 if(size >= obj.length) { Object[] temp = new Object[obj.length*3/2+1]; //搬家 for(int i=0; i<obj.length; i++) { temp[i] = obj[i]; } obj = temp; } obj[size] = value; size ++; } //判斷下標是否合法 public void isIndexLegal(int index) { if(index<0 || index>=size) { //index<0表示陣列越界,index>=size超過了已放的數量,陣列表示的是可放數量,我們要模擬的是已放數量 try { throw new Exception("超出範圍!"); } catch (Exception e) { e.printStackTrace(); } } } //修改下標為index的值為value public void set(int index, Object value) { // if(index<0 || index>=size) { //index<0表示陣列越界,index>=size超過了已放的數量,陣列表示的是可放數量,我們要模擬的是已放數量 // try { // throw new Exception("超出範圍!"); // } catch (Exception e) { // e.printStackTrace(); // } // } isIndexLegal(index); obj[index] = value; } //獲取下標為index的值 public Object get(int index) { isIndexLegal(index); return obj[index]; } //清除所有 public void clear() { size = 0; //使用者讀不到 obj = new Object[4]; //原來的資料都會被清除掉,原來的引用沒了,最終會被GC回收掉 } //刪除指定下標的值 public void removeAt(int index) { isIndexLegal(index); for(int i=index+1; i<size; i++) { obj[i-1] = obj[i]; } size --; } //測試 public static void main(String[] args) { MyArrayList list = new MyArrayList(); list.add(3); list.add(3); list.add(2); list.add(5); list.add(6); list.set(4,45);//3 3 2 5 45 list.set(4,14);//3 3 2 5 14 list.removeAt(2);//3 3 5 14 list.clear();//全部刪除了 for (int i=0; i<list.getSize(); i++) { System.out.print(list.get(i)+" "); } } }
二、LinkedList
Node是結點,Joint、Connector是節點,本文可能有錯別字,讀者注意下!
2.1、線性結構的連結串列
LinkedList實際上是用雙向迴圈連結串列實現的,但是雙向迴圈連結串列涉及到的引用比較多,看起來比較複雜,我們可以先用單鏈表實現!
2.2、簡單模擬單鏈表
首先模擬結點,結點包含資料和下一個結點的地址,Java不像C/C++,不宜直接操作地址,但是我們可以用物件的引用。
先定義一個結點類,再寫自定義MyLinkedList:
package com.review07.linkedList; public class Node { Object value; //資料 Node next; //下一個結點的地址(物件的引用) public Node(Object value) { //構造方法 this.value = value; } //新增set和get方法 public Object getValue() { return value; } public void setValue(Object value) { this.value = value; } public Node getNext() { return next; } public void setNext(Node next) { this.next = next; } }
package com.review07.linkedList; public class MyLinkedList { private int size =0; private Node head = null; //頭結點剛開始時空的 //獲取大小 public int getSize() { return size; } //在最後一個結點新增 public void add(Object value) { Node newNode = new Node(value); if(head == null) { //第一次新增 head = newNode; }else { Node temp = head; //當前結點,相當於游標 while(temp.getNext() != null) { temp = temp.getNext(); //當前結點向後移動 } //迴圈結束說明到了最後一個節點,注意新增時是新增到最後一個節點的next,而不是temp直接等於newNode temp.setNext(newNode); } size ++; } public void set(int index, Object value) { Node temp = head; for(int i=0; i<index; i++) { temp = temp.getNext(); }//temp定位到指定索引位置 temp.setValue(value); } public Object get(int index) { Node temp = head; for(int i=0; i<index; i++) { temp = temp.next; } return temp.getValue(); } //刪除所有結點 public void clear() { head = null;//頭沒有了,沒有物件引用,gc會回收 size = 0; } //刪除指定 結點 public void removeAt(int index) { //對頭處理和對其他結點處理是不同的 if(index == 0) { //刪除的是頭結點 head = head.getNext(); }else { //要找到刪除元素的前一個元素,這樣通過要刪除元素的前一個,也可以找到要刪除的後一個 Node temp = head; for(int i=0; i<index-1; i++) { temp = temp.getNext(); } temp.setNext(temp.getNext().getNext()); } size --; } public static void main(String[] args) { MyLinkedList list = new MyLinkedList(); list.add(3); list.add(3); list.add(2); list.add(5); list.add(6); list.set(4,45);//3 3 2 5 45 list.set(4,14);//3 3 2 5 14 list.removeAt(2);//3 3 5 14 //list.clear();//全部刪除了 for (int i=0; i<list.getSize(); i++) { System.out.print(list.get(i)+" "); } } }
三、ArrayList和LinkedList效能比較
1.新增
ArrayList:新增是陣列的方式,要考慮放不下的情況,放不下時需要先建立一個數組,再搬過去。資料少的時候沒有多大影響,資料很大涉及到百萬時,建立新陣列+“搬家”,這顯然是一個浩大的工程。
LinkedList:當資料很大涉及到百萬時,用LinkedList就不一樣了,用雙向迴圈連結串列就很簡單了,根據第一個節點就可以直接找到最後一個節點,中間有幾百萬個都無所謂,直接改一下引用就好了
結果:LinkedList效率高
3.刪除
ArrayList:
上述例子中,若刪除第二個元素,後面的元素要一個一個向前移,所以越是刪前面的元素,越是耗費時間。當資料量很大時,要刪前面的元素時,用ArrayList儲存資料時相當不划算的。
ArrayList迴圈刪除時(比如刪除ArrayList中偶數),應該從後往前遍歷刪除!
(注意:刪了,整體向前移了之後,陣列的最後一個元素只是賦值給了它的前一個元素,沒有徹底的消失,只是size--之後,我們不能訪問和操作它,只能訪問到它的前一個元素。)
LinkedList:
刪前面或者後面的效率很高,刪除中間的效率最低。
相對來說:LinkedList效率較高
3.獲取和設定
ArrayList有索引,效率會比連結串列高的多
LinkedList用迴圈遍歷,每迴圈一次,就要用引用訪問一次。如果要迴圈遍歷時,Java中用foreach,而不用for迴圈。