文章目录
前言
例如:线性表是数据结构的第一部分,也是用的比较多的一部分知识,话不多说,开始卷
一、线性表
线性表(linear list)是 n 个具有相同特性的数据元素的的有限序列。线性表是一种在实际中广泛使用的数据结构。常见的线性表:顺序表、链表、栈、队列
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储
二、顺序表
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改
(一)顺序表的模拟实现
public class MyArrayList {
private int[] elem;
private int usedSize;
private static final int DEFAULT_SIZE = 10;
public MyArrayList() {
this.elem = new int[DEFAULT_SIZE];
}
/**
* 打印顺序表:
* 根据usedSize判断即可
*/
public void display() {
for (int i = 0; i < this.usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
/**
* 判断当前的顺序表是不是满的!
* @return true:满 false代表空
*/
public boolean isFull() {
return this.usedSize == this.elem.length;
}
private boolean checkPosInAdd(int pos) {
return pos < this.usedSize && pos >= 0;//合法
}
//在顺序表末尾增加一个元素
public void add(int data) {
if(isFull()) {
this.elem = Arrays.copyOf(this.elem,elem.length * 2);
}
this.elem[usedSize] = data;
this.usedSize++;
}
// 在 pos 位置新增元素
public void add(int pos, int data) {
if(pos == usedSize) {
add(data);
return;
}
if(!checkPosInAdd(pos)) {
return;
}
if(isFull()) {
this.elem = Arrays.copyOf(this.elem,elem.length * 2);
}
for (int i = this.usedSize - 1; i > pos - 1 ; i--) {
this.elem[i + 1] = this.elem[i];
}
this.elem[pos] = data;
this.usedSize++;
}
// 判定是否包含某个元素
public boolean contains(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if(this.elem[i] == toFind) {
return true;
}
}
return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind) {
for (int i = 0; i < this.usedSize; i++) {
if(this.elem[i] == toFind) {
return i;
}
}
return -1;
}
// 获取 pos 位置的元素
public int get(int pos) {
if(checkPosInAdd(pos)) {
return this.elem[pos];
}
return -1;
}
private boolean isEmpty() {
return this.usedSize == 0;
}
// 给 pos 位置的元素设为 value
public void set(int pos, int value) {
if(checkPosInAdd(pos)) {
this.elem[pos] = value;
}
}
//删除第一次出现的关键字key
public void remove(int toRemove) {
int cur = indexOf(toRemove);
if(cur == -1) {
return;
}
for (int i = cur; i < this.usedSize - 1; i++) {
this.elem[i] = this.elem[i + 1];
}
this.usedSize--;
}
// 获取顺序表长度
public int size() {
return this.usedSize;
}
// 清空顺序表
public void clear() {
this.usedSize = 0;
}
}
三、ArrayList简介
在集合框架中,ArrayList 是一个普通的类,实现了 List 接口,具体框架如图:
ArrayList集合框架
【说明】
- ArrayList 实现了 RandomAccess 接口,表明 ArrayList 支持随机访问
- ArrayList 实现了 Cloneable 接口,表明 ArrayList 是可以 clone 的
- ArrayList 实现了 Serializable 接口,表明 ArrayList 是支持序列化的
- 和 Vector 不同,ArrayList 不是线程安全的,在单线程下可以使用,在多线程中可以选择 Vector 或者 CopyOnWriteArrayList
- ArrayList 底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
四、ArrayList使用
(一)ArrayList的构造
方法 | 解释 |
---|---|
ArrayList() | 无参构造 |
ArrayList(Collection<? extends E>c) | 利用其他 Collection 构建 ArrayList(例如利用LinkedList类型对象构造一个相同元素的ArrayList类型对象) |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
关于底层实现代码中,有一点可以注意一下,就是有参的构造方法和无参的构造方法中,EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的使用区别,强调一点,调用无参构造函数时,并不会直接分配十个空间的内存单元,而是在调用add方法时才会根据之前调用的是无参还是有参的构造函数来分配空间的
ArrayList中EMPTY_ELEMENTDATA和DEFAULTCAPACITY_EMPTY_ELEMENTDATA的区别
实例化一个顺序表:
public static void main(String[] args) {
//避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
//ArrayList创建,推荐写法
//构造一个空的列表
List<Integer> list = new ArrayList<>();
//构造一个具有10个容量的列表
List<Integer> list1 = new ArrayList<>(10);
list1.add(1);
list1.add(2);
}
(二)ArrayList常见操作
方法 | 解释 |
---|---|
boolean add(E e) | 尾插 e |
void add(int index,E element) | 将 e 插入到 index 位置 |
boolean addAll(Collection<? extends E>c) | 尾插 c 中的元素 |
E remove(int index) | 删除 index 位置元素 |
boolean remove(Object o) | 删除遇到的第一个o |
E get(int index) | 获取下标 index 位置元素 |
E set(int index,E element) | 将下标 index 位置元素设置为 element |
void clear() | 清空 |
boolean contains(Object o) | 判断 o 是否在线性表中 |
int indexOf(Object o) | 返回第一个 o 所在下标 |
int lastIndexOf(Object o) | 返回最后一个 o 的下标 |
ListsubList(int fromIndex,int tolndex) | 截取部分 list(注意:截取的结果并不会创建一个新的线性表,而是让定义的 list 直接指向原顺序表的fromIndex的位置) |
(三)ArrayList的遍历
ArrayList 可以使用四种方式遍历:for循环 + 下标、foreach、由于重写了toString方法、使用迭代器
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(5);
int size = list.size();
for (int i = 0; i < size; i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
for (Integer x: list) {
System.out.print(x + " ");
}
System.out.println();
Iterator<Integer> it1 = list.iterator();
while(it1.hasNext()) {
System.out.print(it1.next() + " ");
}
System.out.println();
System.out.println("------------------");
System.out.println("迭代器的remove操作:");
Iterator<Integer> it = list.iterator();
int i = 0;
while ( it.hasNext() ) {
Integer str = it.next();
System.out.print(str + " ");
it.remove();
}
System.out.println(list);
}
上面有额外补充的关于迭代器的 remove 方法的练习,要注意的是对迭代器实例化后,如果使用了本身类里的方法对元素进行了修改,迭代器的结构就会被破环,这种情况下使用 remove 方法会抛出异常,具体解释在下面链接中:
Java迭代器Iterator的remove()方法
(四)ArrayList的扩容机制
【步骤】
- 检测是否真正需要扩容,如果是调用grow准备扩容
- 预估需要库容的大小
- 初步预估按照1.5倍大小扩容
- 如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
- 真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
- 使用copyOf进行扩容