ArrayList

ArrayList是一个多线程不安全的基于数组实现的集合结构

1、基本参数

private static final int DEFAULT_CAPACITY = 10;
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static final Object[] EMPTY_ELEMENTDATA = {};
private transient Object[] elementData;

默认容量为10,最大容量为:Integer.MAX_VALUE - 8
elementData:数组对象,用来当作容器
EMPTY_ELEMENTDATA:空数组,容量为0

2、构造方法:

有参构造方法:

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

若传入的initialCapacity大于0,则初始化一个Object类型的数组,并用这个initialCapacity作为它的初始容量;若initialCapacity为0则构造一个空数组;若小于0就抛出异常。

无参构造方法:

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

它会初始化一个空的数组
从上面我们可以看到,ArrayList初始化时除非我们给它赋初值,否则它会创建一个空的数组并且动态的增加数组容量

3、常用方法:

**public E get(int index)
public E set(int index, E element)
public boolean add(E e)
public void add(int index, E element)
public E remove(int index)
public boolean remove(Object o)
**

插入元素:
set方法:

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

获取位置i,并向其中插入元素,若i不在数组的size范围之内则抛出异常,否则用新值替换旧值并返回旧值

与add方法相关的一系列方法:

public boolean add(E e) {
    ensureCapacityInternal(size + 1); 
    elementData[size++] = e;
    return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
/*刚初始化并且未给数组赋初值*/
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

add为直接添加一个元素。
若是刚初始化并且未给数组赋初值则会为数组赋默认初值DEFAULT_CAPACITY=10,然后插入当前元素
每当数组已满时就开始扩容

扩容:

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

新容量为oldCapacity +oldCapacity /2,变成原来的1.5倍大小并将原数组所有元素拷贝到新数组中

另一个add方法:

public void add(int index, E element) {
    rangeCheckForAdd(index);
    ensureCapacityInternal(size + 1); 
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    elementData[index] = element;
    size++;
}

在指定位置新增一个元素,并将这个位置往后所有元素都右移一位,而不是用新元素去替换旧元素

获取元素:
get方法:

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

获取位置i上的元素,若i不再size范围内则抛出异常,这里注意,若容量为10而里面的元素只有5个,则arrayList.get(i>5)会抛出异常而不是null

实例:

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import org.junit.Test;
public class T {
	@Test
	public void tt() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
		List list = new ArrayList();
		/*for(int i=0;i<1;i++) {
			list.add(1);
		}*/
		Field field = list.getClass().getDeclaredField("elementData");
		field.setAccessible(true); 
		Object[] elementData = (Object[]) field.get(list);
		System.out.println("ArrayList容量:            " + list.size());
		System.out.println("----------分割线---------");
		System.out.println("内部数组容量:               " + elementData.length);
		System.out.println("----------分割线----------");
		System.out.print("元素值为:" + "             ");
		for(Object i : elementData)
			System.out.print(i + "   ");
	}
}

这里我们通过反射拿到对应的ArrayList内部private变量和private方法,开始并不插入元素,然后打印看结果:

ArrayList容量:            0
----------分割线---------
内部数组容量:               0
----------分割线----------
元素值为:             

可以看到,ArrayList和内部数组容量:都为0

然后我们打开上述代码中for循环的注释,并向里面插入一个元素,结果如下:

ArrayList容量:            1
----------分割线---------
内部数组容量:               10
----------分割线----------
元素值为:             1   null   null   null   null   null   null   null   null   null   

可以看到,ArrayList的容量为1,内部数组elementData会自动扩容为10
接下来我们插入10个元素,结果如下:

ArrayList容量:            10
----------分割线---------
内部数组容量:               10
----------分割线----------
元素值为:             1   1   1   1   1   1   1   1   1   1   

ArrayList容量没变,elementData容量没变,接下来插入11个元素,结果:

ArrayList容量:            11
----------分割线---------
内部数组容量:               15
----------分割线----------
元素值为:             1   1   1   1   1   1   1   1   1   1   1   null   null   null   null   

可以看到,ArrayList的容量和自己添加进去的元素个数一样,而内部数组elementData容量变成了原来的1.5倍,15。接下来插入15个元素:

ArrayList容量:            15
----------分割线---------
内部数组容量:               15
----------分割线----------
元素值为:             1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   

内、外数组都满了,并未扩容

接下来16个:

ArrayList容量:            16
----------分割线---------
内部数组容量:               22
----------分割线----------
元素值为:             1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   1   null   null   null   null   null

好了,这下明白了,内部数组在一开始时容量为0,等到我们插入1个元素后,自动扩容到10,并且它总是会等到快要溢出的时候才会进行扩容,扩容后的大小为之前容量的1.5倍,而ArrayList容量(size)记录的是你放到数组中元素的个数

remove方法:

private void fastRemove(int index) {
    modCount++;
    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
}

这里就不贴代码了,remove方法主要就是靠上述代码来进行数组的删除。在这里,删除的时候会要求我们传入一个参数,它表示了我们所要删除的元素在数组中的索引。然后ArrayList会将索引数组后面的元素向前挪,并且覆盖这个位置来达到删除
在这里又有一个问题了:
ArrayList循环遍历并删除元素会出现错误吗?
我们用例子来说明:

import java.util.ArrayList;
import java.util.List;

import org.junit.Test;
public class T {
	@Test
	public void tt(){
		List list = new ArrayList();
		list.add("a");
		list.add("b");
		list.add("d");
		list.add("d");
		list.add("e");
		list.add("f");
		System.out.println(list);
		for(int j=0;j<=list.size()-1;j++) {
			if(list.get(j).equals("d"))
				list.remove(list.get(j));
		}
		System.out.println(list);
	}
}

这里,向ArrayList中添加了6个字符串,并且再一次循环中试图删除其中重复的元素d。下面是结果:

[a, b, d, d, e, f]     这一行是数组刚赋值过后的结果
[a, b, d, e, f]     这一行是数组删除后的结果

可以发现,元素“d”并没有被完全删除。因为每次调用remove方法后,被删除的元素之后所有的元素会向前挪动一位,也就是说这里第一次删除过后,后面一个“d”把删除的“d”的位置给覆盖掉了。下一次进行remove时,就不会再次遍历这个遍历过的位置,导致无法重复删除。
解决办法:删除时从后往前删除,就不会有这种情况了

数组越界的问题:
发生越界时的数组下标分别为10、15、22、33、49和73,由此推断越界异常都发生在数组扩容之时。
这里假设A和B两个线程要向集合中添加元素,首先呢,集合中已经存在了9个元素,正常情况下要是再向里面插入2个元素,它就会扩容,扩容后的容量为15
首先,线程A开始执行add()方法,在执行ensureCapacityInternal(size + 1)时,发现集合没有满,故数组没有扩容,然后线程A被阻塞。接下来线程B进入add()方法,执行ensureCapacityInternal(size + 1),由于前一个线程并没有添加元素,现在容量任然是9,依然不需要扩容,所以该线程就开始添加元素,size++,变为10,此时集合已满。而刚刚被阻塞的线程A开始执行,因为它之前判断不需要扩容,所以就直接向集合中插入第11个元素。这时就发生了数组下标越界异常!

遍历:
1、for循环

List list = new ArrayList();
    list .add(1);
	list .add(2);
	list .add(3);
	list .add(4);
for(int i=0;i<list.size();i++)
	System.out.println(l.get(i));

推荐使用此方法

2、iterator

Iterator iterator = l.iterator();
	while(iterator.hasNext()) {
		System.out.println(iterator.next());
	}

或者

for(Iterator it2 = list.iterator();it2.hasNext();){
    System.out.println(it2.next());
}

3、增强for

for(String tmp:list){
    System.out.println(tmp);
 }

总结:ArrayList是一个数组容器,底层由动态数组来进行实现,每当容量已满时就开始扩容,扩容时会创建一个新数组容量为之前的1.5倍,并且会将所有元素拷贝到新数组中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值