?史上最全的Java容器集合之基礎資料結構(手撕連結串列)
前言
文字已收錄至我的GitHub倉庫,歡迎Star:github.com/bin39232820…
種一棵樹最好的時間是十年前,其次是現在
我知道很多人不玩qq了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習群,群聊號碼:549684836 鼓勵大家在技術的路上寫部落格
絮叨
上一篇的集合的父介面 Collection 和 Iterator 大家應該都很瞭解了吧,上一篇是基礎,大家還是很有必要好好去學習一下
?史上最全的Java容器集合之入門
這一篇 帶領大家來解密,我們容器的基礎資料結構,為啥有這篇呢,本來想直接寫容器的,但是寫到一半,發現自己的資料結構也不是很好,所以帶大家瞭解一下簡單的資料結構。
陣列
第一個陣列是一種效率最高的儲存和隨機訪問的方式
陣列是一種連續儲存線性結構,元素型別相同,大小相等,存取速度快
無論使用哪種型別的陣列,陣列的識別符號[],它其實是一個引用,指在堆中建立了一個真實的物件(這個物件對我們程式設計師是不可見的),我自己找了半天,我說他既然有length屬性,那我肯定能在Java中找到這個物件,結果我硬是沒找到,這個物件儲存的是對其他物件的引用。length方法是唯一一個能用這個物件訪問的屬性或者方法
陣列的特點
- 一致性:陣列只能儲存相同資料型別元素,元素的資料型別可以是任何相同的資料型別。
- 有序性:陣列中的元素是有序的,通過下標訪問。
- 不可變性:陣列一旦初始化,則長度(陣列中元素的個數)不可變。 陣列是一種連續儲存線性結構,元素型別相同,大小相等
陣列的缺點:
- 事先必須知道陣列的長度
- 插入刪除元素很慢
- 空間通常是有限制的
- 需要大塊連續的記憶體塊
- 插入刪除元素的效率很低
帶你們來解密一下陣列
我去,我發現 啥都沒有,這麼奇怪,那我為什麼能訪問它的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,就輸出
-
給定位置插入節點到連結串列中
-
將原本由上一個節點的指向交由插入的節點來指向
-
上一個節點指標域指向想要插入的節點
-
首先判斷該位置是否有效(在連結串列長度的範圍內)
-
找到想要插入位置的上一個節點
-
-
獲取連結串列的長度
- 每訪問一次節點,變數++操作即可
-
刪除給定位置的節點
-
將原本由上一個節點的指向後面一個節點
-
首先判斷該位置是否有效(在連結串列長度的範圍內)
-
找到想要插入位置的上一個節點
-
-
對連結串列進行排序
- 使用冒泡演演算法對其進行排序
-
刪除連結串列重複資料
- 操作跟氣泡排序差不多,只要它相等,就能刪除了~
還有很多就不一一列舉,各位大佬要深入的自己深入吧,感覺腦子不夠用,哈哈
結尾
資料結構的基礎就講那麼多,也就是入門,後面還是靠自己,接下來我們要進入正題,講我們的容器集合了。
因為博主也是一個開發萌新 我也是一邊學一邊寫 我有個目標就是一週 二到三篇 希望能堅持個一年吧 希望各位大佬多提意見,讓我多學習,一起進步。
日常求贊
好了各位,以上就是這篇文章的全部內容了,能看到這裡的人呀,都是人才。
創作不易,各位的支援和認可,就是我創作的最大動力,我們下篇文章見
六脈神劍 | 文 【原創】如果本篇部落格有任何錯誤,請批評指教,不勝感激 !