Java數(shù)據(jù)結(jié)構(gòu)之ArrayList與順序表詳解
前言
在 Java編程的廣袤世界里,數(shù)據(jù)結(jié)構(gòu)猶如精巧的建筑藍圖,決定著程序在數(shù)據(jù)處理與存儲時的效率、靈活性以及可擴展性。其中,ArrayList和順序表作為線性數(shù)據(jù)結(jié)構(gòu)的典型代表,猶如兩顆璀璨的明星,在眾多數(shù)據(jù)處理場景中熠熠生輝。
順序表,以其簡潔而直觀的連續(xù)內(nèi)存存儲方式,為數(shù)據(jù)的快速隨機訪問提供了可能。它猶如一位嚴(yán)謹?shù)墓芗?,將?shù)據(jù)元素有條不紊地排列在連續(xù)的內(nèi)存空間中,使得我們能夠通過精確的索引迅速定位到所需的數(shù)據(jù),就像在一本精心編排索引的書籍中快速找到特定章節(jié)一樣高效。這種特性在一些對數(shù)據(jù)訪問速度有嚴(yán)格要求且數(shù)據(jù)量相對固定或可預(yù)知的場景中,展現(xiàn)出了無與倫比的優(yōu)勢。 而 ArrayList,則是 Java 集合框架中備受矚目的一員。它基于數(shù)組實現(xiàn),卻又巧妙地融入了動態(tài)擴容機制,如同一位富有彈性的藝術(shù)家,能夠根據(jù)創(chuàng)作需求靈活調(diào)整畫布的大小。這種動態(tài)性使得 ArrayList在應(yīng)對數(shù)據(jù)量不確定、頻繁增刪元素的復(fù)雜情況時游刃有余。它為開發(fā)者提供了豐富便捷的方法庫,極大地簡化了數(shù)據(jù)操作的流程,仿佛一把瑞士軍刀,在各種數(shù)據(jù)處理任務(wù)中都能發(fā)揮出色的作用。 在實際的編程旅程中,深入理解 ArrayList 和順序表的特性、性能表現(xiàn)以及適用場景,就如同掌握了兩門精湛的技藝。無論是構(gòu)建高效的數(shù)據(jù)庫管理系統(tǒng)、開發(fā)復(fù)雜的算法,還是打造用戶友好的圖形界面應(yīng)用,都離不開對這兩種數(shù)據(jù)結(jié)構(gòu)的精準(zhǔn)運用。本博客將深入剖析 Java ArrayList 和順序表,從原理到實現(xiàn),從性能對比到應(yīng)用場景分析,為你揭開它們神秘的面紗,引領(lǐng)你在 Java編程的數(shù)據(jù)結(jié)構(gòu)之路上穩(wěn)步前行,幫助你在面對各種數(shù)據(jù)處理挑戰(zhàn)時做出明智而高效的抉擇。
一、線性表
線性表(linear list)是n個具有相同特性的數(shù)據(jù)元素的有限序列。 線性表是一種在實際中廣泛使用的數(shù)據(jù)結(jié)構(gòu),常見的線性表:順序表、鏈表、棧、隊列…
線性表在邏輯上是線性結(jié)構(gòu),也就說是連續(xù)的一條直線。但是在物理結(jié)構(gòu)上并不一定是連續(xù)的,線性表在物理上存儲時,通常以數(shù)組和鏈?zhǔn)浇Y(jié)構(gòu)的形式存儲。
在 Java 編程領(lǐng)域,線性表是一種極為基礎(chǔ)且關(guān)鍵的數(shù)據(jù)結(jié)構(gòu),它猶如大廈的基石,支撐起眾多復(fù)雜算法與程序功能的實現(xiàn)。線性表,從直觀上理解,是由一系列具有相同類型的數(shù)據(jù)元素按照特定的順序依次排列而成的集合,其元素之間存在著一對一的線性關(guān)系。
1.1線性表的基本特性
(一)有限性
線性表中的元素數(shù)量是有限的。無論是空表(不包含任何元素),還是包含大量元素的表,其元素個數(shù)總能確定。例如,一個班級的學(xué)生名單,無論班級規(guī)模大小,學(xué)生人數(shù)總歸是可以明確統(tǒng)計的。
(二)有序性
元素在表中的排列具有確定的順序。每個元素都有其特定的位置,除了第一個元素?zé)o前驅(qū),最后一個元素?zé)o后繼外,其他元素都有且僅有一個直接前驅(qū)和一個直接后繼。就像電影院里的座位號,每個座位都有其固定的前后順序。
(三)同一性
表中的所有元素都屬于同一數(shù)據(jù)類型。這一特性使得對線性表的操作能夠具有一致性和規(guī)范性。例如,一個存儲整數(shù)的線性表,其中所有元素都為整型,這樣在進行數(shù)學(xué)運算或數(shù)據(jù)比較等操作時就可以按照統(tǒng)一的整型規(guī)則進行。
1.2線性表在Java中的常見實現(xiàn)方式
存儲結(jié)構(gòu)
順序表利用數(shù)組來存儲線性表中的元素。在 Java 中,可以聲明一個數(shù)組來容納這些元素,例如 Object[] data(或指定具體類型如 int[] data 等)。數(shù)組在內(nèi)存中是連續(xù)分配空間的,這使得順序表在隨機訪問元素時具有極高的效率。通過元素的索引,可以直接計算出其在內(nèi)存中的存儲地址,從而快速獲取到該元素,時間復(fù)雜度為 O(1)。
操作實現(xiàn)
插入操作:當(dāng)向順序表中插入一個元素時,如果插入位置不是在表尾,需要將插入位置之后的所有元素依次向后移動一位,為新元素騰出空間,然后再將新元素放入指定位置。這種移動操作在元素數(shù)量較多時可能會耗費較多時間,平均時間復(fù)雜度為O(n) ,其中 為線性表的長度。例如,在一個長度為 100 的順序表中,如果要在第 50 個位置插入一個元素,就需要將第 50 個到第 100 個元素向后移動一位。
刪除操作:類似地,刪除操作如果不是刪除表尾元素,需要將刪除位置之后的元素依次向前移動一位,以填補刪除元素后留下的空位。其平均時間復(fù)雜度也為O(n) 。比如刪除一個長度為 80 的順序表中的第 30 個元素,就需要將第 31 個到第 80 個元素向前移動一位。
查詢操作:由于可以直接通過索引訪問數(shù)組元素,所以查詢指定位置元素的時間復(fù)雜度為O(1) 。而查詢某個特定值的元素時,可能需要遍歷整個順序表,在最壞情況下時間復(fù)雜度為O(n) 。例如,在一個存儲學(xué)生成績的順序表中,如果要查找成績?yōu)?90 分的學(xué)生,可能需要逐個比較所有學(xué)生的成績。
二、順序表
順序表是用一段物理地址連續(xù)的存儲單元依次存儲數(shù)據(jù)元素的線性結(jié)構(gòu),一般情況下采用數(shù)組存儲。在數(shù)組上完成 數(shù)據(jù)的增刪查改。
2.1定義一個類來表示順序表
class ArrayList<E> { // 存儲數(shù)據(jù)的數(shù)組 private E[] data; // 記錄當(dāng)前順序表中元素的個數(shù) private int size; // 構(gòu)造函數(shù),初始化數(shù)組容量 public ArrayList(int capacity) { data = (E[]) new Object[capacity]; size = 0; } }
這里的 data 數(shù)組就是用來存放順序表元素的核心容器,而 size 則用于追蹤實際存儲的元素數(shù)量。
2.2接口的實現(xiàn)
public class SeqList { private int[] array; private int size; // 默認構(gòu)造方法 SeqList(){ } // 將順序表的底層容量設(shè)置為initcapacity SeqList(int initcapacity){ } // 新增元素,默認在數(shù)組最后新增 public void add(int data) { } // 在 pos 位置新增元素 public void add(int pos, int data) { } // 判定是否包含某個元素 public boolean contains(int toFind) { return true; } // 查找某個元素對應(yīng)的位置 public int indexOf(int toFind) { return -1; } // 獲取 pos 位置的元素 public int get(int pos) { return -1; } // 給 pos 位置的元素設(shè)為 value public void set(int pos, int value) { } //刪除第一次出現(xiàn)的關(guān)鍵字key public void remove(int toRemove) { } // 獲取順序表長度 public int size() { return 0; } // 清空順序表 public void clear() { } // 打印順序表,注意:該方法并不是順序表中的方法,為了方便看測試結(jié)果給出的 public void display() { } }
插入操作
當(dāng)向順序表中插入一個元素時,情況會相對復(fù)雜一些。如果是在順序表的末尾插入元素,操作較為簡單,只需將元素放入 data[size] 的位置,并將 size 加 1 即可。但如果是在中間位置插入,例如在索引 k 處插入元素 e,則需要將索引 k 及之后的元素依次向后移動一位,為新元素騰出空間,然后再將新元素放入索引 k 處。
public void add(int index, E e) { if (index < 0 || index > size) { throw new IllegalArgumentException("Add failed. Illegal index."); } if (size == data.length) { resize(2 * data.length); } for (int i = size - 1; i >= index; i--) { data[i + 1] = data[i]; } data[index] = e; size++; }
刪除操作
刪除順序表中的元素同樣需要考慮多種情況。如果刪除的是末尾元素,直接將 size 減 1 即可。若刪除索引為 k 的元素,則需要將索引 k 之后的元素依次向前移動一位,覆蓋掉要刪除的元素。
public E remove(int index) { if (index < 0 || index >= size) { throw new IllegalArgumentException("Remove failed. Illegal index."); } E ret = data[index]; for (int i = index + 1; i < size; i++) { data[i - 1] = data[i]; } size--; data[size] = null; if (size == data.length / 4 && data.length / 2!= 0) { resize(data.length / 2); } return ret; }
三、ArrayList的簡介
ArrayList是 Java 集合框架中的一個重要類,它位于java.util包中。它實現(xiàn)了List接口,這意味著它是一個有序的集合,允許存儲重復(fù)的元素。它的底層數(shù)據(jù)結(jié)構(gòu)是一個動態(tài)大小的數(shù)組,這使得它可以根據(jù)需要自動調(diào)整大小。
與普通數(shù)組相比,ArrayList更加靈活。普通數(shù)組在創(chuàng)建時就確定了大小,之后很難改變其容量,而ArrayList可以方便地添加或刪除元素,并且會自動處理數(shù)組容量的擴展和收縮。
說明:
1. ArrayList是以泛型?式實現(xiàn)的,使?時必須要先實例化
2. ArrayList實現(xiàn)了RandomAccess接?,表明ArrayList?持隨機訪問
3. ArrayList實現(xiàn)了Cloneable接?,表明ArrayList是可以clone的
4. ArrayList實現(xiàn)了Serializable接?,表明ArrayList是?持序列化的
5. 和Vector不同,ArrayList不是線程安全的,在單線程下可以使?,在多線程中可以選擇Vector或者 CopyOnWriteArrayList
6. ArrayList底層是?段連續(xù)的空間,并且可以動態(tài)擴容,是?個動態(tài)類型的順序表
創(chuàng)建ArrayList對象
要使用ArrayList,首先需要導(dǎo)入java.util.ArrayList類。
import java.util.ArrayList; public class ArrayListExample { public static void main(String[] args) { ArrayList<String> list = new ArrayList<String>(); } }
創(chuàng)建了一個可以存儲String類型元素的ArrayList。其中是泛型參數(shù),用于指定列表中元素的類型。除了String,還可以是其他基本數(shù)據(jù)類型對應(yīng)的包裝類(如Integer、Double等)或自定義的類類型。
四、ArrayList使用
4.1 ArrayList的構(gòu)造
public static void main(String[] args) { // ArrayList創(chuàng)建,推薦寫法 // 構(gòu)造?個空的列表 List<Integer> list1 = new ArrayList<>(); // 構(gòu)造?個具有10個容量的列表 List<Integer> list2 = new ArrayList<>(10); list2.add(1); list2.add(2); list2.add(3); // list2.add("hello"); // 編譯失敗,List<Integer>已經(jīng)限定了,list2中只能存儲整 形元素 // list3構(gòu)造好之后,與list中的元素?致 ArrayList<Integer> list3 = new ArrayList<>(list2); // 避免省略類型,否則:任意類型的元素都可以存放,使?時將是?場災(zāi)難 List list4 = new ArrayList(); list4.add("111"); list4.add(100); }
4.2 ArrayList常?操作
4.2.1添加元素
add(E e)方法:用于在ArrayList末尾添加元素。
import java.util.ArrayList; public class ArrayListAddExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); System.out.println(fruits); } }
首先創(chuàng)建了一個ArrayList對象fruits,用于存儲String類型的元素。然后通過add方法添加了兩個水果名稱,最后打印出ArrayList,輸出結(jié)果為[Apple, Banana]。
add(int index, E e)方法:用于在指定索引index處插入元素e。
import java.util.ArrayList; public class ArrayListAddAtIndexExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.add(1, "Cherry"); System.out.println(fruits); } }
這里在索引為1的位置插入了"Cherry",原來索引為1(即"Banana")及其后面的元素會向后移動一位。輸出結(jié)果為[Apple, Cherry, Banana]。
4.2.2獲取元素
get(int index)方法:用于獲取ArrayList中指定索引index處的元素。
import java.util.ArrayList; public class ArrayListGetExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); String secondFruit = fruits.get(1); System.out.println(secondFruit); } }
通過get(1)獲取了索引為1的元素,即"Banana",并將其存儲在secondFruit變量中,最后打印輸出。
4.2.3修改元素
直接通過索引賦值:可以像操作數(shù)組一樣,通過指定索引來修改ArrayList中的元素。
import java.util.ArrayList; public class ArrayListSetExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.set(1, "Orange"); System.out.println(fruits); } }
通過set(1, “Orange”)將索引為1的元素(原來為"Banana")修改為"Orange",輸出結(jié)果為[Apple, Orange]。
4.2.4刪除元素
remove(int index)方法:用于刪除指定索引index處的元素。
import java.util.ArrayList; public class ArrayListRemoveByIndexExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.remove(0); System.out.println(fruits); } }
通過remove(0)刪除了索引為0的元素(即"Apple"),輸出結(jié)果為[Banana]。
remove(Object o)方法:用于刪除指定的對象o。
import java.util.ArrayList; public class ArrayListRemoveByObjectExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); fruits.remove("Apple"); System.out.println(fruits); } }
通過remove(“Apple”)刪除了ArrayList中值為"Apple"的元素,輸出結(jié)果為[Banana]。
4.2.5遍歷ArrayList
使用普通for循環(huán)
import java.util.ArrayList; public class ArrayListForLoopExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); for (int i = 0; i < fruits.size(); i++) { System.out.println(fruits.get(i)); } } }
在這個循環(huán)中,i從0開始,每次循環(huán)增加1,直到i小于ArrayList的大?。ㄍㄟ^size()方法獲?。?。在循環(huán)體內(nèi)部,通過get(i)方法獲取當(dāng)前索引的元素并打印輸出。
使用增強for循環(huán)(for - each循環(huán))
import java.util.ArrayList; public class ArrayListForEachLoopExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); for (String fruit : fruits) { System.out.println(fruit); } } }
fruit變量會依次獲取ArrayList中的每個元素,循環(huán)會自動遍歷整個ArrayList,直到所有元素都被訪問。
使用迭代器(Iterator):
import java.util.ArrayList; import java.util.Iterator; public class ArrayListIteratorExample { public static void main(String[] args) { ArrayList<String> fruits = new ArrayList<>(); fruits.add("Apple"); fruits.add("Banana"); Iterator<String> iterator = fruits.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } }
通過iterator()方法獲取ArrayList的迭代器。然后通過hasNext()方法檢查是否還有下一個元素,如果有,則通過next()方法獲取并打印下一個元素,直到?jīng)]有更多元素為止。
五、ArrayList的擴容機制
ArrayList是?個動態(tài)類型的順序表,即:在插?元素的過程中會?動擴容。
public boolean add(E e) { modCount++; add(e, elementData, size); return true; } private void add(E e, Object[] elementData, int s) { if (s == elementData.length) //第?次存儲的時候 elementData.length=0,s = 0 elementData = grow(); elementData[s] = e; size = s + 1; } private Object[] grow() { return grow(size + 1); } private Object[] grow(int minCapacity) { int oldCapacity = elementData.length; if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { int newCapacity = ArraysSupport.newLength(oldCapacity, minCapacity - oldCapacity, /* minimum growth */ oldCapacity >> 1 /* preferred growth */); return elementData = Arrays.copyOf(elementData, newCapacity); } else { return elementData = new Object[Math.max(DEFAULT_CAPACITY,minCapacity)]; } } //oldLength: 當(dāng)前數(shù)組的?度 //minGrowth: 最?需要增加的?度 //prefGrowth: ?選的增? 度(通常是當(dāng)前??的?半) public static int newLength(int oldLength, int minGrowth, int prefGrowth) { // preconditions not checked because of inlining // assert oldLength >= 0 // assert minGrowth > 0 //當(dāng)前?度加上 minGrowth 和 prefGrowth 中的較?值。 int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might overflow if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) { return prefLength; } else { // put code cold in a separate method return hugeLength(oldLength, minGrowth); } } private static int hugeLength(int oldLength, int minGrowth) { int minLength = oldLength + minGrowth; if (minLength < 0) { // overflow throw new OutOfMemoryError("Required array length " + oldLength + " + " + minGrowth + " is too large"); } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) { return SOFT_MAX_ARRAY_LENGTH; } else { return minLength; } }
如果調(diào)?不帶參數(shù)的構(gòu)造?法,第?次分配數(shù)組??為10
后續(xù)擴容為1.5倍擴容
以上就是Java數(shù)據(jù)結(jié)構(gòu)之ArrayList與順序表詳解的詳細內(nèi)容,更多關(guān)于Java ArrayList與順序表的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java實現(xiàn)拖拽文件上傳dropzone.js的簡單使用示例代碼
本篇文章主要介紹了Java實現(xiàn)拖拽文件上傳dropzone.js的簡單使用示例代碼,具有一定的參考價值,有興趣的可以了解一下2017-07-07SpringCloud之監(jiān)控數(shù)據(jù)聚合Turbine的實現(xiàn)
這篇文章主要介紹了SpringCloud之監(jiān)控數(shù)據(jù)聚合Turbine的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-08-08SpringBoot實現(xiàn)事件監(jiān)聽(異步執(zhí)行)的示例代碼
事件監(jiān)聽是一種機制,可以定義和觸發(fā)自定義的事件,以及在應(yīng)用程序中注冊監(jiān)聽器來響應(yīng)這些事件,本文主要介紹了SpringBoot實現(xiàn)事件監(jiān)聽(異步執(zhí)行)的示例代碼,感興趣的可以了解一下2024-08-08springboot 使用yml配置文件給靜態(tài)變量賦值教程
這篇文章主要介紹了springboot 使用yml配置文件給靜態(tài)變量賦值教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04Spring Boot ActiveMQ如何設(shè)置訪問密碼
這篇文章主要介紹了Spring Boot ActiveMQ如何設(shè)置訪問密碼,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07