ArrayList详解

本文详细介绍了ArrayList的四种初始化方式,包括普通方式、内部类方式、Arrays.asList和Collections.nCopies。分析了ArrayList的构造函数及扩容机制,并探讨了Arrays.asList构建的集合的特性。此外,还讨论了ArrayList的插入、删除操作以及如何通过哈希值优化元素定位,以实现O(1)的获取时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Array + List = 数组 + 列表 = ArrayList = 数组列表

四种初始化方式

方式一:普通方式(这个方式很简单也是我们最常用的方式)

ArrayList<String> list = new ArrayList<String>();
list.add("aaa");
list.add("bbb");
list.add("ccc");

方式二:内部类方式 (这种方式也比较常用,而且省去了多余的代码量)

ArrayList<String> list = new ArrayList<String>(){
    list.add("aaa");
    list.add("bbb");
    list.add("ccc");
};

方式三:Arrays.asList

ArrayList<String> list = new ArrayList<String>(Arrays.asList("aaa", "bbb", "ccc"));

构造函数源码分析

public ArrayList(Collection <? extends E > c) {
    elementData = c.toArray();
    if((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652) 
        if(elementData.getClass() != Object[].class) 
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array. 
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

通过构造函数可以看到,只要实现 Collection 类的都可以作为入参。 

在通过转为数组以及拷贝Arrays.copyOf 到 Object[] 集合中在赋值给属性elementData 。

注意:c.toArray might (incorrectly) not return Object[] (see 6260652) 

public Object[] toArray() 返回的类型不一定就是Object[] ,其类型取决于其返回的实际类型,毕竟Object 是父类,它可以其他任意型。(子类实现和父同名的方法,仅仅返回值不一致时,默认调用是子类的实现方法)

举例

@Test 
public void t() {
    List < Integer > list1 = Arrays.asList(1, 2, 3);
    System.out.println("通过数组转换:" + (list1.toArray().getClass() == Object[].class)); 
  
    ArrayList < Integer > list2 = new ArrayList < Integer > (Arrays.asList(1, 2, 3));
    System.out.println("通过集合转换:" + (list2.toArray().getClass() == Object[].class));	 
}

测试结果:
通过数组转换:false

通过集合转换:true

造成这个结果的原因,如下:

1. Arrays.asList使用的是:Arrays.copyOf(this.a, size,(Class<? Arrays.copyOf(this.a, size,(Class<? extends T[]>) a .getClass());
2. ArrayList 构造函数使用的是:Arrays.copyOf(elementData, size, Arrays.copyOf(elementData, size, Object[].class);

Arrays.asList构建出来的List与new ArrayList得到的List,压根就不是一个List!类关系图如下:

区别:Arrays.asList 构建的集合,不能赋值给ArrayList
           Arrays.asList 构建的集合,不能再添加元素
           Arrays.asList 构建的集合,不能再删除元素

方式四:Collections.ncopies

//这会初始化一个由10个0组成的集合。
ArrayList<Integer> list = new ArrayList<Integer>(Collections.nCopies(10, 0));

插入

普通插入

/** 
 * Appends the specified element to the end of this list.
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
	ensureCapacityInternal(size + 1); // Increments modCount!!
	elementData[size++] = e;
	return true;
}

ArrayList默认初始化时会申请10个长度的空间,如果超过这个长度则需要进行扩容,那么它是怎么扩容的呢?
从根本上分析来说,数组是定长的,如果超过原来定长长度,扩容则需要申请新的数组长度,并把原数组元素拷贝到新数组中,如下图;

1. 判断长度充足;ensureCapacityInternal(size + 1);
2. 当判断长度不足时,则通过扩大函数,进行扩容; grow(int minCapacity);
3. 扩容的长度计算;int newCapacity = oldCapacity + (oldCapacity >> 1); 旧容量右移1位,这相当于扩容了原来容量的 (int)3/2 。例如:4或10扩容时:1010 + 1010 >> 1 = 1010 + 0101 = 10 + 5 = 15;2或7扩容时:0111 + 0111 >> 1 = 0111 + 0011 >> 7 + 3 = 10;
4. 当扩容完以后,就需要进行把数组中的数据拷贝到新数组中,这个过程会用到Arrays.copyOf(elementData, newCapacity); 但他的底层用到的是System. arraycopy

指定位置插入

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    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));
}

1. 判断size是否可以插入。 
2. 判断插入后是否需要扩容; ensureCapacityInternal(size + 1); 
3. 数据元素迁移,把从待插入位置后的元素,顺序往后迁移。
4. 给数组的指定位置赋值,也就是把待插入元素插入进来。

删除

public E remove(int index) {
	rangeCheck(index);
	modCount++;
	E oldValue = elementData(index);
	int numMoved = size - index - 1;
	if(numMoved > 0) System.arraycopy(elementData, index + 1, elementData, index, numMoved);
	elementData[--size] = null; // clear to let GC do its work 
	return oldValue;
}

删除的过程主要包括;
1. 校验是否越界; rangeCheck(index); 
2. 计算删除元素的移动长度numMoved,并通过System.arraycopy自己把元素复制给自己。
3. 把结尾元素清空,null。

扩展

如果给你一组元素;a、b、c、d、e、f、g,需要你放到ArrayList中,但是要求获取一个元素的时间复杂度都是O(1),你怎么处理?
想解决这个问题,就需要知道元素添加到集合中后知道它的位置,而这个位置呢,其实可以通过哈希值与集合长度与运算,得出存放数据的下标,如下图;

public static void main(String[] args) {
	List <String> list = new ArrayList <String> (Collections. <String> nCopies(8, "0"));
	list.set("a".hashCode() & 8 - 1, "a");
	list.set("b".hashCode() & 8 - 1, "b");
	list.set("c".hashCode() & 8 - 1, "c");
	list.set("d".hashCode() & 8 - 1, "d");
	list.set("e".hashCode() & 8 - 1, "e");
	list.set("f".hashCode() & 8 - 1, "f");
	list.set("g".hashCode() & 8 - 1, "g");
	System.out.println("元素集合:" + list);
	System.out.println("获取元素f [\"f\".hashCode() & 8 - 1)] Idx:" + ("f".hashCode() & (8 - 1)) + " 元素:" + list.get("f".hashCode() & 8 - 1));
	System.out.println("获取元素e [\"e\".hashCode() & 8 - 1)] Idx:" + ("e".hashCode() & (8 - 1)) + " 元素:" + list.get("e".hashCode() & 8 - 1));
	System.out.println("获取元素d [\"d\".hashCode() & 8 - 1)] Idx:" + ("d".hashCode() & (8 - 1)) + " 元素:" + list.get("d".hashCode() & 8 - 1));
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值