深入理解java集合-入门篇之-ArrayList

一.ArrayList

先来看看ArrayList这个集合, 一般对ArrayList的理解是这样的

单列集合, 有序 可重复, 底层采用数组的方式实现, 可扩容

我们来解析ArrayList的源码

  1. 先看看ArrayList的成员变量
    在这里插入图片描述

在ArrayList中大概存在5-6个成员变量, 但是对我们来说有用的就这两个
1.Object[] elementData 数组存储数据
2.int size 标识ArrayList大小(集合包含的元素个数)

由于ArrayList的源码过于复杂, 我们模拟一份代码, 便于理解

public class Demo_ArrayList<E> {

    // 以Object数组的形式存储数据
    private Object[] elementData;
    // 标识Demo_ArrayList的长度
    private int size;

}
  1. 然后看一下ArrayList的构造方法

在这里插入图片描述

空参构造仅仅做了一件事情
给elementData变量进行初始化
初始长度为0(不是10, 第一次扩容才是10)

在这里插入图片描述

int类型参数的构造比空参构造要复杂一些
还是给elementData变量进行初始化
不过初始长度根据参数创建
参数小于0就会抛出异常

在这里插入图片描述

Collection类型参数就更为复杂了
他的目的是将单列集合参数c复制一份, 然后转化成数组赋值给elementData变量

然后来生成模拟代码
Collection类型参数的构造比较复杂, 就不在模拟

public class Demo_ArrayList<E> {

    // 以Object数组的形式存储数据
    private Object[] elementData;
    // 标识Demo_ArrayList的长度
    private int size;

    // 空参构造
    public Demo_ArrayList() {
        elementData = new Object[]{};
    }

    // int参数构造, 按照initSize的大小初始化elementData
    public Demo_ArrayList(int initSize) {
        if (initSize >= 0) {
            elementData = new Object[initSize];
        } else {
            throw new IllegalArgumentException("非法容量, 容量不能小于0");
        }
    }

}
  1. 然后看一下ArrayList的add方法
    在这里插入图片描述

这个是最常用的add方法,他由三行代码组成
第一行是调用扩容方法, 如果(size+1) 的大小满足扩容条件, 就会自动扩容(扩容后面再详细理解)
第二行就是为数组elementData赋值, 注意(size++)会在本行代码执行完成之后, 再进行自增, 所以参数e赋值到了elementData[size]上, 然后才会使长度加一
第三行表示添加成功了, 失败就会抛异常了

在这里插入图片描述

这个是向指定位置插入数据的add方法,他由五行代码组成
第一行是判断插入位置是否满足条件, 满足(index > size || index < 0)就会抛异常
第二行是调用扩容方法
第三行比较复杂, 他是使用System.arraycopy()方法将elementData数组从index位置分割开, 然后将后半段移一位再覆盖回去, 这样elementData[index]位置便会空出来, 再给新添加的数据element赋值

这里可能不太好理解, 所以举个栗子

  1. 开始elementData数组是这样的 [1, 2, 3, 4, 5, null, null, null, null, null]
  2. 再索引为2处添加数据为10
  3. 然后elementData数组是就会这样 [1, 2, null, 3, 4, 5, null, null, null, null]
  4. 然后elementData数组赋值, [1, 2, 10, 3, 4, 5, null, null, null, null]

然后继续
第四行就是给elementData的index位置赋值
第五行是表示集合长度的size+1

模拟代码

import java.util.Arrays;

public class Demo_ArrayList<E> {

    // 以Object数组的形式存储数据
    private Object[] elementData;
    // 标识Demo_ArrayList的长度
    private int size;

    // 空参构造
    public Demo_ArrayList() {
        elementData = new Object[]{};
    }

    // int参数构造, 按照initSize的大小初始化elementData
    public Demo_ArrayList(int initSize) {
        if (initSize >= 0) {
            elementData = new Object[initSize];
        } else {
            throw new IllegalArgumentException("非法容量, 容量不能小于0");
        }
    }

    public boolean add(E e){
        // 扩容
        grow(size + 1);
        // 赋值
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E e) {
        // 判断
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("非法索引");
        // 扩容
        grow(size + 1);
        // 从index位置向后平移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        // 赋值
        elementData[index] = e;
        // 长度加一
        size ++;
    }

	//一会再写
    private void grow(int i) {}

    @Override
    public String toString() {
        return Arrays.toString(Arrays.copyOf(elementData,size));
    }
    
}

下面来简单说说扩容机制的思路
首先点开ArrayList源码中add方法所调用的那个方法ensureCapacityInternal
在这里插入图片描述
他调用了ensureExplicitCapacity()方法
在这里插入图片描述

这两个方法就是为了判断是否扩容, 和minCapacity参数是否满足条件

在这里插入图片描述

grow()方法是正式扩容的方法
他会将老的容量elementData.length 进行如下算法扩容
int newCapacity = oldCapacity + (oldCapacity >> 1);
使用了位移运算符右移(容量的变化大概是0, 10, 15, 22)

4.然后看一下ArrayList的get方法
在这里插入图片描述

get方法十分简单
第一行判断index索引是否越界(index >= size)
第二行获取elementData[index]的值, 然后按照泛型强转, 返回

4.然后看一下ArrayList的set方法
在这里插入图片描述

set方法也不复杂
第一行还是判断index索引是否越界(index >= size)
第二行通过获取到旧的值按照泛型强转之后, 临时保存
第三行修改elementData[index]的值
第四行返回旧的值(为啥要返回旧的值呢?)

然后生成模拟代码

import java.util.Arrays;

public class Demo_ArrayList<E> {

    // 以Object数组的形式存储数据
    private Object[] elementData;
    // 标识Demo_ArrayList的长度
    private int size;

    // 空参构造
    public Demo_ArrayList() {
        elementData = new Object[]{};
    }

    // int参数构造, 按照initSize的大小初始化elementData
    public Demo_ArrayList(int initSize) {
        if (initSize >= 0) {
            elementData = new Object[initSize];
        } else {
            throw new IllegalArgumentException("非法容量, 容量不能小于0");
        }
    }

    public boolean add(E e){
        // 扩容
        grow(size + 1);
        // 赋值
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E e) {
        // 判断
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("索引越界");
        // 扩容
        grow(size + 1);
        // 从index位置向后平移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        // 赋值
        elementData[index] = e;
        // 长度加一
        size ++;
    }

    // 获取值
    public E get(int index){
        if (index >= size)
            throw new IndexOutOfBoundsException("索引越界");
        return (E)elementData[index];
    }
    
    // 设置值
    public E set(int index, E e){
        if (index >= size)
            throw new IndexOutOfBoundsException("索引越界");
        E oldValue = (E) elementData[index];
        elementData[index] = e;
        return oldValue;
    }

    // 扩容
    private void grow(int minCapacity) {
        // 判断是否满足条件
        if (minCapacity > elementData.length){
            // 如果预计的长度小于10, 则扩容到10
            if (minCapacity < 10){
                minCapacity = 10;
            }
            // 老的容量
            int oldCapacity = elementData.length;
            // 新的容量
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            // 保证新的容量大于预计长度
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            // 扩容
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }

    @Override
    public String toString() {
        return Arrays.toString(Arrays.copyOf(elementData,size));
    }

}

5.然后看一下ArrayList的remove方法
在这里插入图片描述

通过索引删除数据, 有点复杂
第一行依旧是判断index索引是否越界(index >= size)
第二行modCount++ 不知道是做什么的
第三行依旧是获取对应index的老数据
第四行是计算出要截取的长度 = 集合长度 - (索引+ 1), 由于索引是从0开始的, 而集合长度size从1开始才有意义
第五行不太好理解, 他是使用System.arraycopy()方法将elementData数组从index + 1位置分割开, 然后将后半段移一位再覆盖回去, 由于是先复制再覆盖的, 所以ArrayList的最后一个值是多余的
所以在第六行, 现将size减一, 然后在将ArrayList的最后一个值设置为null
最后一行, 返回老的值

同样举个栗子

  1. 开始elementData数组是这样的 [1, 2, 3, 4, 5, null, null, null, null, null]
  2. 再索引为2处删除数据
  3. 然后elementData数组是就会这样 [1, 2, 4, 5, 5, null, null, null, null, null]
  4. 然后删除最后一个, [1, 2, 4, 5, null, null, null, null, null, null]

在这里插入图片描述

通过对象删除数据, 思路就是遍历elementData数组, 匹配参数对应的索引, 然后再通过索引删除
其中参数ken可能为null, 但是ArrayList可以存储null, 所以必须分开处理
其中匹配成功调用的**fastRemove()**方法与上面的remove方法思路一致, 只不过是私有的(为啥要再写一遍一样的方法呢?)

最终模拟代码

import java.util.Arrays;

public class Demo_ArrayList<E> {

    // 以Object数组的形式存储数据
    private Object[] elementData;
    // 标识Demo_ArrayList的长度
    private int size;

    // 空参构造
    public Demo_ArrayList() {
        elementData = new Object[]{};
    }

    // int参数构造, 按照initSize的大小初始化elementData
    public Demo_ArrayList(int initSize) {
        if (initSize >= 0) {
            elementData = new Object[initSize];
        } else {
            throw new IllegalArgumentException("非法容量, 容量不能小于0");
        }
    }

    public boolean add(E e){
        // 扩容
        grow(size + 1);
        // 赋值
        elementData[size++] = e;
        return true;
    }

    public void add(int index, E e) {
        // 判断
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException("索引越界");
        // 扩容
        grow(size + 1);
        // 从index位置向后平移一位
        System.arraycopy(elementData, index, elementData, index + 1,
                size - index);
        // 赋值
        elementData[index] = e;
        // 长度加一
        size ++;
    }

    // 获取值
    public E get(int index){
        if (index >= size)
            throw new IndexOutOfBoundsException("索引越界");
        return (E)elementData[index];
    }

    // 设置值
    public E set(int index, E e){
        if (index >= size)
            throw new IndexOutOfBoundsException("索引越界");
        E oldValue = (E) elementData[index];
        elementData[index] = e;
        return oldValue;
    }

    // 通过索引删除
    public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException("索引越界");

        E oldValue = (E) elementData[index];

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                    numMoved);
        elementData[--size] = null;

        return oldValue;
    }

    // 通过对象删除
    public boolean remove(Object o) {
        if (o == null) {
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    remove(index);
                    return true;
                }
        } else {
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    remove(index);
                    return true;
                }
        }
        return false;
    }

    // 获取大小
    public int size() {
        return size;
    }

    // 扩容
    private void grow(int minCapacity) {
        // 判断是否满足条件
        if (minCapacity > elementData.length){
            // 如果预计的长度小于10, 则扩容到10
            if (minCapacity < 10){
                minCapacity = 10;
            }
            // 老的容量
            int oldCapacity = elementData.length;
            // 新的容量
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            // 保证新的容量大于预计长度
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            // 扩容
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    }

    @Override
    public String toString() {
        return Arrays.toString(Arrays.copyOf(elementData,size));
    }

}

测试代码

   public static void main(String[] args) {
        Demo_ArrayList<String> list = new Demo_ArrayList<>();

        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");
        list.add("f");

        System.out.println(list.size() +":" + list);

        list.set(2, "2");
        list.set(4, "4");

        System.out.println(list.size() +":" + list);
        list.remove(1);

        System.out.println(list.size() +":" + list);
        list.remove("d");

        System.out.println(list.size() +":" + list);

        for (int i = 0; i < list.size(); i++){
            System.out.println(list.get(i));
        }
    }

如果你已经看到这里, 那说明已经对ArrayList的的理解入门了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值