ArrayList源码分析

ArrayList是我们平时开发中很常见的一种集合,那么它的底层是如何实现的呢?下面介绍一下ArrayList主要属性和方法。

源码分析

属性:

1. private static final long serialVersionUID = 8683452581122892189L;
	// 序列化的标识符,用于在序列化过程中标识类的版本。当您对一个对象进行序列化时,Java运行时
	//  会在序列化的字节流中保存这个序列号,以便在反序列化过程中验证类的版本是否一致
2. private static final int DEFAULT_CAPACITY = 10;  
	//默认的容量是10
3. private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	//空数组,通过new ArrayList(0)创建。
4. private static final Object[] EMPTY_ELEMENTDATA = {};
  	//空数组,通过new ArrayList()创建。
5. transient Object[] elementData; 
	//真正存储数据的地方
6. private int size;
	//数组大小 

注意Object[] elementData使用了transient修饰的

在Java中,transient关键字是一个修饰符,用于修饰类的成员变量。当一个成员变量被声明为transient时,它意味着这个变量不应该被序列化。序列化是Java中一个将对象状态转换为字节流的过程,以便可以将其保存到文件中或通过网络传输。当一个对象被序列化时,它的所有非transient字段都会被保存到字节流中,而transient字段则会被忽略,保持其默认值。

在序列化 ArrayList 时,我们通常只关心实际存储在列表中的元素,而不是那些为了数组扩容而预留的、未被使用的空间。如果 elementData 没有被声明为 transient,那么序列化机制会默认序列化整个数组,包括那些未被使用的空间,这将浪费空间并且降低序列化性能。通过将 elementData 声明为 transientArrayList 可以确保只有实际存储的元素被序列化。ArrayList 类提供了自定义的序列化方法 writeObject 和反序列化方法 readObject,这些方法会控制哪些部分被序列化以及如何反序列化。在 writeObject 方法中,ArrayList 只序列化了实际元素的数量和这些元素值,而不是整个 elementData数组。在 readObject 方法中,ArrayList 根据序列化的元素数量和元素值重新构造 elementData 数组,这样就避免了序列化多余的空间。

方法:

构造方法:

public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
            //大于0则直接创建数组
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
            //创建一个空数组
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
            //小于0则抛出异常
        }
    }
   public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
           //创建一个空数组
    }
    //根据集合创建数组
    public ArrayList(Collection<? extends E> c) {
        Object[] a = c.toArray();
        if ((size = a.length) != 0) {
            if (c.getClass() == ArrayList.class) {
                elementData = a;
            } else {
                elementData = Arrays.copyOf(a, size, Object[].class);
            }
        } else {
            // replace with empty array.
            elementData = EMPTY_ELEMENTDATA;
        }
    }

add(E e)方法:

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  //检查是否需要扩容
        elementData[size++] = e;   		//添加元素
        return true;
    }
private void ensureCapacityInternal(int minCapacity) {    
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); 
    }
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
            //如果使用new ArrayList()则后面需要创建一个容量为10数组
        }
        return minCapacity;   
    }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
		
        // 需要扩容
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
private void grow(int minCapacity) {
        // 扩容1.5倍
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //超过最大值则按最大值扩容
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
        //拷贝数组
    }

add(int index, E element)方法

public void add(int index, E element) {
		//检查下标是否异常
        rangeCheckForAdd(index);    
        // 检查是否需要扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将index~size位置的元素全部后移
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //添加元素
        elementData[index] = element;
        size++;
    }
private void rangeCheckForAdd(int index) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

可以看到这二个方法的逻辑还是比较简单的。

那么上面的modcount变量有什么作用呢?

​modCountjava.util.ArrayList 类的一个保护字段,它用来记录 ArrayList 对象结构性变化的次数。所谓的结构性变化是指那些改变了 ArrayList 大小(例如添加或删除元素)或者以其他方式改变其内部结构(例如 clear 操作)的操作。

这个字段的主要用途是作为迭代过程中的一个安全检查机制。当使用迭代器(Iterator)遍历 ArrayList 时,如果在迭代过程中 ArrayList 被结构性修改了,迭代器会检测到 modCount 的变化,并立即抛出 ConcurrentModificationException 异常,以防止发生不可预料的行为。

get(int index)方法

public E get(int index) {
    // 检查是否越界
    rangeCheck(index);
    // 返回数组index位置的元素
    return elementData(index);
}

private void rangeCheck(int index) {
	//数组下标从0开始,所以index >= size就算越界
    if (index >= size)
        throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
    return (E) elementData[index];
}

ArrayLIst遍历方法

1.for循环遍历

ArrayList<String> list = new ArrayList<>();
// 填充列表
for (int i = 0; i < list.size(); i++) {
    String element = list.get(i);
    // 处理元素
}

2. 使用增强型 for 循环(Java 5及以上)

ArrayList<String> list = new ArrayList<>();
// 填充列表
for (String element : list) {
    // 处理元素
}

3. 使用 Iterator 接口

ArrayList<String> list = new ArrayList<>();
// 填充列表
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
    String element = iterator.next();
    // 这个next()方法返回当前指向的元素,并移到下一个元素的位置
}

4. 使用 ListIterator 接口(允许在遍历过程中修改列表)

ArrayList<String> list = new ArrayList<>();
// 填充列表
ListIterator<String> listIterator = list.listIterator();
while (listIterator.hasNext()) {
    String element = listIterator.next();
    // 处理元素
}

5. 使用 forEach 方法和 Lambda 表达式(Java 8及以上):

ArrayList<String> list = new ArrayList<>();
// 填充列表
list.forEach(element -> {
    // 处理元素
});

其实遍历ArrayList还有很多方法,这里就不一一列举了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值