java-ArrayList与顺序表精讲+ArrayList模拟实现


前言

本文主要介绍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;
    }
}

猜你喜欢

转载自blog.csdn.net/m0_57180439/article/details/121726451