1. 程式人生 > >Java LinkedList學習筆記

Java LinkedList學習筆記

前言

最近在學習Java基礎知識,LinkedList是Java的一種常見的資料型別,所以我準備針對它做一個筆記,以供自己查閱。筆記將不僅僅侷限於LinkedList(基於1.8.0_45),還會對一些Java的知識做一些總結。文章會參照前輩的文章,希望能有所幫助~

概述

以雙向連結串列實現。連結串列無容量限制,但雙向連結串列本身使用了更多空間,每插入一個元素都要構造一個額外的Node物件,也需要額外的連結串列指標操作。
按下標訪問元素-get(i)、set(i,e) 要悲劇的部分遍歷連結串列將指標移動到位 (如果i>陣列大小的一半,會從末尾移起)。
插入、刪除元素時修改前後節點的指標即可,不再需要複製移動。但還是要部分遍歷連結串列的指標才能移動到下標所指的位置。
只有在連結串列兩頭的操作-add()、addFirst()、removeLast()或用iterator()上的remove()倒能省掉指標的移動。

它的定義是

LinkedList is an implementation of {@link List}, backed by a doubly-linked list.
All optional operations including adding, removing, and replacing elements are supported.
All elements are permitted, including null.

中文翻譯是:

LinkedList 是List的一個實現,它由雙向連結串列支援。
它支援add,remove和replace操作,並且可以包含null值。

Transient關鍵字

在LinkedList類一開始,我看到了如下程式碼:

transient int size = 0;//順便加一句,這是linkedList的容量,初始化為0

在這裡就順便對transient關鍵字做一個學習。
transient的解釋是:Java 語言規範中提到,transient 關鍵字用來說明指定屬性不進行序列化。(當持久化物件時, 可能有一個特殊的物件資料成員, 我們不想用 serialization 機制來儲存它。為了在一個特定物件的一個域上關閉serialization, 可以在這個域前加上關鍵字transient。)

序列化

序列化其實我們在Android上使用過的,它是用來持久化物件的狀態。
關於序列化的知識:

transient 關鍵字的作用?

Android中的序列化

Android中的序列化分兩種:

  • Serializable,要傳遞的類實現Serializable介面傳遞物件,
  • Parcelable,要傳遞的類實現Parcelable介面傳遞物件。

Serializable是Java自帶的,而Parcelable是Android加入的。
它們的區別是:Parcelable是將一個完整的物件進行分解,
而分解後的每一部分都是Intent所支援的資料型別。Serializable使用了反射,序列化的過程較慢。Serializable在序列化的時候會產生大量的臨時變數,從而引起頻繁的GC。Parcelable比Serializable效能高,所以在Android上推薦使用Parcelable(雖然書寫稍微麻煩一些)。

上面由一行程式碼引出了Transient關鍵字和Android序列化,下面繼續對LinkedList進行分析。

Link類

private static final class Link<ET> {
        ET data;

        Link<ET> previous, next;

        Link(ET o, Link<ET> p, Link<ET> n) {
            data = o;
            previous = p;
            next = n;
        }
    }

這裡的data指的是使用者的資料,previous和next分別指指向前後節點的指標。

建構函式

    /**
     * Constructs a new empty instance of {@code LinkedList}.
     */
    public LinkedList() {
        voidLink = new Link<E>(null, null, null);
        voidLink.previous = voidLink;
        voidLink.next = voidLink;
    }

    /**
     * Constructs a new instance of {@code LinkedList} that holds all of the
     * elements contained in the specified {@code collection}. The order of the
     * elements in this new {@code LinkedList} will be determined by the
     * iteration order of {@code collection}.
     *
     * @param collection
     *            the collection of elements to add.
     */
    public LinkedList(Collection<? extends E> collection) {
        this();
        addAll(collection);
    }

第一個函式就是構建一個header(為voidLink),並且它的previous和next都指向自己本身;第二個函式就是把一個Collection類中的所有元素新增到list中去。addAll函式將在後面進行學習。

add函式

@Override
    public void add(int location, E object) {
        if (location >= 0 && location <= size) {
            Link<E> link = voidLink;//voidLink就是常說的header
            if (location < (size / 2)) {//如果插入位置位於前半段
                for (int i = 0; i <= location; i++) {
                    link = link.next;
                }
            } else {//如果插入位置位於後半段
                for (int i = size; i > location; i--) {
                    link = link.previous;
                }
            }
            //通過上面的操作,找出了插入位置的原元素
            Link<E> previous = link.previous;
            Link<E> newLink = new Link<E>(object, previous, link);
            previous.next = newLink;
            link.previous = newLink;//這四行程式碼,把原來的元素後移一位,前面插入新的資料,並且要把前後的next和previous做相應的修改(不明白的話,依舊可以如底下畫圖來理解)
            size++;
            modCount++;
        } else {
            throw new IndexOutOfBoundsException();
        }
    }

    /**
     * Adds the specified object at the end of this {@code LinkedList}.
     *
     * @param object
     *            the object to add.
     * @return always true
     */
    @Override
    public boolean add(E object) {
        return addLastImpl(object);
    }

    private boolean addLastImpl(E object) {
        Link<E> oldLast = voidLink.previous;//voidLink也就是header
        Link<E> newLink = new Link<E>(object, oldLast, voidLink);
        voidLink.previous = newLink;
        oldLast.next = newLink;
        size++;//容量加一
        modCount++;
        return true;
    }

在這裡還要強調一下LinkedList是一個雙向迴圈的連結串列。
上面的函式分別是add(E object);add(int location, E object);addLastImpl(E object) 。
add(E object)直接呼叫了addLastImpl(E object)。在addLastImpl(E object)函式裡,把header的previous賦值給oldLast;然後新建一個新的Link物件,其中存放的是使用者需要加入的資料,並且它的previous賦值為oldLast,next賦值為voidLink的地址;這個時候,把header的previous賦值為新端點的地址,把oldlast的next端點也賦值成為新端點的地址,那麼可以說是插入成功了。因為文字略顯晦澀,這裡我將用圖來解釋一下:

因為是筆記,所以我直接使用了自己在草稿紙上畫的圖:

  • size為0的時候(初始化):
    previous和next都指向自己

  • 當size=0的時候,執行如下程式碼後的結果:

Link<E> oldLast = voidLink.previous;
Link<E> newLink = new Link<E>(object, oldLast, voidLink);

這裡寫圖片描述

  • 繼續執行後面兩行的程式碼後:
voidLink.previous = newLink;
oldLast.next = newLink;


上面兩張圖指的是size=0到size=1的變化。

  • 當size=1到size=2的過程中:
    執行如下程式碼後:
Link<E> oldLast = voidLink.previous;
Link<E> newLink = new Link<E>(object, oldLast, voidLink);

這裡寫圖片描述

  • 接上一個步驟,繼續執行如下兩行程式碼後:
voidLink.previous = newLink;
oldLast.next = newLink;

就變成了下圖:
這裡寫圖片描述

如果繼續add的話,應該還是在上圖的基礎上進行。

add(int location, E object)

然後我們再來看add(int location, E object)函式:
它可以使需要新增的資料插入到連結串列相應的位置去(在上面的程式碼中做了相應的註釋)。

addAll函式

@Override
    public boolean addAll(int location, Collection<? extends E> collection) {
        if (location < 0 || location > size) {
            throw new IndexOutOfBoundsException();
        }
        int adding = collection.size();
        if (adding == 0) {
            return false;
        }
        Collection<? extends E> elements = (collection == this) ?
                new ArrayList<E>(collection) : collection;

        Link<E> previous = voidLink;
        if (location < (size / 2)) {//同樣分情況討論,看是從前遍歷還是從後遍歷
            for (int i = 0; i < location; i++) {
                previous = previous.next;
            }
        } else {
            for (int i = size; i >= location; i--) {
                previous = previous.previous;
            }
        }
        Link<E> next = previous.next;
        for (E e : elements) {
            Link<E> newLink = new Link<E>(e, previous, null);
            previous.next = newLink;
            previous = newLink;
        }
        previous.next = next;
        next.previous = previous;
        size += adding;
        modCount++;
        return true;
    }

    @Override
    public boolean addAll(Collection<? extends E> collection) {
        int adding = collection.size();
        if (adding == 0) {
            return false;
        }
        Collection<? extends E> elements = (collection == this) ?
                new ArrayList<E>(collection) : collection;//如果collection是當前linkedList物件,那麼就新建一個Arraylist,否則elements就賦值為collection。
        Link<E> previous = voidLink.previous;//賦值為header的previous,previous的值為linkedList的最後一個元素
        for (E e : elements) {
            Link<E> newLink = new Link<E>(e, previous, null);
            previous.next = newLink;
            previous = newLink;//在這個迴圈裡,依次把元素新增在linkedList的末尾。
        }
        previous.next = voidLink;//把最後一個元素的next指向頭部的header。
        voidLink.previous = previous;//把header的previous指向最後一個元素。這兩行構成了新的迴圈(如之前的圖示)。
        size += adding;
        modCount++;
        return true;
    }

在當前類中過載了兩個addAll函式。其中第二個函式同樣在建構函式中被呼叫(如上)。
在上面的程式碼中做了相應的註釋。

set函式

    public E set(int location, E object) {
        if (location >= 0 && location < size) {
            Link<E> link = voidLink;
            if (location < (size / 2)) {
                for (int i = 0; i <= location; i++) {
                    link = link.next;
                }
            } else {
                for (int i = size; i > location; i--) {
                    link = link.previous;
                }
            }
            E result = link.data;
            link.data = object;
            return result;
        }
        throw new IndexOutOfBoundsException();
    }

從程式碼可以看出,set的時候,同樣是分情況討論的,分為從前遍歷和從後遍歷兩種情況。在set的值,需要對linkedList進行遍歷。

get函式

public E get(int location) {
        if (location >= 0 && location < size) {
            Link<E> link = voidLink;
            if (location < (size / 2)) {
                for (int i = 0; i <= location; i++) {
                    link = link.next;
                }
            } else {
                for (int i = size; i > location; i--) {
                    link = link.previous;
                }
            }
            return link.data;
        }
        throw new IndexOutOfBoundsException();
    }

get的基本邏輯其實和set很相似。也是進行遍歷,然後取值。

remove

public E remove() {
        return removeFirstImpl();
    }

public E remove(int location) {
        if (location >= 0 && location < size) {
            Link<E> link = voidLink;
            if (location < (size / 2)) {//這個if else程式碼塊在當前類經常被使用到,找出相應location的元素
                for (int i = 0; i <= location; i++) {
                    link = link.next;
                }
            } else {
                for (int i = size; i > location; i--) {
                    link = link.previous;
                }
            }
            Link<E> previous = link.previous;
            Link<E> next = link.next;
            previous.next = next;
            next.previous = previous;//改變當前location元素的指標指向關係,從而把該元素移出LinkedList
            size--;
            modCount++;
            return link.data;
        }
        throw new IndexOutOfBoundsException();
    }

    @Override
    public boolean remove(Object object) {
        return removeFirstOccurrenceImpl(object);
    }

從程式碼可以看出,remove 函式有三個。
第一個remove是不帶引數的,它直接呼叫了removeFirstImpl()函式。

private E removeFirstImpl() {
        Link<E> first = voidLink.next;//header的next,當size=0時,next指向header自己;當size>0的時候,next指向LinkedList的第一個元素。
        if (first != voidLink) {//size>0時
            Link<E> next = first.next;//next賦值為第一個元素的next值,也就是第二個元素(ps:當size=1時,又指向它本身)
            voidLink.next = next;//header的next賦值為上面的值
            next.previous = voidLink;//第二個元素(或當size=1時,就是header本身)的previous由指向first變為指向header。
            //經過上面的操作,把LinkedList的第一個元素移除了。
            size--;
            modCount++;
            return first.data;
        }
        throw new NoSuchElementException();
    }

所以remove函式在當前類的作用應該是移除LinkedList的第一個元素。

第二個函式見註釋。

第三個函式的引數是object元素,而不是索引。通過程式碼,它直接呼叫了removeFirstOccurrenceImpl(object)函式:

    private boolean removeFirstOccurrenceImpl(Object o) {
        Iterator<E> iter = new LinkIterator<E>(this, 0);//LinkIterator迭代器
        return removeOneOccurrence(o, iter);
    }

    private boolean removeOneOccurrence(Object o, Iterator<E> iter) {
        while (iter.hasNext()) {//是否存在下一個元素
            E element = iter.next();
            if (o == null ? element == null : o.equals(element)) {//如果刪除的元素是null,如果有元素的值也為null,那麼進行remove操作;如果o不是null,就呼叫equals比較。
                iter.remove();
                return true;
            }
        }
        return false;
    }

它藉助了迭代器去完成remove操作。迭代器就不再在這裡贅述了,它的定義也在LinkedList.java類中。

clear函式

public void clear() {
        if (size > 0) {
            size = 0;//將size容量置為0
            voidLink.next = voidLink;//next指向自己記憶體地址
            voidLink.previous = voidLink;//previous指向自己記憶體地址
            //這裡是不是做一個=null操作更好呢?
            modCount++;
        }
    }

contains函式

@Override
    public boolean contains(Object object) {
        Link<E> link = voidLink.next;//voidLink就是header,指向第一個有效元素(size=0的時候,指向自己)
        if (object != null) {
            while (link != voidLink) {//從頭到尾迴圈一次,到末節點指向自己為止
                if (object.equals(link.data)) {
                    return true;
                }
                link = link.next;
            }
        } else {
            while (link != voidLink) {
                if (link.data == null) {//遍歷到第一個元素為null即可
                    return true;
                }
                link = link.next;
            }
        }
        return false;
    }

後面的一些函式應該不是很常用,後面有時間的話會繼續更新。

與ArrayList的比較

LinkedList 和 ArrayList 都實現了 List 介面。LinkedList 是基於連結串列實現的,所以它的插入和刪除操作比 ArrayList 更加高效。但其隨機訪問的效率要比 ArrayList 差。

總結

其實根據每個Java版本實現程式碼都會有略微不同,如果可以的話,大家應該可以自己實現一個linkedList。所以對LinkedList的學習應該是知道LinkedList的資料結構是雙向連結串列,其餘的具體實現都是圍繞這個迴圈的雙向連結串列進行的。

參考

相關推薦

Java LinkedList學習筆記

前言 最近在學習Java基礎知識,LinkedList是Java的一種常見的資料型別,所以我準備針對它做一個筆記,以供自己查閱。筆記將不僅僅侷限於LinkedList(基於1.8.0_45),還會對一些Java的知識做一些總結。文章會參照前輩的文章,希望能有所

java.util.LinkedList學習筆記

概述 繼承結構 類描述 如繼承結構所示,LinkedList是兩個介面(List和Deque)的混合實現。其實現了List介面中所有的可選操作,並且LinkedList允許所有元素(包括null)的插入和訪問操作。LinkedLis

java IO 學習筆記

key 網絡 java io writer 讀取 方式 訪問 resources str 1.IO的數據源有: 文件 管道 網絡 內存緩存 讀寫方式有字符讀寫 reader writer ,字節讀寫 Stream。 2.IO的異常處理: try with reso

Java Web學習筆記-1

根路徑 text .get set 接口 context cat 方法 web應用 1.servlet理論上可以處理多種形式的請求響應形式 ,http只是其中之一 ,所以HttpServletRequest、 HttpServletResponse分別是ServletReq

java註解學習筆記

ati type類 包括 generated override stack color 類繼承 boolean 今天看了下有關java註解的視頻學習資料在。做點筆記: 學java註解的目的: 能看別人代碼,特別是框架代碼。由於肯定與註解有關。 編程更簡潔,代碼清晰。

java入門學習筆記之1(類的定義,代碼的編譯執行)

spa hex nts 自動調用 [] alt vim 進制 技術 這篇文章講解Java代碼的基本執行過程 我們先拋開各種JAVA IDE,開發工具,只使用文本編輯器,以突出最本質的東西。 在Linux環境下,我們編輯一個文件: vim HelloWorld.java

Java ee學習筆記

server ffffff workspace 動態創建 多公司 組成 指定 瀏覽器 現在 Servlet簡介 Servlet技術規範是JavaEE技術規範中的一個重要組成部分,Servlet是一種獨立於平臺和協議的服務器端的Java應用程序,可以生成動態的Web頁面(實際

Java基礎學習筆記Java基礎語法之接口和多態

java cas 發現 過程 類類型 結果 覆寫 實例 new 接口 接口概念 接口是功能的集合,同樣可看做是一種數據類型,是比抽象類更為抽象的”類”。接口只描述所應該具備的方法,並沒有具體實現,具體的實現由接口的實現類(相當於接口的子類)來完成

Java基礎學習筆記二十二 網絡編程

數據丟失 交互圖 主動 總結 交互 servers -- 處理 關閉 絡通信協議 通過計算機網絡可以使多臺計算機實現連接,位於同一個網絡中的計算機在進行連接和通信時需要遵守一定的規則,這就好比在道路中行駛的汽車一定要遵守交通規則一樣。在計算機網絡中,這些連接和通信的規則被

Java基礎學習筆記二十三 Java核心語法之反射

負責 目錄 boolean tostring 筆記 str 編譯 三種 進制 類加載器 類的加載 當程序要使用某個類時,如果該類還未被加載到內存中,則系統會通過加載,鏈接,初始化三步來實現對這個類進行初始化。 加載就是指將class文件讀入內存,並為之創建一個Clas

Java基礎學習筆記十六 集合框架(二)

first 哈希 cat etag 基於 col 容器 處的 新元素 List List接口的特點: 它是一個元素存取有序的集合。例如,存元素的順序是11、22、33。那麽集合中,元素的存儲就是按照11、22、33的順序完成的。 它是一個帶有索引的集合,通過索引就

Java基礎學習筆記二十四 MySQL安裝圖解

password data 默認 count 重新 doc documents tran xp系統 、MYSQL的安裝 1、打開下載的mysql安裝文件mysql-5.5.27-win32.zip,雙擊解壓縮,運行“setup.exe”。

Java基礎學習筆記二十七 DBUtils和連接池

ride 基本 代碼塊 ear 不同 一行 ria 靜態方法 ... DBUtils 如果只使用JDBC進行開發,我們會發現冗余代碼過多,為了簡化JDBC開發,本案例我們講采用apache commons組件一個成員:DBUtils。DBUtils就是JDBC的簡化開發工

Java基礎學習筆記

body -1 ride java基礎學習 功能 根據 title 過濾 ret File 的高級獲取功能 String[] list() 返回一個字符串數組,這些字符串指定此抽象路徑名表示的目錄中的文件和目錄 示例

java 核心學習筆記(四) 單例類

com null tools 初始化 equal inf div 特殊 對象 如果一個類始終只能創建一個實例,那麽這個類被稱作單例類。 一些特殊的應用場景可能會用到,為了保證只能創建一個實例,需要將構造方法用private修飾,不允許在類之外的其它地方創建類的實例。 又要保

JAVA Web學習筆記

通過 操作 實體 ive 計算機 mic 內容 類型 entity JAVA Web學習筆記 1、JSP (java服務器頁面) 鎖定 本詞條由“科普中國”百科科學詞條編寫與應用工作項目 審核 。 JSP全名為Java Server Pages,中文名叫java服

java web 學習筆記 - tomcat數據源

取數 nbsp pre connect 獲取 text ner auth 每次 1. 數據庫源  以前的JDBC連接步驟為:   1.加載數據庫驅動 2.通過DriverManger獲取數據庫連接connection 3.通過connection

Java Web 學習筆記 第一章,java語言簡介

com 分布式 ron java瀏覽器 family javadoc 全球 intellij jvm 第一章 java語言簡介 一、什麽是java? Java 編程語言:簡單、完全面向對象、分布式、解釋性、健壯、安全與系統無關、可移植、高性能、多線程和動態的編程語言。

java集合學習筆記

必須 tor contain efi 列表 entry blob 鍵值 java集合 集合容器結構圖:java容器類類庫的用途是保存對象,分為兩個概念,collection和map。collection保存單一的元素,而map保存相關聯的鍵值對。collection