ArrayList 与顺序表:Java 线性表深度解析
hello!这里是敲代码的小董,很荣幸您阅读此文,本文只是自己在学习JAVA过程中的笔记,如有不足,期待您的评论指点和关注,欢迎欢迎~~
✨✨个人主页:敲代码的小董
💗💗系列专栏:JAVA
目录
1. 线性表
2. 顺序表
2.1 接口的实现
3. ArrayList简介
4. ArrayList使用
4.1 ArrayList的构造
4.2 ArrayList常见操作
4.3 ArrayList的遍历
4.4 ArrayList的扩容机制
5. ArrayList的具体使用
5.1 简单的洗牌算法
5.2 杨辉三角
6. ArrayList的问题及思考
1. 线性表
线性表,从名字上就可以感觉到,是具有像线一样的性质的表。就比如一个班级的小朋友,一个跟一个排着队,有一个打头,有一个收尾,当中的每一个小朋友都知道他前面一个是谁,他后面一个是谁,这样如同一根线一样把他们串联起来,就可以称之为线性表。
线性表(linear list)是n个具有相同特性的数据元素的有限序列,是最基本、最简单的数据结构之一。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列......
注意:
- 线性表是一个序列,元素之间是有顺序的。
- 若存在多个元素,则第一个元素无前驱,最后一个元素无后继。
- 其他每个元素,有且只有一个前驱和后继。
如果一个小朋友去拉两个小朋友后面的衣服,那就不可能排成一队了;同样,如果一个小朋友后面的衣服,被两个甚至多个小朋友拉扯,这其实是在打架,并不是有序排队。
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
2. 顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。
线性表的顺序存储结构就是在内存中找一块地儿,通过占位的形式,把一定的内存空间给占了,然后把相同数据类型的数据元素依次存放在这块空地中。因为线性表的每个数据元素的类型都相同,所以可以使用一维数组来实现顺序存储结构。
2.1 接口的实现
以简单顺序表实现为例:
public class MyArrayList {private int[] array;private int size;// 默认构造方法public MyArrayList() {this.array = new int[10];}// 将顺序表的底层容量设置为initCapacitypublic MyArrayList(int initCapacity) {this.array = new int[initCapacity];}// 判断是否满private boolean isFull() {if (this.size >= this.array.length) {return true;}return false;}// 扩容方法private void resize() {this.array = Arrays.copyOf(this.array, 2 * this.array.length);}// 新增元素,默认在数组最后新增public void add(int data) {if (isFull()) {resize();}this.array[this.size] = data;this.size++;}private void checkPos(int pos) {if (pos < 0 || pos >= this.size) {throw new PosOutOfBoundsException(pos + " 位置不合法");}}// 在 pos 位置新增元素public void add(int pos, int data) {if (pos < 0 || pos > this.size) {throw new PosOutOfBoundsException(pos + " 位置不合法");}if (isFull()) {this.array = Arrays.copyOf(this.array, 2 * this.array.length);}for (int i = this.size - 1; i >= pos; i--) {this.array[i + 1] = this.array[i];}this.array[pos] = data;this.size++;}// 判定是否包含某个元素public boolean contains(int toFind) {for (int i = 0; i < this.size; i++) {if (this.array[i] == toFind) {return true;}}return false;}// 查找某个元素对应的位置public int indexOf(int toFind) {for (int i = 0; i < this.size; i++) {if (this.array[i] == toFind) {return i;}}return -1;}// 获取 pos 位置的元素public int get(int pos) {checkPos(pos);return this.array[pos];}// 给 pos 位置的元素设为 valuepublic void set(int pos, int value) {checkPos(pos);this.array[pos] = value;}//删除第一次出现的关键字keypublic void remove(int toRemove) {int index = indexOf(toRemove);if (index == -1) {System.out.println("没有找到这个元素");return;}for (int i = index; i < this.size - 1; i++) {this.array[i] = this.array[i + 1];}this.size--;}// 获取顺序表长度public int size() {return this.size;}// 清空顺序表public void clear() {this.size = 0;}// 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的public void display() {for (int i = 0; i < this.size; i++) {System.out.print(this.array[i] + " ");}System.out.println();}
}
3. ArrayList简介
在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:
ArrayList是 Java 集合框架中List接口的实现类,底层基于动态数组。它允许存储任意类型元素(包括null),并保持元素插入顺序。ArrayList本质是动态顺序表,解决了普通数组长度固定的问题,适合频繁随机访问的场景。
说明:
- ArrayList是以泛型方式实现的,使用时必须要先实例化。
- ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问。
- ArrayList实现了Cloneable接口,表明ArrayList是可以clone的。
- ArrayList实现了Serializable接口,表明ArrayList是支持序列化的。
- 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList。
- ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表。
4. ArrayList使用
4.1 ArrayList的构造
//无参构造
ArrayList<String> list = new ArrayList<>(); //默认初始容量为10。
//指定初始容量
ArrayList<Integer> list = new ArrayList<>(16); //预知数据量时可减少扩容开销。
4.2 ArrayList常见操作
ArrayList虽然提供的方法比较多,但是常用方法如下所示,需要用到其他方法时,可以自行查看ArrayList的帮助文档。
public class Main {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("JavaSE");list.add("JavaWeb");list.add("JavaEE");list.add("JVM");list.add("计算机");System.out.println(list);// 获取list中有效元素个数System.out.println(list.size());// 获取和设置index位置上的元素,注意index必须介于[0, size)间System.out.println(list.get(1));list.set(1, "JavaWEB");System.out.println(list.get(1));// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置list.add(1, "Java数据结构");System.out.println(list);// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置list.remove("JVM");System.out.println(list);// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常list.remove(list.size() - 1);System.out.println(list);// 检测list中是否包含指定元素,包含返回true,否则返回falseif (list.contains("计算机")) {list.add("计算机");}// 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找list.add("JavaSE");System.out.println(list.indexOf("JavaSE"));System.out.println(list.lastIndexOf("JavaSE"));// 使用list中[0, 4)之间的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组List<String> ret = list.subList(0, 4);System.out.println(ret);list.clear();System.out.println(list.size());}
}
4.3 ArrayList的遍历
普通for
循环:
for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i));
}
迭代器:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) { System.out.println(iterator.next());
}
增强for
循环:
for (String element : list) { System.out.println(element);
}
4.4 ArrayList的扩容机制
ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。
rrayList默认初始容量为10。当元素数量达到容量上限,会触发扩容:
- 计算新容量:通常为原容量的1.5倍。
- 调用Arrays.copyof复制原数组元素到新数组。频繁扩容影响性能,建议预知数据量时指定初始容量。
5. ArrayList的具体使用
5.1 简单的洗牌算法
CardList.java
import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class CardList {private static final String[] SUITS =new String[]{"♥","♠","♣","♦"};public static List<Card> buyCard(){List<Card> list =new ArrayList<>();for (int i = 0; i < SUITS.length; i++) {for (int j = 1; j < 13 ; j++) {Card card =new Card(SUITS[i],j );list.add(card);}}return list;}public static void shuffle(List<Card> list){Random random =new Random();for (int i = list.size()-1; i > 0 ; i--) {int index =random.nextInt(i);swap(list,i,index);}}public static void swap(List<Card> list,int i,int j) {Card tmp = list.get(i);list.set(i, list.get(j));list.set(j, tmp);}public static void deal(List<List<Card>> hand,List<Card> list){for (int i = 0; i < 5; i++) {for (int j = 0; j < 3; j++) {hand.get(j).add(list.remove(0));}} }public static void main(String[] args) {List<Card> list =buyCard();shuffle(list);System.out.println(list);List<List<Card>> hand =new ArrayList<>();List<Card> hand1 =new ArrayList<>();List<Card> hand2 =new ArrayList<>();List<Card> hand3 =new ArrayList<>();hand.add(hand1);hand.add(hand2);hand.add(hand3);deal(hand,list);for (int i = 0; i < hand.size(); i++) {System.out.println("第"+(i+1)+"个人的牌"+hand.get(i));}System.out.println("剩下的牌"+list);}
}
Card.java
public class Card {private String suit; //花色private int rank; //数字public Card(String suit, int rank) {this.suit = suit;this.rank = rank;}public String getSuit() {return suit;}public void setSuit(String suit) {this.suit = suit;}public int getRank() {return rank;}public void setRank(int rank) {this.rank = rank;}@Overridepublic String toString() {return suit + rank;}
}
5.2 杨辉三角
杨辉三角:
public class Solution {public List<List<Integer>> generate(int numRows) {List<List<Integer>> ret =new ArrayList<>();//处理第一行List<Integer> row =new ArrayList<>();row.add(1);ret.add(row);//从第二行开始处理for (int i = 1; i <numRows ; i++) {List<Integer> curRow =new ArrayList<>();curRow.add(1);for (int j = 1; j < i; j++) {curRow.add(ret.get(i-1).get(j)+ret.get(i-1).get(j-1));}curRow.add(1);ret.add(curRow);}return ret;}
}
6. ArrayList的问题及思考
- ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)。
- 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
- 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。