JDK原始碼閱讀-------自學筆記(二十四)(java.util.LinkedList 再探 自定義講解)

北極的大企鵝發表於2020-10-21

一、實現get方法

1、一般思維實現思路

  • 1)、將物件的值放入一箇中間變數中。
  • 2)、遍歷索引值,將中間量的下一個元素賦值給中間量。
  • 3)、返回中間量中的元素值。
  • 4)、示意圖
  • get(2),傳入角標,迴圈兩次獲取到[1]元素,如圖.

2、實現思路實現

  • 1)、核心方法
 /**
     * 最基本的寫法
     *
     * <p>按照角標迴圈元素,獲取最後一個元素的值</p>
     *
     * <p>存在問題:效率不高</p>
     *
     * @param index 元素的角標
     * @return 角標代表的元素
     */
    public Object get(int index) {

        Node node = firstNode;

        for (int i = 0; i < index; i++) {

            node = node.next;
        }


        return node.elements;
    }

  • 2)、測試
/**
 * @author liuyangos8888
 */
public class TestGet {


    public static void main(String[] args) {
        testGetBase();

    }

    private static void testGetBase() {
        MyGetLinkedList001 myGetLinkedList001 = new MyGetLinkedList001();
        myGetLinkedList001.add("A");
        myGetLinkedList001.add("B");
        myGetLinkedList001.add("C");
        myGetLinkedList001.add("D");
        myGetLinkedList001.add("E");
        myGetLinkedList001.add("F");

        System.out.println(myGetLinkedList001.get(4));
    }
 }

3、思路缺少的條件

  • 1)、判斷索引範圍問題
        // 判斷索引範圍
        if (index < 0 || index > size - 1) {
            throw new RuntimeException("索引不合法!" + index);
        }
  • 2)、查詢方式的優化問題(獲取第888個索引時候,效率極低,建議使用折半查詢)
/**
     * 優化版,提高查詢效率,折半判斷
     *
     * @param index 索引角標
     * @return 索引對應的值
     */
    public Object get1(int index) {

        // 判斷索引範圍
        if (index < 0 || index > size - 1) {
            throw new RuntimeException("索引不合法!" + index);
        }

        Node node = null;

        // size>>1 除以2
        if (index <= (size >> 1)) {
            node = firstNode;

            for (int i = 0; i < index; i++) {

                node = node.next;
            }
        } else {
            node = lastNode;

            for (int i = size - 1; i > index; i--) {

                node = node.previous;
            }
        }


        return node.elements;
    }

  • 3)、發現的其他問題(採用此方式get時候,add需要size++)

   // 不做size++,在get判斷範圍的時候就會出現錯誤
   public void add(Object o) {
        Node node = new Node(o);


        if (firstNode == null) {
            firstNode = node;
        } else {
            node.previous = lastNode;
            node.next = null;
            lastNode.next = node;
        }

        lastNode = node;

        size++;
    }


二、實現remove方法

1、一般思維實現思路

  • 1)、把前一個元素的next元素,變為next.next元素
  • 2)、把最後一個元素的previous元素,變為previous.previous元素
  • 3)、size值減少操作
  • 4)、示意圖
  • 有A、B、C三個節點,以連結串列形式儲存(如上圖)
  • 現在要刪除節點B,A、C不變(如上圖)
  • 方法就是截斷A、C跟B的連線,A和C重新建立新的連線,JAVA的實現方式就是,用物件覆蓋B節點的值。

2、實現思路實現

  • 1)、核心方法

 /**
     * 根據索引,刪除陣列中元素
     *
     * @param index 索引角標
     */
    public void remove(int index) {
   
        Node temp = getNode(index);

        if (temp != null) {
            Node up = temp.previous;
            Node down = temp.next;

            if (up != null) {
                up.next = down;
            }

            if (down != null) {
                down.previous = up;
            }

            size--;
        }

    }

  • 2)、測試
/**
 * @author liuyangos8888
 */
public class TestRemove {


    public static void main(String[] args) {

        MyGetLinkedList002 myGetLinkedList002 = new MyGetLinkedList002();
        myGetLinkedList002.add("A");
        myGetLinkedList002.add("B");
        myGetLinkedList002.add("C");
        myGetLinkedList002.add("D");
        myGetLinkedList002.add("E");
        myGetLinkedList002.add("F");

        System.out.println(myGetLinkedList002);
        myGetLinkedList002.remove(3);
        System.out.println(myGetLinkedList002);
        myGetLinkedList002.remove(0);
        System.out.println(myGetLinkedList002);
        myGetLinkedList002.remove(3);
        System.out.println(myGetLinkedList002);

    }
}

3、思路缺少的條件

  • 1)、判斷索引範圍問題
        // 判斷索引範圍
        if (index < 0 || index > size - 1) {
            throw new RuntimeException("索引不合法!" + index);
        }
  • 2)、第一個元素刪除和最後一個元素刪除問題

            // 刪除第一個元素的時候
            if (index == 0) {
                firstNode = down;
            }

            // 刪除最後一個元素的時候
            if (index == size - 1) {
                lastNode = up;
            }

三、實現insert(add)方法

1、一般思維實現思路

  • 1)、獲取索引的元素值,得到元素的前一個節點
  • 2)、把前節點的後一個節點設定為加入的節點
  • 3)、新節點的前一個節點設定為索引值節點的前節點
  • 4)、前一個節點的後一個節點是索引值節點
  • 5)、索引值節點的前一個節點是新節點
  • 6)、示意圖
  • 有A、B、C三個節點,以連結串列形式儲存,現在要新增D節點,到連結串列中.
  • 切斷A、B的節點連線,A和D,D和B重新建立連線,生成新的連結串列

2、實現思路實現

  • 1)、核心方法
  • 方法一:

    /**
     * 插入一個元素在指定位置
     *
     * @param index 指定位置索引
     * @param o     插入的元素
     */
    public void insert(int index, Object o) {

        Node newNode = new Node(o);

        // 判斷範圍
        checkRound(index);

        Node temp = getNode(index);

        if (temp != null) {

            // 第一個元素和最後一個元素的時候
            if (index == 0 || index == size - 1) {
                temp.elements = newNode.elements;
            } else {
                Node up = temp.previous;
                isNull(up == null, "前一個元素為空");
                up.next = newNode;
                newNode.previous = up;

                newNode.next = temp;
                temp.previous = newNode;
            }
        }

    }

  • 方法二:

/**
     * 插入一個元素在指定位置
     *
     * @param index 指定位置索引
     * @param o     插入的元素
     */
    public void insert1(int index, Object o) {

        Node newNode = new Node(o);

        // 判斷範圍
        checkRound(index);

        Node temp = getNode(index);

        if (temp != null) {

            // 第一個元素和最後一個元素的時候
            if (index == 0) {
                firstNode = newNode;
                newNode.next = temp.next;
            } else if (index == size - 1) {
                Node up = temp.previous;
                isNull(up == null, "前一個元素為空");
                up.next = newNode;
                newNode.previous = up;
            } else {
                Node up = temp.previous;
                isNull(up == null, "前一個元素為空");
                up.next = newNode;
                newNode.previous = up;

                newNode.next = temp;
                temp.previous = newNode;
            }
        }

    }

  • 2)、測試

public class TestInsert {


    public static void main(String[] args) {
        MyInsertLinkedList003 myInsertLinkedList003 = new MyInsertLinkedList003();
        myInsertLinkedList003.add("A");
        myInsertLinkedList003.add("B");
        myInsertLinkedList003.add("C");
        myInsertLinkedList003.add("D");
        myInsertLinkedList003.add("E");
        myInsertLinkedList003.add("F");


        myInsertLinkedList003.insert1(1,"G");

        System.out.println(myInsertLinkedList003);

    }
}


3、思路缺少的條件

  • 1)、判斷索引範圍問題

   private void checkRound(int index) {
        // 判斷索引範圍
        isNull(index < 0 || index > size - 1, "索引不合法!" + index);
    }

    /**
     * 判斷空指標問題
     *
     * @param b      判斷條件
     * @param string 丟擲異常的原因
     */
    private void isNull(boolean b, String string) {
        if (b) {
            throw new RuntimeException(string);
        }
    }


  • 2)、第一個元素刪除和最後一個元素刪除問題

if (temp != null) {

            // 第一個元素和最後一個元素的時候
            if (index == 0) {
                firstNode = newNode;
                newNode.next = temp.next;
            } else if (index == size - 1) {
                Node up = temp.previous;
                isNull(up == null, "前一個元素為空");
                up.next = newNode;
                newNode.previous = up;
            } else {
                Node up = temp.previous;
                isNull(up == null, "前一個元素為空");
                up.next = newNode;
                newNode.previous = up;

                newNode.next = temp;
                temp.previous = newNode;
            }
        }

四、實現泛型和全部程式碼


package com.synway.test.collections.version3.finallist;

import com.synway.test.collections.version3.basesimple.Node;

/**
 * 最終手寫版,新增泛型
 *
 * @author liuyangos8888
 */
public class MyLinkedListFinal<T> {


    /**
     * 第一個節點
     */
    private Node firstNode;


    /**
     * 最後一個節點
     */
    private Node lastNode;


    /**
     * 連結串列大小
     */
    private int size;


    /**
     * 新增節點到陣列中
     *
     * @param o 節點資料
     */
    public void add(T o) {
        Node node = new Node(o);


        if (firstNode == null) {
            firstNode = node;
        } else {
            node.previous = lastNode;
            node.next = null;
            lastNode.next = node;
        }

        lastNode = node;

        size++;
    }


    /**
     * 根據索引,刪除陣列中元素
     *
     * @param index 索引角標
     */
    public void remove(int index) {
        checkRound(index);

        Node temp = getNode(index);

        if (temp != null) {
            Node up = temp.previous;
            Node down = temp.next;

            if (up != null) {
                up.next = down;
            }

            if (down != null) {
                down.previous = up;
            }

            // 刪除第一個元素的時候
            if (index == 0) {
                firstNode = down;
            }

            // 刪除最後一個元素的時候
            if (index == size - 1) {
                lastNode = up;
            }

            size--;
        }

    }


    /**
     * 插入一個元素在指定位置
     *
     * @param index 指定位置索引
     * @param o     插入的元素
     */
    public void insert(int index, T o) {

        Node newNode = new Node(o);

        // 判斷範圍
        checkRound(index);

        Node temp = getNode(index);

        if (temp != null) {

            // 第一個元素和最後一個元素的時候
            if (index == 0) {
                firstNode = newNode;
                newNode.next = temp.next;
            } else if (index == size - 1) {
                Node up = temp.previous;
                isNull(up == null, "前一個元素為空");
                up.next = newNode;
                newNode.previous = up;
            } else {
                Node up = temp.previous;
                isNull(up == null, "前一個元素為空");
                up.next = newNode;
                newNode.previous = up;

                newNode.next = temp;
                temp.previous = newNode;
            }
        }

    }


    /**
     * 優化版,提高查詢效率,折半判斷
     *
     * @param index 索引角標
     * @return 索引對應的值
     */
    public T get(int index) {
        checkRound(index);

        Node node = getNode(index);

        return node != null ? (T) node.elements : null;
    }


    /**
     * 根據角標,獲取節點
     *
     * @param index 傳入角標
     * @return 獲取節點
     */
    private Node getNode(int index) {
        Node node;
        // size>>1 除以2
        if (index <= (size >> 1)) {
            node = firstNode;

            for (int i = 0; i < index; i++) {

                node = node.next;
            }
        } else {
            node = lastNode;

            for (int i = size - 1; i > index; i--) {

                node = node.previous;
            }
        }
        return node;
    }

    /**
     * 稽核傳入的角標範圍是否越界
     *
     * @param index 傳入角標
     */
    private void checkRound(int index) {
        // 判斷索引範圍
        isNull(index < 0 || index > size - 1, "索引不合法!" + index);
    }

    /**
     * 判斷空指標問題
     *
     * @param b      判斷條件
     * @param string 丟擲異常的原因
     */
    private void isNull(boolean b, String string) {
        if (b) {
            throw new RuntimeException(string);
        }
    }

    /**
     * 獲取陣列中元素
     *
     * @return 元素陣列
     */
    @Override
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");

        Node temp = firstNode;

        while (temp != null) {
            stringBuilder.append(temp.elements).append(",");
            temp = temp.next;
        }

        stringBuilder.setCharAt(stringBuilder.length() - 1, ']');

        return stringBuilder.toString();
    }


}


相關文章