目录
1.线性表
2.顺序表
2.1接口的实现
3.ArrayList的简介
4.ArrayList使用
4.1ArrayList的构造
4.2ArrayList常见的操作
4.3ArrayList的遍历
4.4ArrayList的扩容机制
5.模拟实现一个ArrayList
1.线性表
线性表:是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表,链表,栈,队列...
线性表在逻辑上线性结构,也就是说连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上储存时,通常以数组和链式结构的形式储存。
2.顺序表
顺序表是用一段物理地址连续的储存单元一次储存数据元素的线性结构,一般情况下采用数组储存。在数组上完成数据的增删查改。
2.1接口的实现
//顺序表接口的实现
class MyArrayList {
private int[] array;
private int size;
//默认的构造方法
public MyArrayList() { }
//将顺序表的底层容量设置成initcapacity
public MyArrayList(int initcapacity) { }
//新增元素,默认在数组的最后新增
public void add(int data) { }
//在pos位置新增元素
public void add(int pos, int data) { }
//判断是否包含某个元素
public boolean contains(int toFind) { return true; }
//寻找某个元素对应的下标
public int indexOf(int toFind) { return -1; }
//获取pos位置的元素
public int get(int pos) { return -1; }
//给pos位置的元素设为value
public void set(int pos, int value) { }
//删除第一次出现的关键字key
public void remove(int toRemove) { }
//获取顺序表长度
public int size() { return 0; }
//清空顺序表
public void clear() { }
//为了方便测试,我们打印顺序表
public void display() { }
}
3.ArrayList的简介
在集合框架中,ArrayList是一个普通类,实现了List接口。
【说明】
- ArrayList是以泛型方式实现的,使用时必须要先实例化;
- ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问;
- ArrayList实现了Cloneable接口,表示ArrayList是可以clone的;
- ArrayList实现了Serializable接口,表明ArrayList是支持序列化的;
- 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList;
- ArrayList的底层是一段连续的空间,可以进行动态扩容,是一个动态类型的顺序表。
4.ArrayList使用
4.1ArrayList的构造
方法 | 解释 |
---|---|
ArrayList() | 无参构造 |
ArrayList(Collection<? extends E> c) | 利用其他的Collection构造ArrayList |
ArrayList(int initialCapacity) | 指定顺序表初始容量 |
//ArrayList的使用
public class Test {
public static void main(String[] args) {
//无参构造
List<Integer> list1 = new ArrayList<>();
//构造一个10容量的列表
List<Integer> list2 = new ArrayList<>(10);
//构造一个和list2中元素一致
ArrayList<Integer> list3 = new ArrayList<>(list2);
}
}
4.2ArrayList常见的操作
ArrayList提供了很多的方法,但是常用的方法是有限的,我们接下来进行演示。
//ArrayList常用的方法
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//在数组的末尾进行添加元素
System.out.println("=========在数组的末尾进行添加元素=========");
list.add("javaSE");
list.add("javaWeb");
list.add("javaEE");
list.add("JVM");
list.add("末尾");
System.out.println(list);
//获取list中有效的元素个数
System.out.println("============获取list有效的元素个数=============");
System.out.println(list.size());
System.out.println("============获取和设置1位置的元素===============");
System.out.println(list.get(1));
list.set(1, "1位置修改");
System.out.println(list.get(1));
System.out.println("============在某位置插入指定元素================");
list.add(1,"插入1位置");
System.out.println(list);
System.out.println("=============删除指定位置元素==================");
list.remove("插入1位置");
System.out.println(list);
list.remove(1);
System.out.println(list);
System.out.println("=============检测是否包含某一元素===============");
System.out.println(list.contains("java"));
System.out.println("=============在数组中查找某一元素,并返回其下标====");
System.out.println(list.indexOf("javaEE"));
System.out.println(list.lastIndexOf("javaEE"));
System.out.println("=============使用list中[0,2)之间的元素构成一个新的数组,但是其实是共用的一个数组=========");
List<String> ret = list.subList(0,2);
System.out.println(ret);
System.out.println("=============清空数组=========================");
list.clear();
System.out.println(list.size());
}
}
4.3ArrayList的遍历
ArrayList可以使用三种方式来遍历: for循环+下表、foreach、使用迭代器
//遍历
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//for+下标
for (int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + " ");
}
System.out.println();
//foreach
for (Integer integer : list) {
System.out.print(integer + " ");
}
System.out.println();
//迭代器
Iterator<Integer> it = list.listIterator();
while(it.hasNext()){
System.out.println(it.next() + " ");
}
System.out.println();
}
}
【注意】
- ArrayList最长使用的遍历方法是:for遍历+小标以及foreach;
- 迭代器是一种设计模式的一种。
4.4ArrayList的扩容机制
下面的代码是否有缺陷?为什么?
//是否有缺陷
public class Test {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i = 0; i < 100; i++) {
list.add(i);
}
}
}
ArrayList是一个动态类型的顺序表,即:在插入元素的过程中,会自动的扩容。我们看看以下的源码。
我们在创建ArrayList对象时,如果调用无参的构造方法,实际上并没有给它分配相应的空间,只有当add时,才会开始分配,所以创建对象时的大小为0;我们再调用add()方法,add()会先确定内部容量ensureCapacityInternal();再计算具体需要空间的数值calculateCapacity();在进行calculateCapacity()函数计算具体需要空间的数值时,我们判断这个数组是否为空,如果为空,返回默认值10或有效数据数+1中的较大值,我提的这种情况是返回10;然后进入ensureExplicitCapacity()确定数组的显式容量,需要的容量是10,但现在数组的容量是0,需要扩容,进入函数grow();这个函数grow()会扩大到原来数组的1.5倍,如果新的容量没有旧容量大,就扩大到传过来的参数(需要的空间大小),如果新的容量超过数组容量的上线,就直接扩大到上限,然后进行原数据的复制;最后新增了一个元素。
5.模拟实现一个ArrayList
我们使用一个自定义的类来模拟实现一个ArrayList中所有常用的功能。
package demo2;
import java.util.Arrays;
/**
* Describe:
* User:lenovo
* Date:2022-12-28
* Time:18:50
*/
//ArrayList的模拟实现
//这个类中可能抛出IndexOutOfException,我们在使用的使用要进行异常捕捉
class MyArrayList{
public int[] elem;
public int usedSize;//用于记录储存了多少数据
public static final int DEFAULT_SIZE = 5;
//构造方法
public MyArrayList() {
this.elem = new int[DEFAULT_SIZE];
}
//为了方便测试,我们险些打印顺序表,注意:这不是顺序表中的方法。
public void display() {
for (int i = 0; i < usedSize; i++) {
System.out.print(this.elem[i] + " ");
}
System.out.println();
}
//获取顺序表的长度
public int size() {
return 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;
}
//新增元素,在数组的最后新增元素
public void add(int data) {
//判断空间是否存满
if(this.isFull()) {
resize();
}
this.elem[this.usedSize] = data;
this.usedSize++;
}
/**
* 判断数组是否存储满了
* @return
*/
private boolean isFull() {
/*if(this.usedSize == this.elem.length) {
return true;
}
return false;*/
return this.usedSize == this.elem.length;
}
/**
* 扩容
* 用于为数组开辟新的空间
*/
private void resize() {
this.elem = Arrays.copyOf(this.elem, 2*this.elem.length);
}
//在pos位置新增新的元素
public void add(int pos, int data) {
checkIndex(pos);
if(isFull()) {
resize();
}
for(int i = usedSize - 1; i >= pos; i--) {
elem[i+1] = elem[i];
}
elem[pos] = data;
usedSize++;
}
/**
* 用于检查传过来的下表是否合法
* @param pos
*/
private void checkIndex(int pos) {
if(pos < 0 || pos > usedSize) {
throw new IndexOutOfException("位置不合法,请检查位置的合法性!");
}
}
//获取pos位置的元素
public int get(int pos) {
checkGetIdex(pos);
return elem[pos];
}
/**
* 检查要获取元素的下表是否合法
*/
private void checkGetIdex(int pos) {
if(pos < 0 || pos >= usedSize) {
throw new IndexOutOfException("get获取元素时,位置不合法,请检查位置的合法性!");
}
}
//给pos位置的元素设为value
public void set(int pos, int value) {
checkGetIdex(pos);
this.elem[pos] = value;
}
//删除第一次出现的关键字key
public boolean remove(int toRemove) {
int index = indexOf(toRemove);
if(index == -1) {
System.out.println("没有这个数据!");
return false;
}
for(int i = index; i < usedSize - 1; i++) {
elem[i] = elem[i + 1];
}
usedSize--;
elem[usedSize] = 0;
return true;
}
//清空顺序表
public void clear() {
usedSize = 0;
}
}
package demo2;
/**
* Describe:
* User:lenovo
* Date:2022-12-28
* Time:18:51
*/
public class IndexOutOfException extends RuntimeException{
public IndexOutOfException() {
super();
}
public IndexOutOfException(String message) {
super(message);
}
}