文章目录
前言
本文主要介绍ArrayList的构造与常用的操作方法,最后还有ArrayList的模拟实现,预祝大家学有所成。
一、ArrayList简介
1.ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
2.ArrayList实现了Cloneable接口,表面ArrayList是可以clone的
3.ArrayList实现了Serializable接口,表面ArrayList是支持序列化的(序列化指将一个对象转变为字符串)
4.和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList
5.ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表
二、ArrayList使用
2.1ArrayList的构造
代码如下(示例):
public static void main(String[] args) {
//ArrayList创建,(推荐写法)
//构造一个空的列表
List<Integer> list1=new ArrayList<>();//()里面放顺序表的长度
//构造一个具有10容量的列表
List<Integer> list2=new ArrayList<>(10);
list2.add(1);//add方法默认插入数组最后一位
list2.add(2);
list2.add(3);
//list2.add("hhh");//会报错,List<Integer已经限定了list2中只能放整形
System.out.println(list2);//打印[1,2,3]
//使用另一个ArrayList对list3进行初始化
ArrayList<Integer> list3=new ArrayList<>(list2);//可以把list2的对象作为参数传给构造方法
//需要注意的是,如果是Integer类型的ArrayList(也就是ArrayList<Integer>)
// 如果你传参为list,那你的参数的list也必须是Integer的
System.out.println(list3);//打印[1,2,3]
}
2.2ArrayList常见操作
代码如下(示例):
public static void main(String[] args) {
//基本方法介绍
//1. add
ArrayList<String> list=new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");//list底层是一个数组,add方法默认插入到数组最后一个位置(尾插)
System.out.println(list);//打印[a,b,c]
//当然了,你也可以通过传参给add方法 来实现需要位置的插入
list.add(1,"hhh");//往下标1的地方插入字符串hhh
System.out.println(list);//打印[a, hhh, b, c]
//2. addAll
//已有一个list,你可以再来一个list2整体放进list里
ArrayList<String> list2=new ArrayList<>();
list2.add("一个测试");
list.addAll(list2);
System.out.println(list);//打印[a, hhh, b, c, 一个测试],这里也是默认尾插
//3. remove
String ret=list.remove(1);//删除下标为1的元素,remove会返回删除的元素
System.out.println(ret);//打印hhh
System.out.println(list);//打印[a, b, c, 一个测试]
//也可以删除一个具体的数据
list.remove("a");
System.out.println(list);//打印[b, c, 一个测试]
//4. set
list.set(0,"d");//把下标0的位置数据更改为d
System.out.println(list);//打印[d, c, 一个测试]
//5.contains
//判断list中是否包含某个元素
System.out.println(list.contains("c"));//打印true
System.out.println(list.contains("a"));//打印false
//6. indexOf
//查找某个元素下标
System.out.println(list.indexOf("c"));//打印1
//7. lastIndexOf
//查找最后一次出现数据x 的下标
//比如我们现在list3里面有a c c c b
ArrayList<String> list3=new ArrayList<>();
list3.add("a");
list3.add("c");
list3.add("g");
list3.add("c");
list3.add("b");
System.out.println(list3.lastIndexOf("c"));//打印3
//8. subList
//截取list中的一部分,返回一个List<E>类型的数据,如果你list是String的,那E就是String
List<String> tmp=list3.subList(1,3);//从1下标开始截取,截取到下标3的位置,左闭右开(1下标截取,2下标截取,3下标不截取)
System.out.println(tmp);
//9. clear
list.clear();//清空list里的数据
System.out.println(list);
}
2.3ArrayList的遍历
ArrayList可以使用三种方式进行遍历:
1.for循环+下标
2.foreach
3.使用迭代器
public static void main(String[] args) {
List<Integer> list=new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
//使用for循环+下标遍历
//list底层是一个数组,下标从0开始
for(int i=0;i<list.size();i++){
System.out.print(list.get(i)+" ");
//get(i)会返回数组下标为i的元素
//返回值为E类型,也就是我们指定的类型,这里是Integer
}//打印1 2 3 4
System.out.println();
//使用foreach遍历
//ArrayList实现了Iterable接口,所以它可以用foreach进行打印
//ps:用法——for(元素的类型 数组元素 :数组)
//list底层就是一个数组
for(Integer integer:list){
System.out.print(integer+" ");
}//打印1 2 3 4
System.out.println();
//使用迭代器进行打印
//使用迭代器需要调用方法iterator
Iterator<Integer> iterator=list.iterator();
//iterator方法返回值类型为Iterator,我们用Iterator类型的接收
//接收的都是Integer的(因为这里是Iterator<Integer> )
while(iterator.hasNext()){
//hasNext()方法,判断数组下面一个位置还有没有数,
// 有就返回true,并且向数组后一位移动
System.out.print(iterator.next()+" ");
}//打印1 2 3 4
System.out.println();
//还有一种专门打印list相关的迭代器listIterator(它继承了Iterator)
ListIterator<Integer> iterator1=list.listIterator();
while(iterator1.hasNext()){
System.out.print(iterator1.next()+" ");
}//打印1 2 3 4
//ListIterator还有add,remove(对list进行添加和删除)等非常常用的功能
}
2.4ArrayList的扩容机制
我们来看一段代码:
public static void main(String[] args) {
ArrayList<String> list1=new ArrayList<>();
//初始大小为?
ArrayList<String> list2=new ArrayList<>(20);
//初始大小为20
}
当我们没有传值过去,底层的数组怎么判断大小呢?我们ctrl+左键点一下ArrayList看看
elementData是什么?继续点进去看一下
发现elementData就是底层的数组名,这个数组目前是没有初始化的(没有初始化也就意味着目前没有大小)。
size是数组当前有效的数据个数
那我们在ArrayLits()那个构造方法里面看到了
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
DEFAULTCAPACITY_EMPTY_ELEMENTDATA是什么?
继续点进去看一下,发现也是一个数组
你会继续发现,这个冗长的玩意就是骗人的,它啥也没做。
也就是说DEFAULTCAPACITY_EMPTY_ELEMENTDATA,这个数组它初始化了,但是初始化为空(大小为0),然后我们
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
就是elementData指向了后面冗长的玩意指向的对象,但是后面指向的是个空的数组啊,所以我们的elementData也是指向一个空数组
那到这里我们就可以认为
public static void main(String[] args) {
ArrayList<String> list1=new ArrayList<>();
//初始大小为?
}
list2指向的对象(数组)当前没有大小(大小为0)
简单示意图如下:
那这个时候问题又来了,既然这个数组大小为0,那我们存放数据的时候为什么可以成功捏?比如下面这段代码,它是没有报错的:
可以正常打印hhh,那我们现在就需要继续看看add是怎么实现的了,这个后面在ArrayList模拟实现中会提到。
结论:
1.如果ArrayList调用了不带参数的构造方法,那么顺序表的大小为0,当第一次add的时候,整个顺序表才变为10;当这个10倍放满了,开始扩容,扩容为1.5倍。
2.如果调用的是给定容量的构造方法,那么你的顺序表大小就是给定的容量,放满了还是1.5倍进行扩容。
三、ArrayList的模拟实现
class MyArrayList<E>{
private Object[] elementData;//底层数组
private int usedSize;//有效的数据个数
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA={
};
public MyArrayList(){
this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//无参构造
}
public MyArrayList(int capacity) throws IllegalAccessException {
//给了容量了
if(capacity>0){
//数组容量肯定要大于0
this.elementData=new Object[capacity];
}else if(capacity==0){
this.elementData=DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}else{
throw new IllegalAccessException("容量大小不能为负数!!!");//这里你也可以抛一个自定义异常
}
}
public boolean add(E e){
//添加元素,相当于存放在了顺序表最后位置
//确定一个真正的容量
//预测->扩容(把检查顺序表空和满以及扩容放到了一起)
ensureCapacityInternal(usedSize+1);
elementData[usedSize]=e;
usedSize++;
return true;
}
private void copy(int index,E e){
for(int i=usedSize-1;i>=index;i++){
elementData[i+1]=elementData[i];
}
elementData[index]=e;
}
public void add(int index,E e){
//1.检查下标是否合法
rangeCheckForAdd(index);
//2.确定真正的容量
ensureCapacityInternal(usedSize+1);
//挪数据(在某个位置添加后,原先的数据要后移)
copy(index,e);
usedSize++;
}
private void rangeCheckForAdd(int index){
if(index<0||index>size()){
throw new IndexOutOfBoundsException("index位置不合法");
}
}
//获取顺序表大小
public int size(){
return this.usedSize;
}
private void ensureCapacityInternal(int minCapacity){
//1.计算出需要的容量
int capacity=calculateCapacity(elementData,minCapacity);
//2.拿着计算出的容量,去看,满了或空了就扩容
ensureExplicitCapacity(capacity);
}
private void ensureExplicitCapacity(int minCapacity){
//确定明确的容量
//1.如果进不去if语句,说明数组还没有放满
if(minCapacity-elementData.length>0){
//扩容
grow(minCapacity);
}
}
private void grow(int minCapacity){
int oldCapacity= elementData.length;
int newCapacity=oldCapacity+(oldCapacity>>1);//1.5倍扩容
if(newCapacity-minCapacity<0){
newCapacity=minCapacity;
}
if(newCapacity-MAX_ARRAY_SIZE>0){
//说明你要的容量非常大
newCapacity=hugeCapcity(minCapacity);
}
elementData=Arrays.copyOf(elementData,newCapacity);
}
private static final int MAX_ARRAY_SIZE=Integer.MAX_VALUE-8;
private static int hugeCapcity(int minCapacity){
if(minCapacity<0){
throw new OutOfMemoryError();
}
return (minCapacity>MAX_ARRAY_SIZE)?Integer.MAX_VALUE:MAX_ARRAY_SIZE;
}
private static int calculateCapacity(Object[] elementData,int minCapacity){
//1.是否之前elementData数组分配过大小
if(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA){
return Math.max(10,minCapacity);
}
//2.如果分配过,就返回+1后的值
return minCapacity;
}
}