1. 程式人生 > 程式設計 >?史上最全的Java容器集合之基礎資料結構(手撕連結串列)

?史上最全的Java容器集合之基礎資料結構(手撕連結串列)

前言

文字已收錄至我的GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩qq了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習群,群聊號碼:549684836 鼓勵大家在技術的路上寫部落格

絮叨

上一篇的集合的父介面 Collection 和 Iterator 大家應該都很瞭解了吧,上一篇是基礎,大家還是很有必要好好去學習一下
?史上最全的Java容器集合之入門
這一篇 帶領大家來解密,我們容器的基礎資料結構,為啥有這篇呢,本來想直接寫容器的,但是寫到一半,發現自己的資料結構也不是很好,所以帶大家瞭解一下簡單的資料結構。

陣列

第一個陣列是一種效率最高的儲存和隨機訪問的方式
陣列是一種連續儲存線性結構,元素型別相同,大小相等,存取速度快

無論使用哪種型別的陣列,陣列的識別符號[],它其實是一個引用,指在堆中建立了一個真實的物件(這個物件對我們程式設計師是不可見的),我自己找了半天,我說他既然有length屬性,那我肯定能在Java中找到這個物件,結果我硬是沒找到,這個物件儲存的是對其他物件的引用。length方法是唯一一個能用這個物件訪問的屬性或者方法

陣列的特點

  • 一致性:陣列只能儲存相同資料型別元素,元素的資料型別可以是任何相同的資料型別。
  • 有序性:陣列中的元素是有序的,通過下標訪問。
  • 不可變性:陣列一旦初始化,則長度(陣列中元素的個數)不可變。 陣列是一種連續儲存線性結構,元素型別相同,大小相等

陣列的缺點:

  • 事先必須知道陣列的長度
  • 插入刪除元素很慢
  • 空間通常是有限制的
  • 需要大塊連續的記憶體塊
  • 插入刪除元素的效率很低

帶你們來解密一下陣列

一個[I 感覺像個非法字元,我們繼續反射

我去,我發現 啥都沒有,這麼奇怪,那我為什麼能訪問它的length屬性呢?

先不管為什麼沒有length成員變數,我們先搞清楚[I這個類是哪裡宣告的。既然[I都不是合法的識別符號,那麼這個類肯定不是在Java程式碼中顯式宣告的。想來想去,只能是JVM自己在執行時生成的了。JVM生成類還是一件很容易的事情,甚至無需生成位元組碼,直接在方法區中建立型別資料,就差不多完工了。

在The JavaTM Virtual Machine Specification Second Edition 5.3.3 Creating Array Classes。描述到: 類載入器先看看陣列類是否已經被建立了。如果沒有,那就說明需要建立陣列類;如果有,那就無需建立了。如果陣列元素是引用型別,那麼類載入器首先去載入陣列元素的類。JVM根據元素型別和維度,建立相應的陣列類。

果然是JVM這傢伙自個偷偷建立了[I類。JVM不把陣列類放到任何包中,也不給他們起個合法的識別符號名稱,估計是為了避免和JDK、第三方及使用者自定義的類發生衝突。其實想想,JVM也必須動態生成陣列類,因為Java陣列類的數量與元素型別、維度(最多255)有關,相當相當多了,是沒法預先宣告好的。

0 iconst_2                   //將int型常量2壓入運算元棧  
1 newarray 10 (int)    //將2彈出運算元棧,作為長度,建立一個元素型別為int,維度為1的陣列,並將陣列的引用壓入運算元棧  
3 astore_1                 //將陣列的引用從運算元棧中彈出,儲存在索引為1的區域性變數(即a)中  
4 aload_1                  //將索引為1的區域性變數(即a)壓入運算元棧  
5 arraylength            //從運算元棧彈出陣列引用(即a),並獲取其長度(JVM負責實現如何獲取),並將長度壓入運算元棧  
6 istore_2                 //將陣列長度從運算元棧彈出,儲存在索引為2的區域性變數(即i)中  
7 return                    //main方法返回  
複製程式碼

7 return //main方法返回
可見,在這段位元組碼中,根本就沒有看見length這個成員變數,獲取陣列長度是由一條特定的指令arraylength實現編譯器對Array.length這樣的語法做了特殊處理,直接編譯成了arraylength指令。另外,JVM建立陣列類,應該就是由newarray這條指令觸發的了。

對陣列操作有一個工類類Arrays,大家可以學習一下,原始碼定義常量的方式我們也可以學習一下,用位運算,還是不錯的。

然後,博主的能力不夠了 什麼位元組碼物件 反正大家瞭解那麼多就行了。

連結串列

連結串列是離散儲存線性結構

n個節點離散分配,彼此通過指標相連,每個節點只有一個前驅節點,每個節點只有一個後續節點,首節點沒有前驅節點,尾節點沒有後續節點。

連結串列優點:

  • 空間沒有限制
  • 插入刪除元素很快

連結串列缺點:

  • 存取速度很慢

連結串列又分了好幾類:

  • 單向連結串列

    • 一個節點指向下一個節點
  • 雙向連結串列

    • 一個節點有兩個指標域
  • 迴圈連結串列

    • 能通過任何一個節點找到其他所有的節點,將兩種(雙向/單向)連結串列的最後一個結點指向第一個結點從而實現迴圈

用Java手撕連結串列

節點(Node)是由一個需要儲存的物件及對下一個節點的引用組成的。也就是說,節點擁有兩個成員:儲存的物件、對下一個節點的引用。下面圖是具體的說明:

讓我們來實現一個簡單的單向連結串列吧

先定義一個Node

新增一個節點

遍歷節點

獲取連結串列的長度

下面的程式碼實現的 增 刪 改 查 基本上都有了 大家可以自己參考一下(自己比較菜,就寫了幾個簡單的 很多東西需要演演算法的支援,就沒寫那麼多來了)



/**
 * 
 */
public class MyLink {

    
    Node head = null; 

    /**
     * 
     */
    public class Node {
    
        public Object data;
        
        public Node next;

       
        public Node(Node next) {
            this.data = data;
            this.next = next;
        }

       
        public Node(Object data) {
            this.data = data;
        }
    }

  /**
     * 新增一個節點
     *
     * @param data
     */
    public void addNode(Object data) {
        Node addNode = new Node(data); //例項化一個節點
        //判斷是不是第一個節點,如果是的話,我就把這個加入到頭節點,因為是頭節點所有就沒有前驅節點了
        if (head == null) {
            head = addNode;
        } else {
            //如果不是第一個節點 得找到當前連結串列的最後一個節點 把要加入的節點的當做最後一個節點的後繼節點
            Node temp = head; //定義一個臨時節點 把頭節點賦值給他 然後一直找,直到找到最後一個節點
            while (temp.next != null) {
                temp = temp.next;
            }
            //找到當前連結串列最後一個節點,然後把要加入的這個節點當做尾節點的後繼
            temp.next = addNode;
        }
    }

    /**
     * 遍歷連結串列
     */
    public void traverse() {
        if (null == head) {
            System.out.println("連結串列的長度為0");
        } else {

            /**
             * 說明不是隻有一個元素
             */
            Node temp = head;
            while (temp != null) {
                System.out.println(temp.data);
                temp = temp.next;
            }
        }

    }


    /**
     * 獲取連結串列的長度
     *
     * @return
     */
    public int length() {
        int length = 0;
        Node temp = head;
        while (temp != null) {
            length++;
            temp = temp.next;
        }
        return length;

    }

    // 首先需要判斷指定位置是否正確
    public void insertNode(int index,Object value) {
        if (index < 1 || index > length() + 1) {
            System.out.println("下標校驗不通過。");
            return;
        }

        //臨時節點,從頭節點開始
        Node temp = head;

        //記錄遍歷的當前位置
        int currentPos = 0;


        //初始化要插入的節點
        Node insertNode = new Node(value);

        while (temp.next != null) {

            //找到上一個節點的位置了
            if ((index - 1) == currentPos) {

                //temp表示的是上一個節點

                //將原本由上一個節點的指向交由插入的節點來指向
                insertNode.next = temp.next;

                //將上一個節點的指標域指向要插入的節點
                temp.next = insertNode;

                return;

            }

            currentPos++;
            temp = temp.next;

        }


    }

    /**
     * 刪除指定位置的節點
     *
     * @param index
     */
    public void delete(int index) {

        //首先需要判斷指定位置是否正確,
        if (index < 1 || index > length() + 1) {
            System.out.println("下標校驗不通過。");
            return;
        }

        //臨時節點,從頭節點開始
        Node temp = head;

        //記錄遍歷的當前位置
        int currentPos = 0;

        while (temp.next != null) {
            //先找到要刪除節點 上一個節點的位置

            if (index - 1 == currentPos) {
                //temp 表示要刪除的節點

                //temp.next表示的是想要刪除的節點

                //將想要刪除的節點儲存一下
                Node deleteNode = temp.next;
                //把想要刪除的下一個節點由上一個節點互動
                temp.next = deleteNode.next;


                return;

            }

            currentPos++;
            temp = temp.next;

        }


    }


    public Object getOne(int index){
        //首先需要判斷指定位置是否正確,
        if (index < 1 || index > length()) {
            System.out.println("下標校驗不通過");
            return null;
        }

        //臨時節點
        Node temp = head;


        //記錄遍歷的當前位置
        int currentPos = 1;

        while (temp!=null){

            if (index==currentPos){
                return temp.data;
            }
            currentPos++;
            temp=temp.next;

        }

        return null;

    }
  

    public static void main(String[] args) {
        MyLink myLink = new MyLink();
        myLink.addNode("1");
        myLink.addNode("aaaa");
        myLink.traverse();
        System.out.println("-------------------------------------");
        myLink.delete(1);
        myLink.addNode("333");
        myLink.traverse();
        System.out.println("-------------------------------------");
        Object one = myLink.getOne(1);
        System.out.println(one);
    }


}

複製程式碼

連結串列的一點總結

操作一個連結串列只需要知道它的頭指標就可以做任何操作了

  • 新增資料到連結串列中

    • 遍歷找到尾節點,插入即可(只要while(temp.next != null),退出迴圈就會找到尾節點)
  • 遍歷連結串列

    • 從首節點(有效節點)開始,只要不為null,就輸出
  • 給定位置插入節點到連結串列中

    • 將原本由上一個節點的指向交由插入的節點來指向

    • 上一個節點指標域指向想要插入的節點

    • 首先判斷該位置是否有效(在連結串列長度的範圍內)

    • 找到想要插入位置的上一個節點

  • 獲取連結串列的長度

    • 每訪問一次節點,變數++操作即可
  • 刪除給定位置的節點

    • 將原本由上一個節點的指向後面一個節點

    • 首先判斷該位置是否有效(在連結串列長度的範圍內)

    • 找到想要插入位置的上一個節點

  • 對連結串列進行排序

    • 使用冒泡演演算法對其進行排序
  • 刪除連結串列重複資料

    • 操作跟氣泡排序差不多,只要它相等,就能刪除了~

還有很多就不一一列舉,各位大佬要深入的自己深入吧,感覺腦子不夠用,哈哈

結尾

資料結構的基礎就講那麼多,也就是入門,後面還是靠自己,接下來我們要進入正題,講我們的容器集合了。
因為博主也是一個開發萌新 我也是一邊學一邊寫 我有個目標就是一週 二到三篇 希望能堅持個一年吧 希望各位大佬多提意見,讓我多學習,一起進步。

日常求贊

好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才

創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見

六脈神劍 | 文 【原創】如果本篇部落格有任何錯誤,請批評指教,不勝感激 !