最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文掌握J(rèn)ava 數(shù)據(jù)結(jié)構(gòu)超詳細(xì)筆記(收藏版)

 更新時(shí)間:2025年09月19日 14:45:49   作者:聰明勇敢有力氣!  
本文給大家介紹Java數(shù)據(jù)結(jié)構(gòu),包括數(shù)組、鏈表、List、Set、Map及迭代器,對(duì)比了其內(nèi)存特性、操作效率和應(yīng)用場(chǎng)景,并介紹了哈希及沖突解決方法,感興趣的朋友跟隨小編一起看看吧

前言

Java 中的數(shù)據(jù)結(jié)構(gòu)是用來存儲(chǔ)和組織數(shù)據(jù)的方式,不同的數(shù)據(jù)結(jié)構(gòu)適用于不同的場(chǎng)景。數(shù)據(jù)結(jié)構(gòu)的理解非常重要,如果哪里寫的不對(duì),請(qǐng)指出。

一、數(shù)組(Array)

數(shù)組是一種線性表,就是數(shù)據(jù)排列成一條直線一樣的結(jié)構(gòu)。在內(nèi)容空間中,數(shù)組的表現(xiàn)是一塊連續(xù)的內(nèi)存和儲(chǔ)存有相同的數(shù)據(jù)類型。正因?yàn)檫@個(gè)特性,數(shù)組可以實(shí)現(xiàn)通過索引下標(biāo),在O(1)的時(shí)間復(fù)雜度內(nèi)快速檢索某個(gè)數(shù)據(jù),這就是“隨機(jī)訪問”。但是由于內(nèi)存空間是連續(xù)的,所以數(shù)組在進(jìn)行插入和刪除操作時(shí),就需要對(duì)數(shù)據(jù)進(jìn)行維護(hù),進(jìn)行大量的數(shù)據(jù)搬移工作。

數(shù)組初始化的方式

使用動(dòng)態(tài)初始化
動(dòng)態(tài)初始化是指先使用 new 關(guān)鍵字指定數(shù)組的長(zhǎng)度,之后再為數(shù)組元素賦值。

public class ArrayDynamicInitialization {
    public static void main(String[] args) {
        // 動(dòng)態(tài)初始化一個(gè)長(zhǎng)度為 5 的整型數(shù)組
        int[] arr = new int[5];
        // 為數(shù)組元素賦值
        arr[0] = 1;
        arr[1] = 2;
        // 輸出數(shù)組元素
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}    

使用靜態(tài)初始化
靜態(tài)初始化時(shí),無需使用 new 關(guān)鍵字顯式指定數(shù)組長(zhǎng)度,而是直接在聲明數(shù)組的同時(shí)為其元素賦值。

public class ArrayStaticInitialization {
    public static void main(String[] args) {
        // 靜態(tài)初始化一個(gè)整型數(shù)組
        int[] arr = {1, 2, 3, 4, 5};
        // 輸出數(shù)組元素
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}

注意:如果聲明了數(shù)組但沒有進(jìn)行初始化就直接使用,會(huì)導(dǎo)致編譯通過,但在運(yùn)行時(shí)拋出 NullPointerException 異常。

數(shù)組能否存儲(chǔ)空值(null)

基本類型數(shù)組不能存儲(chǔ) null 值。Java 中的基本數(shù)據(jù)類型(如 int、double、char 等)有其對(duì)應(yīng)的默認(rèn)值,當(dāng)創(chuàng)建基本類型數(shù)組時(shí),數(shù)組元素會(huì)被初始化為這些默認(rèn)值,而不能將 null 賦值給它們。
例如:

public class PrimitiveArrayNull {
    public static void main(String[] args) {
        // 創(chuàng)建一個(gè) int 類型的數(shù)組
        int[] intArray = new int[3];
        // 嘗試將 null 賦值給基本類型數(shù)組元素,會(huì)導(dǎo)致編譯錯(cuò)誤
        // intArray[0] = null; 
        // 基本類型數(shù)組元素有默認(rèn)值,int 類型默認(rèn)值為 0
        System.out.println(intArray[0]); 
    }
}    

引用類型數(shù)組可以存儲(chǔ) null 值。引用類型數(shù)組的元素存儲(chǔ)的是對(duì)象的引用,null 表示該引用不指向任何對(duì)象。
例如:

class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
public class ReferenceArrayNull {
    public static void main(String[] args) {
        // 創(chuàng)建一個(gè) Person 類型的數(shù)組
        Person[] personArray = new Person[3];
        // 可以將 null 賦值給引用類型數(shù)組元素
        personArray[0] = null; 
        // 創(chuàng)建一個(gè) Person 對(duì)象并賦值給數(shù)組元素
        personArray[1] = new Person("Alice");
        // 輸出數(shù)組元素
        for (int i = 0; i < personArray.length; i++) {
            if (personArray[i] != null) {
                System.out.println(personArray[i].getName());
            } else {
                System.out.println("Element at index " + i + " is null");
            }
        }
    }
}    

Java中的包裝類是引用數(shù)據(jù)類型‌。包裝類(Wrapper Classes)是為Java中的8種基本數(shù)據(jù)類型(boolean、char、byte、short、int、long、float、double)分別定義的對(duì)應(yīng)引用類型。這些包裝類包括:Boolean、Character、Byte、Short、Integer、Long、Float、Double‌。
包裝類的作用是將基本數(shù)據(jù)類型轉(zhuǎn)換為引用數(shù)據(jù)類型,以便在泛型和集合中使用。通過包裝類,可以將基本數(shù)據(jù)類型作為對(duì)象處理,從而可以使用對(duì)象的屬性和方法。例如,Integer類提供了將字符串轉(zhuǎn)換為整數(shù)的靜態(tài)方法valueOf(),以及將整數(shù)轉(zhuǎn)換為字符串的toString()方法‌。

內(nèi)置方法

新增
數(shù)組沒有內(nèi)置的新增方法,只能根據(jù)索引賦值,(如 arr[0] = 10;)。

刪除
數(shù)組沒有內(nèi)置的刪除方法,不過,你可以通過一些間接的方法來模擬刪除操作。如果需要頻繁進(jìn)行刪除操作,使用 Java 的集合類(如 ArrayList)會(huì)更方便。
例如:
創(chuàng)建一個(gè)新數(shù)組,把原數(shù)組中除了要?jiǎng)h除元素之外的其他元素復(fù)制到新數(shù)組中。

public class DeleteElementByNewArray {
    public static void main(String[] args) {
        int[] originalArray = {1, 2, 3, 4, 5};
        int indexToDelete = 2; // 要?jiǎng)h除元素的索引
        int[] newArray = new int[originalArray.length - 1];
        int newIndex = 0;
        for (int i = 0; i < originalArray.length; i++) {
            if (i != indexToDelete) {
                newArray[newIndex] = originalArray[i];
                newIndex++;
            }
        }
        // 輸出新數(shù)組元素
        for (int element : newArray) {
            System.out.println(element);
        }
    }
}    

修改
數(shù)組沒有內(nèi)置的修改方法,只能覆蓋值,通過索引賦值(如 arr[0] = 10;)是唯一修改數(shù)組元素的方式。

查詢
數(shù)組沒有內(nèi)置專門用于查詢特定元素是否存在或獲取元素位置的方法,通過循環(huán)遍歷數(shù)組中的每一個(gè)元素,將其與目標(biāo)元素進(jìn)行比較,從而判斷數(shù)組中是否包含該元素,或者找到元素所在的位置。

遍歷
數(shù)組沒有內(nèi)置的遍歷方法,只能通過傳統(tǒng) for 循環(huán),或增強(qiáng) for 循環(huán)(for - each 循環(huán))實(shí)現(xiàn)數(shù)組的遍歷。

擴(kuò)容
無法直接擴(kuò)容:數(shù)組沒有像 ArrayList 的 add() 方法那樣的動(dòng)態(tài)擴(kuò)容機(jī)制。

存儲(chǔ)位置

不管是基本類型的數(shù)組還是其他對(duì)象數(shù)組,都會(huì)存于堆內(nèi)存。棧內(nèi)存里只存放指向這些數(shù)組對(duì)象的引用, 通過引用可以在程序中對(duì)數(shù)組對(duì)象進(jìn)行操作。

數(shù)組對(duì)象存于堆內(nèi)存

堆內(nèi)存是用于存儲(chǔ)對(duì)象實(shí)例的區(qū)域。數(shù)組本質(zhì)上也是對(duì)象,不管是基本類型(如 int、char 等)的數(shù)組,還是對(duì)象類型(如自定義類、String 等)的數(shù)組,都是通過 new 關(guān)鍵字或者靜態(tài)初始化(編譯器會(huì)隱式轉(zhuǎn)換為 new 操作)來創(chuàng)建的,而使用 new 操作創(chuàng)建的對(duì)象都會(huì)被分配到堆內(nèi)存中。
對(duì)于對(duì)象類型,這些引用也存儲(chǔ)在堆上的數(shù)組對(duì)象內(nèi)部;而對(duì)象實(shí)體本身則可能(但不是一定)存儲(chǔ)在堆上。

棧內(nèi)存存放數(shù)組對(duì)象引用

棧內(nèi)存主要用于存儲(chǔ)局部變量和方法調(diào)用的上下文信息。當(dāng)在方法內(nèi)部聲明一個(gè)數(shù)組變量時(shí),這個(gè)變量實(shí)際上是一個(gè)引用類型的變量,它存儲(chǔ)在棧內(nèi)存中,其值是指向堆內(nèi)存中數(shù)組對(duì)象的地址。
在程序中,對(duì)數(shù)組對(duì)象的各種操作(如訪問元素、修改元素值等)都是通過棧內(nèi)存中的引用進(jìn)行的。

優(yōu)點(diǎn)和影響
動(dòng)態(tài)內(nèi)存分配: 堆內(nèi)存允許在運(yùn)行時(shí)動(dòng)態(tài)分配和釋放內(nèi)存,使得數(shù)組的大小可以根據(jù)程序的需求進(jìn)行調(diào)整(雖然 Java 數(shù)組本身大小固定,但可以通過創(chuàng)建新數(shù)組并復(fù)制元素來模擬動(dòng)態(tài)調(diào)整)。

多方法共享對(duì)象: 不同的方法可以通過引用訪問同一個(gè)堆內(nèi)存中的數(shù)組對(duì)象,實(shí)現(xiàn)數(shù)據(jù)的共享和傳遞。
不過,也存在一些潛在的問題,比如堆內(nèi)存的垃圾回收機(jī)制會(huì)帶來一定的性能開銷,需要合理管理對(duì)象的生命周期以避免內(nèi)存泄漏。

數(shù)組特性:

查詢和修改速度快

連續(xù)內(nèi)存存儲(chǔ):數(shù)組在內(nèi)存中是一段連續(xù)的存儲(chǔ)空間,每個(gè)元素在內(nèi)存中依次排列
高效的地址計(jì)算:根據(jù)數(shù)組的起始地址和元素的索引,能通過簡(jiǎn)單的計(jì)算快速定位到元素的位置。假設(shè)數(shù)組的起始地址是 baseAddress,每個(gè) int 類型元素占用 4 個(gè)字節(jié),要訪問索引為 i 的元素,其內(nèi)存地址可以通過公式 baseAddress + i * 4 計(jì)算得出。這種基于地址計(jì)算的訪問方式時(shí)間復(fù)雜度是 O(1),意味著無論數(shù)組的長(zhǎng)度是多少,訪問任意元素的時(shí)間都是固定的,所以查詢速度非??臁?br />直接覆蓋值:修改數(shù)組元素時(shí),只需定位到該元素的內(nèi)存位置,然后將新的值覆蓋原來的值即可。由于查詢?cè)匚恢玫牟僮骱芸?,所以修改操作也能快速完成,時(shí)間復(fù)雜度同樣為 O(1)。

增加和刪除操作慢

插入操作:在數(shù)組中插入元素時(shí),通常需要將插入位置之后的所有元素向后移動(dòng),為新元素騰出空間。
刪除操作:刪除數(shù)組中的元素時(shí),需要將刪除位置之后的所有元素向前移動(dòng),填補(bǔ)刪除元素所留下的空位。
擴(kuò)容問題:如果數(shù)組容量不足,在增加元素時(shí)還需要進(jìn)行擴(kuò)容操作。通常的做法是創(chuàng)建一個(gè)更大的新數(shù)組,再把原數(shù)組的元素復(fù)制到新數(shù)組中,這也會(huì)增加操作的時(shí)間復(fù)雜度。

基礎(chǔ)數(shù)組,在增加和刪除,以及擴(kuò)容上,其實(shí)不符合數(shù)據(jù)結(jié)構(gòu)中對(duì)數(shù)組的定義,因?yàn)樵臄?shù)組沒有增加和刪除方法,也不能自動(dòng)擴(kuò)容。

在深入一些,數(shù)組的底層代碼數(shù)據(jù)結(jié)構(gòu)

在 Java 里,數(shù)組實(shí)際上是一個(gè)對(duì)象。每個(gè)數(shù)組對(duì)象都包含一個(gè)頭部信息和元素?cái)?shù)據(jù)區(qū)。
頭部信息: 包含數(shù)組的一些元數(shù)據(jù),例如數(shù)組的長(zhǎng)度。這個(gè)長(zhǎng)度信息在數(shù)組創(chuàng)建時(shí)就被確定,并且后續(xù)無法更改??梢酝ㄟ^數(shù)組對(duì)象的 length 屬性來獲取這個(gè)長(zhǎng)度值,像 arr.length 就能得到數(shù)組 arr 的長(zhǎng)度。
元素?cái)?shù)據(jù)區(qū): 數(shù)組的元素存儲(chǔ)在連續(xù)的內(nèi)存塊中。對(duì)于基本數(shù)據(jù)類型的數(shù)組(如int[]、double[]等),這些元素直接存儲(chǔ)在這個(gè)內(nèi)存塊中。對(duì)于對(duì)象數(shù)組(如Object[]、String[]等),這些對(duì)象引用存儲(chǔ)在內(nèi)存塊中,而對(duì)象本身一般存儲(chǔ)在堆內(nèi)存中。

多維數(shù)組

Java 支持多維數(shù)組,多維數(shù)組本質(zhì)上是數(shù)組的數(shù)組。例如,二維數(shù)組可以看作是由多個(gè)一維數(shù)組組成的。

int[][] twoDArray = new int[3][4];

這里創(chuàng)建了一個(gè) 3 行 4 列的二維數(shù)組。在底層,它首先是一個(gè)包含 3 個(gè)元素的一維數(shù)組,每個(gè)元素又是一個(gè)包含 4 個(gè) int 類型元素的一維數(shù)組。每個(gè)一維子數(shù)組在內(nèi)存中也是連續(xù)存儲(chǔ)的,但不同的一維子數(shù)組在內(nèi)存中不一定是連續(xù)的。

二、鏈表

在 Java 里,鏈表主要有三種,分別是單向鏈表、雙向鏈表和循環(huán)鏈表。鏈表的節(jié)點(diǎn)和數(shù)組一樣,都存儲(chǔ)在堆內(nèi)存中。鏈表的每個(gè)節(jié)點(diǎn)都是獨(dú)立的對(duì)象,通過引用相互連接,數(shù)組是連續(xù)內(nèi)存,而鏈表是不連續(xù)內(nèi)存。

單向鏈表

結(jié)構(gòu)特點(diǎn)
單向鏈表由一系列節(jié)點(diǎn)構(gòu)成,每個(gè)節(jié)點(diǎn)包含兩部分:數(shù)據(jù)域和指向下一個(gè)節(jié)點(diǎn)的引用(指針)。鏈表的第一個(gè)節(jié)點(diǎn)稱為頭節(jié)點(diǎn),最后一個(gè)節(jié)點(diǎn)的引用指向 null,表示鏈表的結(jié)束。
插入和刪除操作: 在已知節(jié)點(diǎn)的情況下,在鏈表中間插入或刪除節(jié)點(diǎn)的時(shí)間復(fù)雜度為 O(1),因?yàn)橹恍枰薷墓?jié)點(diǎn)的引用。但如果要在指定位置插入或刪除節(jié)點(diǎn),需要先遍歷鏈表找到該位置,此時(shí)時(shí)間復(fù)雜度為 O(n)。
查詢操作: 由于單向鏈表只能從頭節(jié)點(diǎn)開始依次遍歷,所以查詢指定位置或值的節(jié)點(diǎn)的時(shí)間復(fù)雜度為 O(n)。
空間開銷: 每個(gè)節(jié)點(diǎn)只需要額外存儲(chǔ)一個(gè)指向下一個(gè)節(jié)點(diǎn)的引用,空間開銷相對(duì)較小。

雙向鏈表

結(jié)構(gòu)特點(diǎn)
雙向鏈表的節(jié)點(diǎn)除了包含數(shù)據(jù)域和指向下一個(gè)節(jié)點(diǎn)的引用外,還包含一個(gè)指向前一個(gè)節(jié)點(diǎn)的引用。這使得鏈表可以雙向遍歷,既可以從頭節(jié)點(diǎn)開始向后遍歷,也可以從尾節(jié)點(diǎn)開始向前遍歷。
插入和刪除操作: 在已知節(jié)點(diǎn)的情況下,插入和刪除節(jié)點(diǎn)的時(shí)間復(fù)雜度為
O(1),因?yàn)榭梢灾苯有薷那昂蠊?jié)點(diǎn)的引用。同樣,如果要在指定位置插入或刪除節(jié)點(diǎn),需要先遍歷鏈表找到該位置,時(shí)間復(fù)雜度為 O(n)。
查詢操作: 查詢指定位置或值的節(jié)點(diǎn)的時(shí)間復(fù)雜度為 O(n),但可以根據(jù)節(jié)點(diǎn)的位置選擇從頭部或尾部開始遍歷,在某些情況下可以提高查詢效率。
空間開銷: 每個(gè)節(jié)點(diǎn)需要額外存儲(chǔ)兩個(gè)引用(指向前一個(gè)節(jié)點(diǎn)和后一個(gè)節(jié)點(diǎn)),空間開銷相對(duì)單向鏈表較大。

循環(huán)鏈表

結(jié)構(gòu)特點(diǎn)
循環(huán)鏈表分為單向循環(huán)鏈表和雙向循環(huán)鏈表。單向循環(huán)鏈表中,最后一個(gè)節(jié)點(diǎn)的引用指向頭節(jié)點(diǎn),形成一個(gè)閉環(huán);雙向循環(huán)鏈表中,頭節(jié)點(diǎn)的前一個(gè)引用指向尾節(jié)點(diǎn),尾節(jié)點(diǎn)的后一個(gè)引用指向頭節(jié)點(diǎn)。
插入和刪除操作: 與單向鏈表和雙向鏈表類似,在已知節(jié)點(diǎn)的情況下,插入和刪除節(jié)點(diǎn)的時(shí)間復(fù)雜度為 O(1),但在指定位置插入或刪除節(jié)點(diǎn)需要先遍歷鏈表,時(shí)間復(fù)雜度為 O(n)。
查詢操作: 查詢指定位置或值的節(jié)點(diǎn)的時(shí)間復(fù)雜度為 O(n),由于鏈表是循環(huán)的,在遍歷過程中需要注意避免陷入無限循環(huán)。
應(yīng)用場(chǎng)景: 循環(huán)鏈表適用于需要循環(huán)訪問數(shù)據(jù)的場(chǎng)景,如實(shí)現(xiàn)循環(huán)隊(duì)列、游戲中的循環(huán)角色列表等。

數(shù)組與鏈表的區(qū)別是什么?

內(nèi)存分配:
數(shù)組在內(nèi)存中是連續(xù)分配的,而鏈表的節(jié)點(diǎn)在內(nèi)存中不一定連續(xù),通過指針相互連接。
隨機(jī)訪問:
數(shù)組支持隨機(jī)訪問,通過索引可以直接訪問數(shù)組中的元素,時(shí)間復(fù)雜度為 O (1)。鏈表不支持隨機(jī)訪問,要訪問某個(gè)節(jié)點(diǎn),需要從鏈表頭開始遍歷,時(shí)間復(fù)雜度為 O (n)。
插入和刪除操作:
在數(shù)組中間插入或刪除元素時(shí),需要移動(dòng)大量元素,時(shí)間復(fù)雜度為 O (n)。鏈表在插入和刪除節(jié)點(diǎn)時(shí),只需修改相關(guān)節(jié)點(diǎn)的指針,時(shí)間復(fù)雜度為 O (1)(前提是已經(jīng)找到要操作節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn))。
內(nèi)存空間:
數(shù)組需要預(yù)先分配固定大小的內(nèi)存空間,如果元素?cái)?shù)量不確定,可能會(huì)造成內(nèi)存浪費(fèi)或溢出。鏈表則根據(jù)需要?jiǎng)討B(tài)分配內(nèi)存,靈活性更高,但每個(gè)節(jié)點(diǎn)需要額外的空間存儲(chǔ)指針。

三、Collection (單列集合)

List接口:

List是一個(gè)有序的集合,允許重復(fù)元素,并且每個(gè)元素都有一個(gè)位置索引。

ArrayList:

底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組,查詢快,增刪慢,線程不安全,效率高,可以存儲(chǔ)重復(fù)元素。

ArrayList擴(kuò)容機(jī)制
ArrayList 的底層是基于數(shù)組實(shí)現(xiàn)的,當(dāng)向 ArrayList 中添加元素時(shí),如果當(dāng)前數(shù)組的容量不足以容納新元素,就會(huì)觸發(fā)擴(kuò)容操作。
具體的擴(kuò)容步驟如下:
計(jì)算新容量:默認(rèn)情況下,新容量是舊容量的 1.5 倍(oldCapacity + (oldCapacity >> 1))。例如,初始容量為 10,當(dāng)添加第 11 個(gè)元素時(shí),會(huì)觸發(fā)擴(kuò)容,新容量為 10 + (10 >> 1) = 15。
創(chuàng)建新數(shù)組: 根據(jù)計(jì)算得到的新容量,創(chuàng)建一個(gè)新的數(shù)組。
復(fù)制元素: 將舊數(shù)組中的元素復(fù)制到新數(shù)組中。
更新引用: 將 ArrayList 內(nèi)部的數(shù)組引用指向新數(shù)組。

LinkedList:

底層數(shù)據(jù)結(jié)構(gòu)是雙向鏈表,查詢慢,增刪快,線程不安全.效率高,可以存儲(chǔ)重復(fù)元素。

LinkedList不存在 “擴(kuò)容” 概念的原因
與 ArrayList 基于數(shù)組實(shí)現(xiàn)不同,LinkedList 是基于鏈表實(shí)現(xiàn)的。數(shù)組在創(chuàng)建時(shí)需要指定固定的長(zhǎng)度,當(dāng)元素?cái)?shù)量超過數(shù)組容量時(shí),需要?jiǎng)?chuàng)建一個(gè)更大的數(shù)組并將原數(shù)組元素復(fù)制過去,這就是 ArrayList 的擴(kuò)容機(jī)制。而 LinkedList 中的節(jié)點(diǎn)是動(dòng)態(tài)分配內(nèi)存的,每添加一個(gè)新元素,就會(huì)創(chuàng)建一個(gè)新的節(jié)點(diǎn)對(duì)象,并將其插入到鏈表中,不需要預(yù)先分配固定大小的內(nèi)存空間,因此不存在擴(kuò)容的問題。只要系統(tǒng)的內(nèi)存足夠,就可以不斷地向 LinkedList 中添加元素。

Vector:

底層數(shù)據(jù)結(jié)構(gòu)是數(shù)組,查詢快.,增刪慢. 線程安全,它通過在方法上使用 synchronized 關(guān)鍵字來保證線程安全,效率低,可以存儲(chǔ)重要元素。

在選擇線程安全的 List 時(shí),需要根據(jù)具體的業(yè)務(wù)場(chǎng)景來決定:
如果對(duì)性能要求不高,且使用簡(jiǎn)單,可以選擇 Vector。
如果需要將現(xiàn)有的非線程安全的 List 轉(zhuǎn)換為線程安全的,可以使用 Collections.synchronizedList。
如果是讀多寫少的場(chǎng)景,CopyOnWriteArrayList 是一個(gè)更好的選擇。

CopyOnWriteArrayList :
是 Java 并發(fā)包(java.util.concurrent)中提供的線程安全的 List 實(shí)現(xiàn)。它采用==寫時(shí)復(fù)制(Copy-On-Write)==的策略,當(dāng)進(jìn)行寫操作(如 add、remove 等)時(shí),會(huì)先將原數(shù)組復(fù)制一份,在新數(shù)組上進(jìn)行操作,操作完成后再將原數(shù)組的引用指向新數(shù)組。讀操作則直接在原數(shù)組上進(jìn)行,不需要加鎖。
優(yōu)缺點(diǎn):
優(yōu)點(diǎn):讀操作不需要加鎖,并發(fā)讀的性能非常高,適用于讀多寫少的場(chǎng)景。
缺點(diǎn):寫操作需要復(fù)制數(shù)組,會(huì)占用額外的內(nèi)存空間,并且寫操作的性能較低。同時(shí),由于寫時(shí)復(fù)制的特性,迭代器遍歷的是創(chuàng)建迭代器時(shí)的數(shù)組副本,可能無法反映最新的修改。

Set接口:

Set是一個(gè)不包含重復(fù)元素的集合,沒有順序的概念。

HashSet:

hashSet 是基于哈希表的 Set 實(shí)現(xiàn),提供了快速查找、添加和刪除性能,但它不保證元素的順序,并允許一個(gè) null 元素。

LinkedHashSet:

LinkedHashSet 繼承自 HashSet,并且維護(hù)了一個(gè)雙向鏈表來記錄插入順序,因此它既保持了插入順序又具備 HashSet 的高效性能。

TreeSet :

TreeSet 實(shí)現(xiàn)了 SortedSet 接口,能夠?qū)υ剡M(jìn)行排序,默認(rèn)按照自然順序或根據(jù)提供的比較器進(jìn)行排序。底層使用紅黑樹數(shù)據(jù)結(jié)構(gòu),保證了對(duì)數(shù)級(jí)別的性能。

TreeSet 的對(duì)數(shù)級(jí)性能意味著其操作時(shí)間與數(shù)據(jù)規(guī)模的對(duì)數(shù)成正比,這使得它在需要排序和高效操作的場(chǎng)景中表現(xiàn)優(yōu)異。紅黑樹的自平衡特性是實(shí)現(xiàn)這一性能的關(guān)鍵。

安全的ConcurrentSkipListSet
ConcurrentSkipListSet 是線程安全的 SortedSet 實(shí)現(xiàn),基于跳表(Skip List)結(jié)構(gòu),支持并發(fā)訪問而不需要外部同步。采用了無鎖算法(基于 CAS,Compare-And-Swap)來保證并發(fā)操作的線程安全性,避免了傳統(tǒng)鎖機(jī)制帶來的性能開銷。

跳表是什么,下次再更

Queue接口:

隊(duì)列是一個(gè)典型的先進(jìn)先出的容器,常被當(dāng)作一種可靠的將對(duì)象從程序的某個(gè)區(qū)城傳輸?shù)搅?區(qū)域的途徑.,在并發(fā)編程中特別重要。

四、Map(雙列集合)

Map保存具有映射關(guān)系的數(shù)據(jù)。 key和value。 key不能重復(fù),沒有繼承Collection接口。

HashMap

HashMap中的對(duì)象并不是線程安全的,最多只有一個(gè)key值為null。但可以有多個(gè)value值為null,性能最好。HashMap 允許 key 為 “”,并且可以和其他鍵同時(shí)存在,因?yàn)榭兆址彩且粋€(gè)有效的鍵。
底層結(jié)構(gòu): 基于數(shù)組 + 鏈表 + 紅黑樹實(shí)現(xiàn)。數(shù)組作為哈希桶,鏈表用于解決哈希沖突,當(dāng)鏈表長(zhǎng)度超過 8 且數(shù)組長(zhǎng)度大于 64 時(shí),鏈表會(huì)轉(zhuǎn)化為紅黑樹,以提升查找性能。
擴(kuò)容觸發(fā)條件: HashMap 有兩個(gè)關(guān)鍵參數(shù),初始容量(默認(rèn) 16)和負(fù)載因子(默認(rèn) 0.75)。當(dāng)元素?cái)?shù)量超過閾值(閾值 = 容量 × 負(fù)載因子)時(shí),就會(huì)觸發(fā)擴(kuò)容操作。
擴(kuò)容步驟
創(chuàng)建新數(shù)組:新數(shù)組的容量是原數(shù)組的 2 倍。
重新計(jì)算哈希值并遷移元素:將原數(shù)組中的元素重新計(jì)算哈希值,然后根據(jù)新的哈希值插入到新數(shù)組的相應(yīng)位置。對(duì)于鏈表節(jié)點(diǎn),會(huì)根據(jù)新的哈希值拆分成兩個(gè)鏈表;對(duì)于紅黑樹節(jié)點(diǎn),會(huì)拆分成兩個(gè)紅黑樹或鏈表。

LinkedHashMap

LinkedHashMap 結(jié)合了哈希表和雙向鏈表的特性。它不僅像 HashMap 一樣可以快速地存儲(chǔ)和檢索鍵值對(duì),還能維護(hù)鍵值對(duì)的插入順序(默認(rèn))或者訪問順序。
底層結(jié)構(gòu): 繼承自 HashMap,在 HashMap 的基礎(chǔ)上維護(hù)了一個(gè)雙向鏈表,用于記錄元素的插入順序或訪問順序。
擴(kuò)容觸發(fā)條件: 和 HashMap 一致,當(dāng)元素?cái)?shù)量超過閾值時(shí)觸發(fā)擴(kuò)容。
擴(kuò)容步驟: 和 HashMap 相同,創(chuàng)建新數(shù)組,重新計(jì)算哈希值并遷移元素,同時(shí)維護(hù)雙向鏈表的順序。

Hashtable

是同步的.這個(gè)類中的-些方法加入了synchronized關(guān)鍵字,保證了
Hashtable中的對(duì)象是線程安全的,性能最差,
Hashtable 不允許 key 為 null。當(dāng)嘗試將 null 作為 key 插入 Hashtable 時(shí),會(huì)拋出 NullPointerException。這是因?yàn)?Hashtable 的 put 方法內(nèi)部會(huì)對(duì) key 進(jìn)行 null 檢查。
Hashtable 允許 key 為 “”,可以正常存儲(chǔ)和訪問。
底層結(jié)構(gòu): 基于數(shù)組 + 鏈表實(shí)現(xiàn),通過 synchronized 保證線程安全。
擴(kuò)容觸發(fā)條件:當(dāng)元素?cái)?shù)量超過閾值(閾值 = 容量 × 負(fù)載因子,默認(rèn)負(fù)載因子為 0.75)時(shí),觸發(fā)擴(kuò)容。
擴(kuò)容步驟 創(chuàng)建新數(shù)組:新數(shù)組容量為原數(shù)組的 2 倍 + 1。
重新計(jì)算哈希值并遷移元素:將原數(shù)組元素重新計(jì)算哈希值,插入到新數(shù)組相應(yīng)位置。

TreeMap

底層基于 紅黑樹(Red-Black Tree) 實(shí)現(xiàn),這是一種自平衡的二叉搜索樹。紅黑樹的性質(zhì)確保了鍵的有序性,每個(gè)節(jié)點(diǎn)存儲(chǔ)的是 Entry<K, V> 對(duì)象,其中 K 是鍵,V 是值。
TreeMap 不允許 key 為 null,因?yàn)?TreeMap 基于紅黑樹實(shí)現(xiàn),需要對(duì) key 進(jìn)行比較排序,而 null 無法參與比較,所以如果嘗試將 null 作為 key 插入 TreeMap,會(huì)拋出 NullPointerException。
TreeMap 允許 key 為 “”,它會(huì)根據(jù)鍵的自然順序或指定的比較器對(duì)空字符串鍵進(jìn)行排序存儲(chǔ)。
底層結(jié)構(gòu): 基于紅黑樹實(shí)現(xiàn),元素按照鍵的自然順序或指定的比較器順序排序。
擴(kuò)容機(jī)制: TreeMap 不存在像數(shù)組那樣的擴(kuò)容概念。紅黑樹是動(dòng)態(tài)調(diào)整結(jié)構(gòu)的,插入和刪除元素時(shí)會(huì)通過旋轉(zhuǎn)和變色操作來保持樹的平衡。

在選擇線程安全的 Map 時(shí),需要根據(jù)具體的業(yè)務(wù)場(chǎng)景來決定:
如果對(duì)性能要求不高,且使用簡(jiǎn)單,可以選擇 Hashtable。
如果需要將現(xiàn)有的非線程安全的 Map 轉(zhuǎn)換為線程安全的,可以使用 Collections.synchronizedMap。
如果是高并發(fā)的讀寫場(chǎng)景,ConcurrentHashMap 是一個(gè)不錯(cuò)的選擇。
如果需要鍵有序且支持并發(fā)操作,可以選擇 ConcurrentSkipListMap。

安全的map

ConcurrentHashMap
原理: ConcurrentHashMap 是 Java 并發(fā)包(java.util.concurrent)中提供的線程安全的 Map 實(shí)現(xiàn)。在 Java 7 及以前,它采用分段鎖機(jī)制,將整個(gè) Map 分成多個(gè)段(Segment),不同的段可以被不同的線程同時(shí)訪問,從而提高并發(fā)性能。在 Java 8 及以后,ConcurrentHashMap 采用了 CAS(Compare-And-Swap)和 synchronized 來實(shí)現(xiàn)并發(fā)控制,進(jìn)一步優(yōu)化了性能。
ConcurrentHashMap不允許 key 為 null。因?yàn)?ConcurrentHashMap 是為多線程環(huán)境設(shè)計(jì)的,null 可能會(huì)導(dǎo)致歧義,例如在 get 方法返回 null 時(shí),無法確定是鍵不存在還是值為 null,所以不允許 key 為 null。
ConcurrentHashMap 允許 key 為 “”,可以正常存儲(chǔ)和訪問。
底層結(jié)構(gòu): Java 8 及之后版本采用數(shù)組 + 鏈表 + 紅黑樹的結(jié)構(gòu),通過 CAS 和 synchronized 保證線程安全。
擴(kuò)容觸發(fā)條件: 和 HashMap 類似,當(dāng)元素?cái)?shù)量超過閾值時(shí)會(huì)觸發(fā)擴(kuò)容。此外,插入元素時(shí)若發(fā)現(xiàn)鏈表長(zhǎng)度超過 8 且數(shù)組長(zhǎng)度小于 64,會(huì)先嘗試擴(kuò)容數(shù)組而非將鏈表轉(zhuǎn)為紅黑樹。
擴(kuò)容步驟 創(chuàng)建新數(shù)組:新數(shù)組容量為原數(shù)組的 2 倍。
多線程協(xié)助遷移元素:一個(gè)線程發(fā)現(xiàn)需要擴(kuò)容時(shí),先創(chuàng)建新數(shù)組并標(biāo)記擴(kuò)容戳。其他線程在插入元素時(shí)若發(fā)現(xiàn)正在擴(kuò)容,會(huì)協(xié)助進(jìn)行元素遷移。每個(gè)線程負(fù)責(zé)遷移一段連續(xù)的桶,遷移完成后將原數(shù)組對(duì)應(yīng)桶置為 null。

ConcurrentSkipListMap
原理: ConcurrentSkipListMap 也是 Java 并發(fā)包中的線程安全的 Map 實(shí)現(xiàn),它基于跳表(Skip List)數(shù)據(jù)結(jié)構(gòu)。跳表是一種隨機(jī)化的數(shù)據(jù)結(jié)構(gòu),它通過在每個(gè)節(jié)點(diǎn)中維護(hù)多個(gè)指向其他節(jié)點(diǎn)的指針,從而可以在 O(logn) 的平均時(shí)間復(fù)雜度內(nèi)完成插入、刪除和查找操作。ConcurrentSkipListMap 的鍵是有序的,默認(rèn)按照鍵的自然順序排序,也可以通過構(gòu)造函數(shù)傳入 Comparator 來指定排序規(guī)則。

ConcurrentSkipListMap不允許 key 為 null。ConcurrentSkipListMap 實(shí)現(xiàn)了 SortedMap 接口,它會(huì)對(duì) key 進(jìn)行排序。其底層基于跳表(Skip List)數(shù)據(jù)結(jié)構(gòu),排序操作依賴于 key 實(shí)現(xiàn) Comparable 接口或者在創(chuàng)建 ConcurrentSkipListMap 時(shí)傳入一個(gè) Comparator 來定義排序規(guī)則。由于 null 無法與其他對(duì)象進(jìn)行比較,所以不能作為 key 使用。
在多線程環(huán)境下,如果允許 key 為 null,在進(jìn)行查找、插入等操作時(shí)會(huì)產(chǎn)生歧義。例如,當(dāng)調(diào)用 get 方法返回 null 時(shí),無法確定是因?yàn)?key 不存在,還是 key 對(duì)應(yīng)的 value 為 null。

ConcurrentSkipListMap 沒有傳統(tǒng)的擴(kuò)容機(jī)制,而是通過跳表的動(dòng)態(tài)調(diào)整來適應(yīng)元素的插入和刪除操作。這種方式使得 ConcurrentSkipListMap 具有良好的并發(fā)性能和自適應(yīng)性能,能夠在不同的數(shù)據(jù)規(guī)模下保持高效的操作。

五、增刪改查方法

操作類型List(以 ArrayList 為例Set(以 HashSet 為例Map(以 HashMap 為例)
添加元素boolean add(E e):在列表末尾添加元素 void add(int index, E element):在指定位置插入元素boolean add(E e):如果集合中不存在該元素,則添加并返回 true,否則返回 falseV put(K key, V value):將指定的鍵值對(duì)插入 Map,若鍵已存在則覆蓋舊值并返回舊值,不存在則返回 null
刪除元素E remove(int index):移除指定位置的元素并返回該元素 boolean remove(Object o):移除列表中首次出現(xiàn)的指定元素,成功移除返回 trueboolean remove(Object o):移除集合中指定的元素,成功移除返回 trueV remove(Object key):移除指定鍵對(duì)應(yīng)的鍵值對(duì),并返回該鍵對(duì)應(yīng)的值,若鍵不存在則返回 null
修改元素E set(int index, E element):用指定元素替換列表中指定位置的元素,并返回被替換的元素一般沒有直接修改元素的方法,通常先刪除再添加新元素V put(K key, V value):若鍵已存在,用新值替換舊值并返回舊值
查找元素E get(int index):返回列表中指定位置的元素 int indexOf(Object o):返回列表中首次出現(xiàn)指定元素的索引,若不存在則返回 -1 ,int lastIndexOf(Object o):返回列表中最后一次出現(xiàn)指定元素的索引,若不存在則返回 -1boolean contains(Object o):判斷集合中是否包含指定元素V get(Object key):返回指定鍵對(duì)應(yīng)的值,若鍵不存在則返回 null,boolean containsKey(Object key):判斷 Map 中是否包含指定的鍵,boolean containsValue(Object value):判斷 Map 中是否包含指定的值

List 操作:使用 ArrayList 演示,借助 add 方法添加元素,set 方法修改元素,remove 方法刪除元素,get 方法查找元素。
Set 操作:使用 HashSet 演示,通過 add 方法添加元素,remove 方法刪除元素,contains 方法查找元素。
Map 操作:使用 HashMap 演示,利用 put 方法添加或修改元素,remove 方法刪除元素,get 方法查找元素。

六、迭代器

迭代器(Iterator)是一種設(shè)計(jì)模式,它提供了一種統(tǒng)一的方式來遍歷集合(如 List、Set、Map 等)中的元素,而不需要關(guān)心集合的具體實(shí)現(xiàn)細(xì)節(jié)。
迭代器是一個(gè)對(duì)象,它實(shí)現(xiàn)了 java.util.Iterator 接口,該接口定義了三個(gè)主要方法:
boolean hasNext(): 判斷集合中是否還有下一個(gè)元素,如果有則返回 true,否則返回 false。
E next(): 返回集合中的下一個(gè)元素,并將迭代器的位置向后移動(dòng)一位。如果沒有下一個(gè)元素,調(diào)用該方法會(huì)拋出 NoSuchElementException 異常。
void remove(): 移除迭代器最后返回的元素。該方法只能在每次調(diào)用 next() 方法之后調(diào)用一次,如果違反此規(guī)則,會(huì)拋出 IllegalStateException 異常。

遍歷 List(set使用方式和list相同)

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class IteratorListExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("cherry");
        // 獲取迭代器
        Iterator<String> iterator = list.iterator();
        // 遍歷集合
        while (iterator.hasNext()) {
            String element = iterator.next();
            System.out.println(element);
        }
    }
}

遍歷 Map
對(duì)于 Map,需要先獲取 Map 的 entrySet 或 keySet,然后再使用迭代器進(jìn)行遍歷。

mport java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class IteratorMapExample {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("one", 1);
        map.put("two", 2);
        map.put("three", 3);
        // 獲取 entrySet 的迭代器
        Iterator<Entry<String, Integer>> iterator = map.entrySet().iterator();
        // 遍歷集合
        while (iterator.hasNext()) {
            Entry<String, Integer> entry = iterator.next();
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
    }
}

優(yōu)點(diǎn)
統(tǒng)一的遍歷方式: 迭代器提供了一種統(tǒng)一的方式來遍歷不同類型的集合,使得代碼更加簡(jiǎn)潔和易于維護(hù)。
支持并發(fā)修改檢測(cè): 在使用迭代器遍歷集合時(shí),如果在迭代過程中對(duì)集合進(jìn)行了結(jié)構(gòu)上的修改(如添加、刪除元素),會(huì)拋出 ConcurrentModificationException 異常,從而避免了并發(fā)修改帶來的問題。
可以在遍歷過程中刪除元素:通過迭代器的 remove() 方法,可以在遍歷集合的過程中安全地刪除元素。
缺點(diǎn)
只能單向遍歷: 迭代器只能從前往后依次遍歷集合中的元素,不能反向遍歷。
性能相對(duì)較低: 與增強(qiáng) for 循環(huán)和 forEach 方法相比,迭代器的性能相對(duì)較低,因?yàn)樗枰~外的方法調(diào)用和狀態(tài)維護(hù)。

七、其他遍歷方法

增強(qiáng) for 循環(huán)

增強(qiáng) for 循環(huán)是 Java 5 引入的一種簡(jiǎn)化的遍歷方式,它可以更方便地遍歷數(shù)組和集合。與迭代器相比,增強(qiáng) for 循環(huán)的語法更加簡(jiǎn)潔,但它不能在遍歷過程中刪除元素。

import java.util.ArrayList;
import java.util.List;
public class EnhancedForLoopExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("cherry");
        // 使用增強(qiáng) for 循環(huán)遍歷集合
        for (String element : list) {
            System.out.println(element);
        }
    }
}

forEach 方法

forEach 方法是 Java 8 引入的一種函數(shù)式編程風(fēng)格的遍歷方式,它結(jié)合了 Lambda 表達(dá)式,可以更簡(jiǎn)潔地遍歷集合。與迭代器相比,forEach 方法的語法更加簡(jiǎn)潔,但它也不能在遍歷過程中刪除元素。

import java.util.ArrayList;
import java.util.List;
public class ForEachExample {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("apple");
        list.add("banana");
        list.add("cherry");
        // 使用 forEach 方法遍歷集合
        list.forEach(element -> System.out.println(element));
    }
}

八、 棧(Stack)

棧是一種線性數(shù)據(jù)結(jié)構(gòu),它就像一疊盤子,你只能從最上面添加或移除盤子。添加元素的操作叫入棧(push),移除元素的操作叫出棧(pop)。除此之外,通常還會(huì)有查看棧頂元素(peek)、判斷棧是否為空(isEmpty)以及獲取棧中元素?cái)?shù)量(size)等操作。

java.util.Stack 類

Stack 類繼承自 Vector 類,它是 Java 早期提供的棧實(shí)現(xiàn)。不過,由于 Vector 是線程安全的,其操作會(huì)帶來一定的性能開銷。

import java.util.Stack;
public class StackExample {
    public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        // 入棧操作
        stack.push(1);
        stack.push(2);
        stack.push(3);
        // 出棧操作
        int poppedElement = stack.pop();
        System.out.println("出棧元素: " + poppedElement);
        // 查看棧頂元素
        int topElement = stack.peek();
        System.out.println("棧頂元素: " + topElement);
        // 判斷棧是否為空
        boolean isEmpty = stack.isEmpty();
        System.out.println("棧是否為空: " + isEmpty);
        // 獲取棧的大小
        int size = stack.size();
        System.out.println("棧的大小: " + size);
    }
}

Deque 接口

Deque(雙端隊(duì)列)接口可以當(dāng)作棧來使用,并且它的性能通常比 Stack 類更好。ArrayDeque 是 Deque 接口的一個(gè)常用實(shí)現(xiàn)類。

import java.util.ArrayDeque;
import java.util.Deque;
public class DequeAsStackExample {
    public static void main(String[] args) {
        Deque<Integer> stack = new ArrayDeque<>();
        // 入棧操作
        stack.push(1);
        stack.push(2);
        stack.push(3);
        // 出棧操作
        int poppedElement = stack.pop();
        System.out.println("出棧元素: " + poppedElement);
        // 查看棧頂元素
        int topElement = stack.peek();
        System.out.println("棧頂元素: " + topElement);
        // 判斷棧是否為空
        boolean isEmpty = stack.isEmpty();
        System.out.println("棧是否為空: " + isEmpty);
        // 獲取棧的大小
        int size = stack.size();
        System.out.println("棧的大小: " + size);
    }
}

使用場(chǎng)景
棧在很多場(chǎng)景中都有廣泛應(yīng)用,以下是一些常見的例子:
表達(dá)式求值:在計(jì)算中綴表達(dá)式、后綴表達(dá)式時(shí),??梢杂脕硖幚磉\(yùn)算符和操作數(shù),確保運(yùn)算順序正確。
函數(shù)調(diào)用:在程序執(zhí)行過程中,函數(shù)調(diào)用棧用于記錄函數(shù)的調(diào)用關(guān)系和局部變量,當(dāng)函數(shù)調(diào)用結(jié)束時(shí),會(huì)按照后進(jìn)先出的順序返回。
回溯算法:在回溯算法中,棧可以用來保存路徑和狀態(tài),方便進(jìn)行回溯操作。
瀏覽器的前進(jìn)后退功能:瀏覽器使用兩個(gè)棧分別記錄用戶的前進(jìn)和后退歷史,通過棧的操作實(shí)現(xiàn)頁面的前進(jìn)和后退。

九、樹(Tree)

二叉樹(Binary Tree)

結(jié)構(gòu)靈活: 每個(gè)節(jié)點(diǎn)最多有兩個(gè)子節(jié)點(diǎn),結(jié)構(gòu)相對(duì)簡(jiǎn)單但能表示各種復(fù)雜的層次關(guān)系。
無特定順序: 節(jié)點(diǎn)的值沒有特定的大小順序要求,節(jié)點(diǎn)的排列可以非常多樣化。
應(yīng)用廣泛: 是許多其他樹結(jié)構(gòu)的基礎(chǔ),可用于模擬各種具有層次特性的場(chǎng)景。

原理
二叉樹由節(jié)點(diǎn)和邊組成,節(jié)點(diǎn)包含數(shù)據(jù)和指向左右子節(jié)點(diǎn)的引用。節(jié)點(diǎn)之間通過這些引用形成樹狀結(jié)構(gòu)。創(chuàng)建和操作二叉樹主要圍繞節(jié)點(diǎn)的插入、刪除和遍歷展開。在插入節(jié)點(diǎn)時(shí),根據(jù)具體需求和規(guī)則將新節(jié)點(diǎn)添加到合適的位置;刪除節(jié)點(diǎn)時(shí),需要處理節(jié)點(diǎn)的子節(jié)點(diǎn)以及維護(hù)樹的結(jié)構(gòu)完整性;遍歷則是按照特定順序訪問樹中的每個(gè)節(jié)點(diǎn)。
用于表示具有層次關(guān)系的數(shù)據(jù),如文件系統(tǒng)目錄結(jié)構(gòu)、XML 解析等。

二叉搜索樹(Binary Search Tree,BST)

有序性: 對(duì)于樹中的任意節(jié)點(diǎn),其左子樹中所有節(jié)點(diǎn)的值都小于該節(jié)點(diǎn)的值,右子樹中所有節(jié)點(diǎn)的值都大于該節(jié)點(diǎn)的值。
高效查找: 平均情況下,查找、插入和刪除操作的時(shí)間復(fù)雜度為 (O(log n)),因?yàn)槊看尾僮鞫伎梢愿鶕?jù)節(jié)點(diǎn)值的大小縮小搜索范圍。
中序遍歷有序: 對(duì)二叉搜索樹進(jìn)行中序遍歷會(huì)得到一個(gè)有序的節(jié)點(diǎn)值序列。

原理
基于節(jié)點(diǎn)值的比較來構(gòu)建和維護(hù)樹的結(jié)構(gòu)。插入新節(jié)點(diǎn)時(shí),從根節(jié)點(diǎn)開始比較,若新節(jié)點(diǎn)值小于當(dāng)前節(jié)點(diǎn)值,則進(jìn)入左子樹繼續(xù)比較;若大于,則進(jìn)入右子樹,直到找到合適的插入位置。查找和刪除操作也遵循類似的比較規(guī)則。刪除節(jié)點(diǎn)時(shí),需要處理不同情況,如節(jié)點(diǎn)無子節(jié)點(diǎn)、有一個(gè)子節(jié)點(diǎn)或有兩個(gè)子節(jié)點(diǎn),以保證樹的結(jié)構(gòu)仍然滿足二叉搜索樹的特性。

平衡二叉搜索樹 - AVL 樹定義

高度平衡: 每個(gè)節(jié)點(diǎn)的左右子樹的高度差不超過 1,這保證了樹的高度始終保持在 (O(log n)) 級(jí)別。
性能穩(wěn)定: 由于高度平衡,查找、插入和刪除操作的時(shí)間復(fù)雜度始終為 (O(log n)),避免了二叉搜索樹在最壞情況下退化為鏈表的問題。
旋轉(zhuǎn)操作頻繁: 為了保持平衡,在插入和刪除節(jié)點(diǎn)后可能需要進(jìn)行多次旋轉(zhuǎn)操作(左旋、右旋、左右旋、右左旋)。

原理
在插入或刪除節(jié)點(diǎn)后,會(huì)計(jì)算每個(gè)節(jié)點(diǎn)的平衡因子(左子樹高度 - 右子樹高度)。如果平衡因子的絕對(duì)值大于 1,則通過旋轉(zhuǎn)操作來調(diào)整樹的結(jié)構(gòu),使其重新達(dá)到平衡。旋轉(zhuǎn)操作會(huì)改變節(jié)點(diǎn)之間的父子關(guān)系,但保證樹仍然滿足二叉搜索樹的特性。

紅黑樹(Red - Black Tree)

近似平衡: 紅黑樹并不像 AVL 樹那樣嚴(yán)格平衡,但它通過顏色標(biāo)記和特定規(guī)則保證了樹的最長(zhǎng)路徑不超過最短路徑的兩倍,從而保證了操作的時(shí)間復(fù)雜度為 (O(log n))。
插入和刪除效率高: 相比于 AVL 樹,紅黑樹在插入和刪除節(jié)點(diǎn)時(shí)需要的調(diào)整操作相對(duì)較少,因?yàn)樗钠胶庖笙鄬?duì)寬松。
應(yīng)用廣泛: Java 中的 TreeMap 和 TreeSet 底層就是基于紅黑樹實(shí)現(xiàn)的,在需要有序存儲(chǔ)和高效查找的場(chǎng)景中表現(xiàn)出色。

原理
紅黑樹的每個(gè)節(jié)點(diǎn)都有一個(gè)顏色屬性(紅色或黑色),并遵循以下規(guī)則:每個(gè)節(jié)點(diǎn)要么是紅色,要么是黑色。根節(jié)點(diǎn)是黑色。每個(gè)葉子節(jié)點(diǎn)(NIL 節(jié)點(diǎn),空節(jié)點(diǎn))是黑色。如果一個(gè)節(jié)點(diǎn)是紅色的,則它的子節(jié)點(diǎn)必須是黑色的。對(duì)每個(gè)節(jié)點(diǎn),從該節(jié)點(diǎn)到其所有后代葉節(jié)點(diǎn)的簡(jiǎn)單路徑上,均包含相同數(shù)目的黑色節(jié)點(diǎn)。在插入和刪除節(jié)點(diǎn)時(shí),通過顏色調(diào)整和旋轉(zhuǎn)操作來維護(hù)這些規(guī)則,從而保證樹的近似平衡。

多路搜索樹 - B 樹和 B+ 樹

B 樹
多路存儲(chǔ): 每個(gè)節(jié)點(diǎn)可以有多個(gè)子節(jié)點(diǎn)(通常大于 2),這使得 B 樹可以在一個(gè)節(jié)點(diǎn)中存儲(chǔ)更多的鍵值對(duì),減少了樹的高度。
適用于磁盤存儲(chǔ): 由于樹的高度較低,B 樹在磁盤存儲(chǔ)中非常有用,因?yàn)榇疟P I/O 操作的次數(shù)與樹的高度成正比,較低的樹高度可以減少磁盤 I/O 次數(shù)。
所有節(jié)點(diǎn)都存儲(chǔ)數(shù)據(jù): B 樹的每個(gè)節(jié)點(diǎn)都可以存儲(chǔ)鍵值對(duì)和指向子節(jié)點(diǎn)的指針。

B+ 樹
數(shù)據(jù)集中在葉子節(jié)點(diǎn): 所有的數(shù)據(jù)都存儲(chǔ)在葉子節(jié)點(diǎn),非葉子節(jié)點(diǎn)只存儲(chǔ)索引信息,這使得范圍查詢更加高效。
葉子節(jié)點(diǎn)相連: 葉子節(jié)點(diǎn)之間通過指針相連,形成一個(gè)有序鏈表,方便進(jìn)行范圍查詢和順序訪問。
更適合數(shù)據(jù)庫索引: 在數(shù)據(jù)庫系統(tǒng)中,B+ 樹廣泛應(yīng)用于索引結(jié)構(gòu),因?yàn)樗軌蚋咝У刂С植檎?、插入、刪除和范圍查詢操作。

原理
B 樹
插入節(jié)點(diǎn)時(shí),當(dāng)節(jié)點(diǎn)中的鍵值對(duì)數(shù)量超過一定閾值(節(jié)點(diǎn)的最大容量)時(shí),會(huì)進(jìn)行節(jié)點(diǎn)分裂操作,將節(jié)點(diǎn)一分為二,并將中間的鍵值提升到父節(jié)點(diǎn)。刪除節(jié)點(diǎn)時(shí),若節(jié)點(diǎn)中的鍵值對(duì)數(shù)量小于一定閾值(節(jié)點(diǎn)的最小容量),會(huì)進(jìn)行節(jié)點(diǎn)合并或借鍵操作來保持樹的結(jié)構(gòu)。

B+ 樹
插入和刪除操作與 B 樹類似,但由于數(shù)據(jù)只存儲(chǔ)在葉子節(jié)點(diǎn),操作主要集中在葉子節(jié)點(diǎn)上。在插入時(shí),若葉子節(jié)點(diǎn)已滿,則進(jìn)行分裂操作;刪除時(shí),若葉子節(jié)點(diǎn)的鍵值對(duì)數(shù)量過少,則進(jìn)行合并或借鍵操作。同時(shí),需要維護(hù)葉子節(jié)點(diǎn)之間的鏈表結(jié)構(gòu)。

特性對(duì)比

樹結(jié)構(gòu)節(jié)點(diǎn)數(shù)量限制平衡特性查找時(shí)間復(fù)雜度插入 / 刪除時(shí)間復(fù)雜度適用場(chǎng)景
二叉樹每個(gè)節(jié)點(diǎn)最多 2 個(gè)子節(jié)點(diǎn)最壞 (O(n)),平均取決于樹的形狀最壞 (O(n)),平均取決于樹的形狀表示層次關(guān)系,基礎(chǔ)樹結(jié)構(gòu)
二叉搜索樹每個(gè)節(jié)點(diǎn)最多 2 個(gè)子節(jié)點(diǎn)平均 (O(log n)),最壞 (O(n))平均 (O(log n)),最壞 (O(n))有序數(shù)據(jù)存儲(chǔ)和查找
AVL 樹每個(gè)節(jié)點(diǎn)最多 2 個(gè)子節(jié)點(diǎn)嚴(yán)格平衡(左右子樹高度差不超過 1)O(logn)O(logn)對(duì)查找性能要求極高,插入和刪除操作相對(duì)較少
紅黑樹每個(gè)節(jié)點(diǎn)最多 2 個(gè)子節(jié)點(diǎn)近似平衡(最長(zhǎng)路徑不超過最短路徑的兩倍)O(logn)O(logn)插入、刪除和查找操作都比較頻繁的場(chǎng)景,如 Java 集合框架
B 樹每個(gè)節(jié)點(diǎn)有多個(gè)子節(jié)點(diǎn)(m 階 B 樹,子節(jié)點(diǎn)數(shù)量在 ([ [m/2], m ]) 之間)平衡(所有葉子節(jié)點(diǎn)在同一層)O(logn)O(logn)磁盤存儲(chǔ),數(shù)據(jù)庫索引
B+ 樹每個(gè)節(jié)點(diǎn)有多個(gè)子節(jié)點(diǎn)(m 階 B+ 樹,非葉子節(jié)點(diǎn)子節(jié)點(diǎn)數(shù)量在 ([ [ m/2], m ]) 之間,葉子節(jié)點(diǎn)可存儲(chǔ)多個(gè)鍵值對(duì))平衡(所有葉子節(jié)點(diǎn)在同一層)O(logn)O(logn)數(shù)據(jù)庫索引,范圍查詢

十、 哈希是什么

哈希是把任意長(zhǎng)度的輸入通過哈希函數(shù)轉(zhuǎn)換為固定長(zhǎng)度輸出的過程,這個(gè)輸出值就是哈希值(也叫哈希碼或散列值)。哈希的核心目標(biāo)是高效地存儲(chǔ)和查找數(shù)據(jù)。借助哈希函數(shù),能把數(shù)據(jù)的鍵映射到一個(gè)特定的位置,進(jìn)而實(shí)現(xiàn)快速訪問。

哈希函數(shù)

哈希函數(shù)是哈希技術(shù)的關(guān)鍵,它接收一個(gè)鍵作為輸入,然后返回一個(gè)哈希值。在 Java 中,Object 類有一個(gè) hashCode() 方法,所有類都繼承了這個(gè)方法,可用于生成對(duì)象的哈希碼。例如:

public class HashExample {
    public static void main(String[] args) {
        String str = "hello";
        int hashCode = str.hashCode();
        System.out.println("字符串 \"hello\" 的哈希碼: " + hashCode);
    }
}

在上述代碼里,String 類重寫了 hashCode() 方法,按照特定算法生成字符串的哈希碼。一個(gè)好的哈希函數(shù)應(yīng)該具備以下特性:
確定性:相同的輸入始終產(chǎn)生相同的輸出。
高效性:計(jì)算哈希值的過程要快速。
均勻性:盡可能讓哈希值均勻分布,減少哈希沖突的發(fā)生。

哈希沖突

當(dāng)不同的鍵通過哈希函數(shù)計(jì)算出相同的哈希值時(shí),就會(huì)產(chǎn)生哈希沖突。因?yàn)楣:瘮?shù)的輸出空間通常比輸入空間小,所以哈希沖突難以避免。例如,在一個(gè)大小為 10 的哈希表中,若有 11 個(gè)不同的鍵,必然會(huì)有至少兩個(gè)鍵的哈希值相同。

解決哈希沖突的方法

鏈地址法(Separate Chaining)
這種方法是在哈希表的每個(gè)位置維護(hù)一個(gè)鏈表。當(dāng)發(fā)生哈希沖突時(shí),把沖突的元素添加到對(duì)應(yīng)位置的鏈表中。Java 的 HashMap 就采用了鏈地址法,當(dāng)鏈表長(zhǎng)度超過一定閾值(默認(rèn)為 8)且哈希表長(zhǎng)度大于 64 時(shí),鏈表會(huì)轉(zhuǎn)換為紅黑樹,以提升查找性能。

開放地址法(Open Addressing)
開放地址法是在發(fā)生哈希沖突時(shí),通過某種規(guī)則在哈希表中尋找下一個(gè)可用的位置。常見的開放地址法有線性探測(cè)、二次探測(cè)和雙重哈希等。

哈希在 Java 中是一種高效的數(shù)據(jù)存儲(chǔ)和查找技術(shù),通過合理選擇哈希函數(shù)和解決哈希沖突的方法,能顯著提升程序的性能。

到此這篇關(guān)于一文掌握J(rèn)ava 數(shù)據(jù)結(jié)構(gòu)超詳細(xì)筆記(收藏版)的文章就介紹到這了,更多相關(guān)Java 數(shù)據(jù)結(jié)構(gòu)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringBoot2零基礎(chǔ)到精通之profile功能與自定義starter

    SpringBoot2零基礎(chǔ)到精通之profile功能與自定義starter

    SpringBoot是一種整合Spring技術(shù)棧的方式(或者說是框架),同時(shí)也是簡(jiǎn)化Spring的一種快速開發(fā)的腳手架,本篇讓我們一起學(xué)習(xí)profile功能與自定義starter
    2022-03-03
  • 詳解Java如何通過Socket實(shí)現(xiàn)查詢IP

    詳解Java如何通過Socket實(shí)現(xiàn)查詢IP

    在本文中,我們來學(xué)習(xí)下如何找到連接到服務(wù)器的客戶端計(jì)算機(jī)的IP地址。我們將創(chuàng)建一個(gè)簡(jiǎn)單的客戶端-服務(wù)器場(chǎng)景,讓我們探索用于TCP/IP通信的java.net?API,感興趣的可以了解一下
    2022-10-10
  • Spring之@Lookup注解詳細(xì)解析

    Spring之@Lookup注解詳細(xì)解析

    這篇文章主要介紹了Spring之@Lookup注解詳細(xì)解析,當(dāng)采用@Autowired注解對(duì)單例bean注依賴的原型bean時(shí),會(huì)由于單例bean只會(huì)創(chuàng)建一次,導(dǎo)致依賴的原型bean也只會(huì)注入一次,@Lookup注解可以較為優(yōu)雅的解決此類問題,需要的朋友可以參考下
    2024-01-01
  • Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析

    Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析

    這篇文章主要介紹了Java實(shí)現(xiàn)一個(gè)簡(jiǎn)單的定時(shí)器代碼解析,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-12-12
  • Java工程中可執(zhí)行JAR兩種打包方式詳解

    Java工程中可執(zhí)行JAR兩種打包方式詳解

    這篇文章主要為大家詳細(xì)介紹了Java工程中可執(zhí)行JAR兩種打包方式,一體化可執(zhí)行包和帶外部依賴lib的可執(zhí)行包,有需要的小伙伴可以學(xué)習(xí)一下
    2024-04-04
  • SpringBoot整合sharding-jdbc?實(shí)現(xiàn)分庫分表操作的示例代碼

    SpringBoot整合sharding-jdbc?實(shí)現(xiàn)分庫分表操作的示例代碼

    在Spring?Boot中使用ShardingSphere的Sharding-JDBC來實(shí)現(xiàn)數(shù)據(jù)庫的分庫分表是一個(gè)常見的需求,下面就拉具體介紹一下實(shí)現(xiàn)步驟,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • 使用EasyExcel實(shí)現(xiàn)簡(jiǎn)單的Excel表格解析操作

    使用EasyExcel實(shí)現(xiàn)簡(jiǎn)單的Excel表格解析操作

    這篇文章主要介紹了如何使用EasyExcel完成簡(jiǎn)單的表格解析操作,同時(shí)實(shí)現(xiàn)了大量數(shù)據(jù)情況下數(shù)據(jù)的分次批量入庫,并記錄每條數(shù)據(jù)入庫的狀態(tài),感興趣的可以了解下
    2025-03-03
  • spring一個(gè)項(xiàng)目多個(gè)模塊聚合打包問題解決方案(最新推薦)

    spring一個(gè)項(xiàng)目多個(gè)模塊聚合打包問題解決方案(最新推薦)

    最近遇到個(gè)需求,針對(duì)后端解耦模塊較多的項(xiàng)目,想在云端啟動(dòng)時(shí)簡(jiǎn)潔些只啟動(dòng)一個(gè)jar文件的情景,本文重點(diǎn)給大家介紹spring一個(gè)項(xiàng)目多個(gè)模塊聚合打包問題解決方案,感興趣的朋友一起看看吧
    2023-09-09
  • JAVA中Spring Security示例及常見問題

    JAVA中Spring Security示例及常見問題

    文章概述Spring Security OAuth2與JWT模塊的版本兼容性及遷移建議,強(qiáng)調(diào)2.5.x支持JDK8但已棄用,推薦新項(xiàng)目使用SpringAuthorizationServer(Spring Boot3.x+),并指出依賴沖突、配置示例及密鑰安全注意事項(xiàng),感興趣的朋友一起看看吧
    2025-07-07
  • 通過實(shí)例解析spring對(duì)象生命周期

    通過實(shí)例解析spring對(duì)象生命周期

    這篇文章主要介紹了通過實(shí)例解析spring對(duì)象生命周期,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03

最新評(píng)論

昌江| 淮滨县| 德阳市| 长汀县| 绵阳市| 定南县| 昆山市| 常宁市| 阳春市| 斗六市| 保亭| 盘锦市| 玉林市| 包头市| 民丰县| 郎溪县| 罗山县| 建阳市| 松江区| 黔西| 桂平市| 社会| 柯坪县| 景洪市| 平阴县| 永宁县| 沁水县| 水城县| 峨山| 山东省| 安吉县| 岐山县| 泾阳县| 集贤县| 大同县| 泾川县| 云林县| 莒南县| 宜宾市| 商丘市| 吴堡县|