1. 程式人生 > >2 手寫Java LinkedList核心原始碼

2 手寫Java LinkedList核心原始碼

上一章我們手寫了ArrayList的核心原始碼,ArrayList底層是用了一個數組來儲存資料,陣列儲存資料的優點就是查詢效率高,但是刪除效率特別低,最壞的情況下需要移動所有的元素。在查詢需求比較重要的情況下可以用ArrayList,如果是刪除操作比較多的情況下,用ArrayList就不太合適了。Java為我們提供了LinkedList,是用連結來實現的,我們今天就來手寫一個QLinkedList,來提示底層是怎麼做的。

如上圖,底層用一個雙鏈表,另外有兩個指示器,一個指向頭,一個指向尾。
連結串列中的每個節點的next指向下一個節點,同理pre指向上一個節點,第一個節點的pre為null,最後一個節點的next為null

雙鏈表的細節實現較多,尤其是邊界的問題,要十分仔細,LinkedList中設計了許多的小函式,本例中就不設計那麼多的小方法了,直接把最核心的程式碼都寫到一個方法中。以方便揭示核心原理。

下面是完整的QLinkedList的原始碼,註釋很清楚。

public class QLinkedList<T> {
    private QNode<T> first; //指向頭節點
    private QNode<T> last;  //指向尾節點

    private int size;    //節點的個數

    //節點類
    public static class QNode<T> {
        T value;        //資料
        QNode<T> pre;   //指向上一個節點
        QNode<T> next;  //指向下一個節點


        public QNode(QNode<T> pre, QNode<T> next, T value) {
            this.pre = pre;     //節點的上一個指向
            this.next = next;   //節點的下一個指向
            this.value = value; //存放的資料
        }
    }

    public QLinkedList() {

        //預設是一個空狼連結串列,first,last都為null, 節點個數為0
        first = null;
        last = null;
        size = 0;
    }

    //預設新增到尾
    public void add(T e) {
        addLast(e);
    }

    //新增到頭部
    public void addFirst(T e) {
        if (first == null && last == null) {
            QNode<T> node = new QNode<>(null, null, e);
            first = node;
            last = node;
        } else {
            QNode<T> node = new QNode<>(null, first, e);
            first.pre = node;
        }

        size++;
    }

    //新增到尾部,我們預設新增的都是不為null的資料
    public void addLast(T e) {
        if (e == null) {
            throw new RuntimeException("e == null");
        }

        //1 連結串列還是空的時候
        if (size == 0) {

            //1.1 新建一個節點,pre,next都為null
            QNode<T> node = new QNode(null, null, e);

            //1.2 只有一個節點,first,last都指向null
            first = node;
            last = node;


        //2 連結串列不為空
        } else {

            //2.1 新建一個節點,pre指向last最後一個節點,next為null(因為是最後一個節點)
            QNode<T> node = new QNode<>(last, null, e);

            //2.2 同時之前的最後一節點的next 指向新建的node節點
            last.next = node;

            //2.3 然後移動last,讓last指向最後一個節點
            last = node;
        }

        //新增一個節點後,別忘了節點的總數加 1
        size++;
    }

    // position 從 0 開始
    // 這裡面有個小技巧,可以先判斷一下 position 是大於 size/2 還是小於 size/2
    // 如果 position > size / 2 , 說明position是在連結串列的後半段,我們可以從last開始往前遍歷
    // 如果 position < size / 2, 說明position是在連結串列的前半段,我們可以從first開始往後遍歷
    // 這樣效率會高許多,這也是雙鏈表的意義所在,我們這裡就不這樣做了。直接從前往後遍歷
    // 讀者可以自己實現,以加深對連結串列的理解
    public T get(int position) {
        // 不合法的position直接拋異常,讓開發者直接定位問題
        if (position < 0 || position > size - 1) {
            throw new RuntimeException("invalid position");
        }

        // 如果連結串列為空,直接返回null
        if (size == 0) {
            return null;
        }

        // 如果連結串列只有一個節點,直接返回
        // 因為position合法性在前面已經驗證過
        // 所以在這裡面不用驗證,一定是0
        if(size == 1){
            return first.value;
        }

        // 注意這個新建的 p 節點,p.next 指向的是 first
        // 這是為了下面的迴圈,保證 i == 0 的時候,p 指向第一個節點
        QNode<T> p = new QNode<>(null, first, null);
        for (int i = 0; i <= position; i++) {
            p = p.next;
        }

        //如果找到了,就返回value
        if (p != null) {
            return p.value;
        }

        //否則返回 null
        return null;
    }

    // 返回連結串列的節點總個數
    // 注意first和last節點只是幫助我們方便操作的
    // size可不包括first,last
    public int size() {
        return size;
    }

    // 刪除一個元素,這裡傳的引數是 T e ,我們也可以傳position進行刪除,這裡就不作演示了
    // 可以先呼叫上面的get()方法,返回對應的值,再呼叫此方法
    // 讀者可以自己實現
    public T remove(T e) {
        //1 不合法,拋異常
        if (e == null) {
            throw new RuntimeException("e == null");
        }

        //2 連結串列為空,返回 null
        if (size == 0) {
            return null;
        }

        //2 如果連結串列只有一個節點
        if (size == 1) {
            QNode<T> node = first;

            //3 如果相等,刪除節點 size-- ,並把first,last賦值為null
            if(e == node.value || e.equals(node.value)){
                first = last = null;
                size--;
                return node.value;
            }else {
                //4 不相等,返回null
                return null;
            }
        }

        // 如果連結串列大於1個節點,我們從前往後找value等於e的節點
        // 1 查詢, 和get()方法一樣,注意p的next指向first
        QNode<T> p = new QNode<>(null, first, null);
        boolean find = false;
        for (int i = 0; i < size; i++) {
            p = p.next;

            if (p != null && (e == p.value || e.equals(p.value))) {
                find = true;
                break;
            }
        }

        // 2 如果找到了
        if (find) {
            // 2.1 如果找到的節點是最後一個節點
            // 刪除的是最後一個
            if (p.next == null) {

                //2.2 改變last的值,指向p的前一個節點
                last = p.pre;

                //2.3 p的前一個節點,變成了最後一個節點,所以,前一個節點的next值賦值為null
                p.pre.next = null;

                //2.4 把p.pre賦值為null,已經沒有用了
                p.pre = null;

                //2.5 別忘了節點個數減1
                size--;

                //2.6 返回刪除的節點的value
                return p.value;

            //3.1 如果刪除的是第一個節點(p.pre == null就表明是第一個節點)
            } else if (p.pre == null) {

                //3.2 改變first的指向,指向p的下一個節點
                first = p.next;

                //3.3 p的下一個節點變成了第一個節點,需要把p的下一個節點的pre指向為null
                p.next.pre = null;

                //3.4 p.next沒有用了
                p.next = null;

                //3.5 別忘了節點個數減1
                size--;

                //3.6 返回刪除的節點的value
                return p.value;


            // 4 如果刪除的不是第一個也不是最後一個,是中間的某一個,這種情況最簡單
            } else {

                //4.1 p的上一個節點的next需要指向p的下一個節點
                p.pre.next = p.next;

                //4.2 p 的下一個節點的pre需要指向p的上一個節點
                p.next.pre = p.pre;

                //4.3 此時p無用了,把p的pre,next賦值為null
                //這時候不需要調整first,last的位置
                p.pre = null;
                p.next = null;

                //4.4 別忘了節點個數減1
                size--;

                //4.5 返回刪除的節點的value
                return p.value;
            }
        }

        //沒有找到與e相等的節點,直接返回null
        return null;
    }

}

我們來測試一下QLinkedList,測試程式碼如下:

   public static void main(String[] args) {
        QLinkedList<String> list = new QLinkedList<>();
        list.add("one");
        list.add("two");
        list.add("three");
        list.add("four");

        System.out.println(list.size);
        for (int i = 0; i < list.size; i++) {
            System.out.println(list.get(i));
        }

        System.out.println("===================");

        System.out.println(list.remove("two"));
        System.out.println(list.size);
        for (int i = 0; i < list.size; i++) {
            System.out.println(list.get(i));
        }
    }

輸出如下:

4
one
two
three
four
===================
two
3
one
three
four

由此可見我們的QLinkedList可以正常的add,get,size,remove了。
建議可以參考一下JDK中的LinkedList。以加深對LinkedList的理解
明天手寫HashMap的核心原始碼實現

相關推薦

2 Java LinkedList核心原始碼

上一章我們手寫了ArrayList的核心原始碼,ArrayList底層是用了一個數組來儲存資料,陣列儲存資料的優點就是查詢效率高,但是刪除效率特別低,最壞的情況下需要移動所有的元素。在查詢需求比較重要的情況下可以用ArrayList,如果是刪除操作比較多的情況下,用ArrayList就不太合適了。Java為我

4.2 Java PriorityQueue 核心原始碼

上一節介紹了PriorityQueue的原理,先來簡單的回顧一下 PriorityQueue 的原理 以最大堆為例來介紹 PriorityQueue是用一棵完全二叉樹實現的。 不但是棵完全二叉樹,而且樹中的每個根節點都比它的左右兩個孩子節點元素大 PriorityQueue底層是用陣列來儲存

5 Java Stack 核心原始碼

Stack是Java中常用的資料結構之一,Stack具有"後進先出(LIFO)"的性質。 只能在一端進行插入或者刪除,即壓棧與出棧 棧的實現比較簡單,性質也簡單。可以用一個數組來實現棧結構。 入棧的時候,只在陣列尾部插入 出棧的時候,只在陣列尾部刪除** 我們來看一下Stack的用法 :如

6 Java LinkedHashMap 核心原始碼

概述 LinkedHashMap是Java中常用的資料結構之一,安卓中的LruCache快取,底層使用的就是LinkedHashMap,LRU(Least Recently Used)演算法,即最近最少使用演算法,核心思想就是當快取滿時,會優先淘汰那些近期最少使用的快取物件 LruCache的快取演算法

3 Java HashMap核心原始碼

手寫Java HashMap核心原始碼 上一章手寫LinkedList核心原始碼,本章我們來手寫Java HashMap的核心原始碼。 我們來先了解一下HashMap的原理。HashMap 字面意思 hash + map,map是對映的意思,HashMap就是用hash進行對映的意思。不明白?沒關係。我們來具

4.1 Java PriorityQueue 核原始碼

本章先講解優先順序佇列和二叉堆的結構。下一篇程式碼實現 從一個需求開始 假設有這樣一個需求:在一個子執行緒中,不停的從一個佇列中取出一個任務,執行這個任務,直到這個任務處理完畢,再取出下一個任務,再執行。 其實和 Android 的 Handler 機制中的 Looper 不停的從 MessageQue

原始碼分析 | mybait-spring核心功能(乾貨好文一次學會工廠bean、類代理、bean註冊的使用)

![](https://img2020.cnblogs.com/blog/2030202/202006/2030202-20200609215006431-1003784316.jpg) 作者:小傅哥 部落格:[https://bugstack.cn](https://bugstack.cn) - `彙總系

)mybatis 核心配置文件和接口不在同一包下的解決方案

內置 中間 configure idea pan 數據源配置 uil 基礎 主目錄 smart-sh-mybatis項目app.xml文件中此處配置為: 1 <!-- 從整合包裏找,org.mybatis:mybatis-spring:1.2.4 -->

spring事物(2)-----spring註解事務&&事務傳播行為

一,spring事務的註解 1.1,spring自帶的@Transactional例子   package com.qingruihappy1.dao; import org.springframework.beans.factory.annotation.Autowired; imp

REDIS (15)Java Redis客戶端(1)RESP協議分析(未完)

一直對Jedis有點興趣, 現在靜下心來抽空看看redis客戶端和消費端是怎麼連線的 1. 對Jedis的get命令抓包 傳送 接收 可見是明文協議,0d 0a 是 \r\n我們找下文件 redis 序列化協議 2. 模仿協議內容傳送並接收內容 2.1 傳統BIO

【python與機器學習入門1】KNN(k近鄰)演算法2 識別系統

參考部落格:超詳細的機器學習python入門knn乾貨 (po主Jack-Cui 參考書籍:《機器學習實戰》——第二章 KNN入門第二彈——手寫識別系統demo ——《機器學習實戰》第二章2.3 手寫識別系統       &

java集合核心原始碼01——ArrayList

首先呢,對於ArrayList,相當於一個動態陣列,裡面可以儲存重複資料,並且支援隨機訪問,不是執行緒安全的。對於更多的底層東西,且聽分解。 開啟原始碼,先看繼承了哪些類,實現了哪些介面,然後繼承的這些類或介面是否還有父類,一直深挖到頂部   public class ArrayList

2 實現SpringMVC,第二節:自定義註解及反射賦值

還是回到最終要實現的效果。 可以發現,這裡面使用了大量的自定義註解,並且還有autuwire的屬性也需要被賦值(Spring的IOC功能)。 先來建立自定義註解 注意,根據不同的註解使用的範圍來定義@Target,譬如Controller,Service能註解到類,R

1.事件委託的原理以及優缺點 2. 原生js實現事件代理,並要求相容瀏覽器

Q:事件的委託(代理 Delegated Events)的原理以及優缺點 A:委託(代理)事件是那些被繫結到父級元素的事件,但是隻有當滿足一定匹配條件時才會被挪。這是靠事件的冒泡機制來實現的, 優點是: (1)可以大量節省記憶體佔用,減少事件註冊,比如在table上

Spring學習之——Mini版Spring原始碼

前言 Sping的生態圈已經非常大了,很多時候對Spring的理解都是在會用的階段,想要理解其設計思想卻無從下手。前些天看了某某學院的關於Spring學習的相關視訊,有幾篇講到手寫Spring原始碼,感覺有些地方還是說的挺好的,讓博主對Spring的理解又多了一些,於是在業餘時間也按照視訊講解實現一遍Spri

原始碼分析之基於LinkedListHahMap(二)

package com.mayikt.extLinkedListHashMap; import java.util.LinkedList; import java.util.concurrent.ConcurrentHashMap; /** * 基於linkedList實現hashMap *

JavaLinkedList 應用資料結構之雙向連結串列

作為Java程式設計師,紮實的資料結構演算法能力是必須的 LinkedList理解的最好方式是,自己手動實現它         ArrayList和LinkedList是順序儲存結構和鏈式儲存結構的表在java語言中的實現.   ArrayList提供了一種可增長陣

攜程系統架構師帶你spring mvc,解讀spring核心原始碼

講師簡介: James老師 系統架構師、專案經理 十餘年Java經驗,曾就職於攜程、人人網等一線網際網路公司,專注於java領域,精通軟體架構設計,對於高併發、高效能服務有深刻的見解, 在服務化基礎架構和微服務技術有大量的建設和設計經驗。   課程內容: 1.為什麼讀Spr

1 ArrayList核心原始碼

手寫ArrayList核心原始碼 ArrayList是Java中常用的資料結構,不光有ArrayList,還有LinkedList,HashMap,LinkedHashMap,HashSet,Queue,PriorityQueue等等,我們將手寫這些常用的資料結構的核心原始碼,用盡量少的程式碼來揭示核心原理。

爬蟲入門 一個Java爬蟲

fun sts 重試 功能 bool 內核 ftw private 查找 本文內容 淶源於 羅剛 老師的 書籍 << 自己動手寫網絡爬蟲一書 >> ; 本文將介紹 1: 網絡爬蟲的是做什麽的? 2: 手動寫一個簡單的網絡爬蟲; 1: 網絡爬蟲是做