Java基础之ArrayList
1. 概述
ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现。除该类未实现同步外,其余跟Vector大致相同。每个ArrayList都有一个容量(capacity),表示底层数组的实际大小,容器内存储元素的个数不能多于当前容量。当向容器中添加元素时,如果容量不足,容器会自动增大底层数组的大小。前面已经提过,Java泛型只是编译器提供的语法糖,所以这里的数组是一个Object数组,以便能够容纳任何类型的对象。
size(), isEmpty(), get(), set()方法均能在常数时间内完成,add()方法的时间开销跟插入位置有关,addAll()方法的时间开销跟添加元素的个数成正比。其余方法大都是线性时间。
为追求效率,ArrayList没有实现同步(synchronized),如果需要多个线程并发访问,用户可以手动同步,也可使用Vector替代。
- 会自动扩容的数组,线程不安全
- 查询快,增删慢
2. 底层实现
- Object数组实现,存入元素时会丢失类型
// 默认容量
private static final int DEFAULT_CAPACITY = 10;
// 底层存储数组
transient Object[] elementData;
- 新创建的ArrayList,若在创建时没有指定初始容量,则默认初始容量为0,当添加第一个元素时,扩展容量为默认容量10 ( ArrayList的初始容量现在为0,不再是10了)
- 当容量不为0时,若不够用,则扩展为原长度的1.5倍
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); // 右移相当于除以2,整体相当于扩容1.5倍
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);
}
3. 常用方法介绍
3.1 set()
- 作用:设置给定下标处的值,并返回坐标处的旧值
- 入参:下标,值
- 返回值:给定坐标处的旧值
public E set(int index, E element) {
rangeCheck(index);//下标越界检查
E oldValue = elementData(index);
elementData[index] = element;//赋值到指定位置,复制的仅仅是引用
return oldValue;
}
注意:入参中的下标必须小于数组长度,否则会报 IndexOutOfBoundsException
3.2 get()
- 作用:获取特定下标处的值
- 入参:下标
- 返回值:给定坐标处的值(由于底层数组是Object[],得到元素后需要进行类型转换)
public E get(int index) {
rangeCheck(index);
return (E) elementData[index];//注意类型转换
}
3.3 add()
- 作用:往尾部(特定下标处)添加元素
- 入参:(下标)元素
- 返回值:true
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 空间检查
elementData[size++] = e;
return true;
}
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++;
}

3.4 addAll()
addAll() 方法能够一次添加多个元素,根据位置不同也有两个版本,一个是在末尾添加的 addAll(Collection<? extends E> c)方法,一个是从指定位置开始插入的 addAll(int index, Collection<? extends E> c) 方法。跟 add() 方法类似,在插入之前也需要进行空间检查,如果需要则自动扩容;如果从指定位置插入,也会存在移动元素的情况。 addAll() 的时间复杂度不仅跟插入元素的多少有关,也跟插入的位置相关。
3.5 remove()
remove() 方法也有两个版本,一个是 remove(int index) 删除指定位置的元素,另一个是 remove(Object o) 删除第一个满足o.equals(elementData[index]) 的元素。删除操作是 add() 操作的逆过程,需要将删除点之后的元素向前移动一个位置。需要注意的是为了让GC起作用,必须显式的为最后一个位置赋null值。
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; //清除该位置的引用,让GC起作用
return oldValue;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
注意:如果ArrayList中的元素类型时Integer,则用remove方法参数还是被解释成下标!

4. 遍历方法
4.1 Iterator方法遍历
for(Iterator it = list.iterator();it.hasNext();){
System.out.println(it.next());
}
遍历时不能使用list的删除方法,否则会报并发修改异常,但是可以使用迭代器的删除方法
4.2 for循环遍历
for(int i = 0;i < list.size(); i ++){
System.out.println(list.get(i));
}
遍历时可以修改list
4.3 增强for循环遍历
for(String tmp:list){
System.out.println(tmp);
}
底层基于迭代器实现,遍历时不能使用list的删除方法,否则会报并发修改异常,但是可以使用迭代器的删除方法
5. 类比LinkedList、Vector
List主要有ArrayList、LinkedList与Vector几种实现。
这三者都实现了List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。
ArrayList 是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组.
LinkedList 是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.
当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义.
Vector 和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。
Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.
而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包括 offer(),peek(),poll()等.
注意: 默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。

本文详细介绍了Java中的ArrayList,它是一个基于数组实现的顺序容器,允许存储null元素且非线程安全。ArrayList的底层通过Object数组存储元素,初始容量为0,添加元素时会自动扩容。文章讲解了set()、get()、add()、addAll()和remove()等常用方法的实现原理,并对比了ArrayList与LinkedList、Vector的区别。此外,还讨论了ArrayList的遍历方式,包括Iterator、for循环和增强for循环。最后,提到了在多线程环境下应考虑使用Vector或手动同步ArrayList。
7195

被折叠的 条评论
为什么被折叠?



