1. 程式人生 > >(一)ArrayList和LinkedList的原理、Java程式碼實現、效能比較

(一)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迴圈。