Java集合框架源码分析:ArrayList

一、ArrayList特性

特性描述
是否允许为null允许
是否允许数据重复允许
是否有序有序
是否线程安全非线程安全

二、ArrayList底层数据结构

底层的存储结构为数组,并且可以动态的调整数组的大小。

数组的特性

  • 增删慢:每次删除元素,都需要更改数组长度、拷贝以及移动元素位置。
  • 查询快:由于数组在内存中是一块连续空间,因此可以根据地址 + 索引的方式快速获取对应位置上的元素。

三、ArrayList继承关系

在这里插入图片描述

1、Serializable标记性接口

类的序列化由实现java.io.Serializable接口的类启用。不实现此接口的类将不会使用任何状态序列化和反序列化。可序列化类的所有子类型都是可序列化的。序列化接口没有方法和字段,仅用于标识可串行化的语义。

  • 序列化:将对象的数据写入到文件(写对象)。
  • 反序列化:将文件中对象的数据读取出来(读对象)。
public interface Serializable {
}

2、Cloneable标记性接口

一个类实现Cloneable接口指示Object.clone()方法,该方法对于该类的实例进行字段的复制是合法的。在不实现Cloneable接口的实例上调用对象的克隆方法会导致异常CloneNotSupportedException被抛出。简而言之:克隆就是依据已经有的数据,创造一份新的完全一样的数据拷贝。
源码

public interface Cloneable {
}

克隆的前提条件

  • 被克隆对象所在的类必须实现Cloneable接口。
  • 必须重写clone方法。

3、RandomAccess标记性接口

标记接口由List实现使用,以表名它们支持快速(通常为恒定时间)随机访问。此接口的主要目的是允许通用算法更改其行为,以便在应用于随机访问列表或顺序访问列表时提供良好的性能。

  • ArrayList:随机访问时间消耗小于顺序访问。
  • LinkList:随机访问的时间消耗大于顺序访问。

4、AbstractList抽象接口

四、ArrayList源码分析

1、构造方法

ConstructorConstructor描述
ArrayList()构造一个初始容量为十的空列表
ArrayList(int initialCapacity)构造具有指定初始容量的空列表
ArrayList(Collection<? extends E> c)构造一个包含指定集合的元素列表,按照它们由集合的迭代器返回的顺序。

2、添加方法

方法名描述
public boolean add(E e)将指定的元素追加到此列表的末尾。
public void add(int index, E element)在此列表中的指定位置插入指定的元素。
public booean addAll(Collection<? extends E> c)按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
public boolean addAll(int index, Collection<? extends E> c)将指定集合中的所有元素插入到此列表中,从指定的位置开始。

当未指定初始化大小的时候,ArrayList先是初始化为一个空数组;但在首次添加元素时,ArrayList才会初始化为一个容量为10的数组。这么做的原因是节省内存,一些场景可能只是单纯的定义了数组并没有真实的使用,直接初始化容量为10的数组会造成不必要的浪费。

指定的元素添加

	private static final int DEFAULT_CAPACITY = 10;

    public boolean add(E e) {
    	// 对内部容量进行判断,详情见下方的被调方法
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

扩容方法

// 1.主体函数
private void ensureCapacityInternal(int minCapacity) {
    // 对容量进行判断
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

// 2.通过最小容量和默认容量求出较大值 (用于第一次扩容)
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

// 3.判断是否需要进行扩容
private void ensureExplicitCapacity(int minCapacity) {
    // 实际修改集合次数+1 (在扩容的过程中没用,主要是用于迭代器中)
    modCount++;

    // 判断当前最小容量是否大于数组长度
    if (minCapacity - elementData.length > 0)
        
        // 将计算出来的容量传递给核心扩容方法
        grow(minCapacity);
}

// 4.核心扩容方法
private void grow(int minCapacity) {
    // 记录数组的实际长度
    int oldCapacity = elementData.length;
    // 核心扩容算法,原容量的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    
    // 判断新容量是否小于当前最小容量(第一次调用add方法必然小于)
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    
    // 判断新容量是否大于最大数组长度,如果
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        
        // 条件满足就计算出一个超大容量
        newCapacity = hugeCapacity(minCapacity);

    // 调用数组工具类方法,创建一个新数组,将新数组的地址赋值给elementData
    elementData = Arrays.copyOf(elementData, newCapacity);
}

// 5.超大容量计算
private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

3、删除方法

方法名描述
public E remove(int index)移除指定位置的元素,并返回该位置的原元素。
public boolean remove(Object o)移除首个为 o 的元素,并返回是否移除成功。
protected void removeRange(int fromIndex, int toIndex)批量移除 [fromIndex, toIndex)内的多个元素,这里需要特别注意: toIndex不包括
public boolean removeAll(Collection<?> c)批量移除指定的多个元素。

根据索引删除

public E remove(int index) {
    // 检查索引是否在范围内
    rangeCheck(index);

	// 实际修改集合次数+1 (在扩容的过程中没用,主要是用于迭代器中)
    modCount++;

	// 获取当前索引原来的数据
    E oldValue = elementData(index);

    // 计算集合需要移动元素的个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);、

	// 将源集合最后一个元素置为null,尽早让垃圾回收机制对其进行回收
    elementData[--size] = null; 

	//返回当前索引原来的数据
    return oldValue;
}

4、修改方法

set(int index, E element) 方法,设置指定位置的元素。

public E set(int index, E element) {
    // 检查索引是否在范围内
    rangeCheck(index);
    
    // 获取当前索引原来的数据
    E oldValue = elementData(index);
    
    // 替换后返回旧数据
    elementData[index] = element;
    return oldValue;
}

5、获取方法

get(int index) 方法,获取指定位置的元素。

public E get(int index) {
    // 检查索引是否在范围内
    rangeCheck(index);

	// 获取该索引元素
    return elementData(index);
}

6、转换方法

  • public Object[] toArray() 将 ArrayList 转换为 Object[] 数组。
  • public T[] toArray(T[] a) 将 ArrayList 转换为指定 T 泛型的数组。
/**
 * 将ArrayList转换成Object类型数组
 * @return Object[]
 */
@Override
public Object[] toArray() {
    // 返回的是 Object[] 类型,需要注意:转换成数组就相当于是将 ArrayList 的底层elementData暴露出去
    return Arrays.copyOf(elementData, size);
}
 
 
public static <T> T[] copyOf(T[] original, int newLength) {
    return (T[]) copyOf(original, newLength, original.getClass());
}

7、迭代器

	// 获取迭代器的方法
    public Iterator<E> iterator() {
    	// 创建了一个对象
        return new Itr();
    }
 private class Itr implements Iterator<E> {
 		// 光标,默认值就是0
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        // 将集合实际的修改次数赋值给预期修改次数
        int expectedModCount = modCount;
		
		// 判断集合是否有元素
        public boolean hasNext() {
        	// 光标是否不等于集合的size
            return cursor != size;
        }

        public E next() {
            checkForComodification();
            int i = cursor;
            // 判断,如果大于集合的size就说明没有元素了
            if (i >= size)
                throw new NoSuchElementException();
            // 把集合存数据数组的地址赋值给该方法的局部变量
            Object[] elementData = ArrayList.this.elementData;
            // 进行判断,如果条件满足就会产生并发修改异常
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            // 光标自增,向下移动一位
            cursor = i + 1;
            // 从数组中取出元素并返回
            return (E) elementData[lastRet = i];
        }

		// 校验预期修改集合次数是否和实际修改集合次数一样
        final void checkForComodification() {
            if (modCount != expectedModCount)
            	// 如果不一样,就会产生并发修改异常
                throw new ConcurrentModificationException();
        }
 }

迭代器

  • 集合每次调用add方法的时候,实际修改次数变量的值会自增一次。
  • 在获取迭代器的时候,集合只会执行一次将实际修改集合的次数赋值给预期修改集合的次数。
  • 集合在删除元素的时候也会针对实际修改次数的变量进行自增的操作。

迭代过程中删除元素注意点

  • 当要删除的元素在集合的倒数第二个位置的时候,不会产生并发修改异常。
    • 原因:是因为 在调用hasNext方法的时候,光标的值和集合的长度一样,那么就会返回false就不会再去调用next方法获取集合的元素,既然不会调用next方法那么底层就不会产生并发修改异常。

8、清空方法

从列表中删除所有元素。

    public void clear() {
        modCount++;

        // clear to let GC do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;

        size = 0;
    }

9、包含方法

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

10、Fail-Fast机制

ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。

五、面试题

1、ArrayList是如何扩容的?

第一次扩容10,以后每次都是原容量的1.5倍。

2、ArrayList频繁扩容导致添加性能急剧下降,如何处理?

在明确容量的情况下,创建ArrayList对象时指定初始化容量。

3、ArrayList插入或删除元素一定慢吗?

4、ArrayList是线程安全的吗?

5、如何复制某个ArrayList到另一个ArrayList中去?

6、已知成员变量集合存储N多用户名称,在多线程的环境下,使用迭代器在读取集合数据的同时如何保证可以正常写入数据到集合?

7、ArrayList和LinkList的区别

参考

  • https://blog.csdn.net/mrluo735/article/details/133926158
  • https://www.bilibili.com/video/BV1gE411A7H8?p=32&vd_source=cd03889ff27e1a185b3e97e3ed96d260
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
java源码包实例源码JAVA开发源码50个合集: Ajax框架 ZK.rar Java图书馆管理系统源程序.rar Java图片倒影效果实例源码.rar Java图片翻折,将图像压扁.rar Java坦克大战网络对战版源代码.rar Java声音播放程序源代码.rar JAVA实现CLDC与MIDP底层编程的代码.rar Java实现HTTP连接与浏览,Java源码下载.rar Java实现的FTP连接与数据浏览程序.rar Java实现的放大镜效果附有源文件.rar Java实现的点对点短消息发送协议(smpp)开发源码.rar Java实现的视频播放程序源码.rar Java实现移动的遮照效果.rar JAVA实现超级玛丽.zip Java实现跟踪鼠标运行坐标的源码.rar Java手机与计算机互发彩信源码.rar Java手机游戏大富翁源代码+注释.rar Java手机短信项目源码.rar Java扫雷源码.rar Java生成自定义控件源代码.rar Java调色板面板源代码.rar Java跳棋(基于SWT).rar Java通讯录手机版源码.rar Java鼠标拖拽功能.rar 乐趣大型购物系统.rar 可实现网上对战和人机对战.rar 基于BS结构的Java可视化工作流定制软件.rar 基于J2ME的Java游戏梦幻炸弹人源程序.rar 基于JAVA的ICQ系统.rar 基于Java的mp3播放器源代码.rar 基于Java的小型人事管理系统,带数据库.rar 基于JAVA的日程提醒簿.rar 基于Java的邮件服务器源程序.rar 基于MVC的Java资源管理器 v2.0.rar 基于smpp协议的Java点对点短信发送源码包.rar 季风进销存管理系统(JSP版).rar 客户管理系统 Alfresco Content Management.rar 家庭多媒体播放器.rar 局域网广播系统java源码.rar 开源Winzip压缩工具Java源码.rar 很不错的Java计算器.rar 很强的Java加密解密算法源码.rar 泡泡堂战车游戏JAVA源码.rar 简单模拟的J2ME潜艇大战源代码.rar 简单的注册与登录功能.rar 类似QQ的聊天软件JAVA源码(附设计文档).rar 进程通信.rar 连接postsql数据库的java代码.rar 附加数据库.rar 雷电游戏JAVA版源程序.rar
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

玉成226

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值