列表
ArrayList
ArrayList 是 Java 集合框架中的一部分,实现了 List 接口。它提供了一个动态数组,可以自动调整大小以容纳更多的元素。ArrayList 是基于数组实现的,因此它提供了快速的随机访问性能,但在插入和删除操作上可能不如链表结构高效(特别是当这些操作发生在列表中间时)。
ArrayList 的详细介绍:
- 概述
定义:ArrayList 是一个可以动态增长和缩小的数组实现,允许存储任意数量的对象。
特点:
线程不安全:ArrayList 不是同步的,如果多个线程同时修改同一个 ArrayList 实例,则必须显式地进行同步。
允许重复元素:与 Set 不同,ArrayList 允许存储重复的元素。
允许空值:支持存储 null 值,甚至可以存储多个 null。 - 内部工作原理
ArrayList 内部使用一个对象数组来存储元素。当添加新元素时,如果当前数组容量不足以容纳新元素,ArrayList 会创建一个新的、更大的数组,并将所有现有元素复制到新的数组中。默认情况下,每次扩容大约增加原来容量的一半左右(具体取决于 JVM 实现)。
java
private transient Object[] elementData;
private int size;
基本用法
java
public boolean add(E e) //添加元素到末尾
public boolean isEmpty() //判断是否为空
public int size() //获取长度
public E get(int index) //访问指定位置的元素
public int indexOf(Object o) //查找元素, 如果找到,返回索引位置,否则返回-1
public int lastIndexOf(Object o) //从后往前找
public boolean contains(Object o) //是否包含指定元素,依据是equals方法的返回值
public E remove(int index) //删除指定位置的元素, 返回值为被删对象
//删除指定对象,只删除第一个相同的对象,返回值表示是否删除了元素
//如果o为null,则删除值为null的元素
public boolean remove(Object o)
public void clear() //删除所有元素
//在指定位置插入元素,index为0表示插入最前面,index为ArrayList的长度表示插到最后面
public void add(int index, E element)
public E set(int index, E element) //修改指定位置的元素内容
add 方法
java
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
java
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
add的方法比较简单,里面有一个modCount代表修改次数,首先modCount++;然后调用add(E e, Object[] elementData, int s)这个方法;这个方法首先判断有没有到数组边界,如果到了数组边界,说明要进行扩容了;就调用grow()方法;如果没有到边界就直接在数组后面增加一个新的元素。
remove方法
java
public E remove(int index) {
Objects.checkIndex(index, size);
final Object[] es = elementData;
@SuppressWarnings("unchecked") E oldValue = (E) es[index];
fastRemove(es, index);
return oldValue;
}
java
private void fastRemove(Object[] es, int i) {
modCount++;
final int newSize;
if ((newSize = size - 1) > i)
System.arraycopy(es, i + 1, es, i, newSize - i);
es[size = newSize] = null;
}
因为arraylist的底层使用的数组存储的元素,所以,当我们做删除动作的时候,实际上我们不光要删除那个位置上的元素,还要把删除位置的后续元素整体往前移动一位。
迭代
java
ArrayList<Integer> list = new ArrayList<>();
list.add(0);
list.add(1);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
for (Integer integer : list) {
System.out.println(integer);
}
我代码里面给出了两种arraylist的迭代方式,一种是简单的for循环遍历,一种是foreach循环。不过,foreach看上去更为简洁,而且它普遍适用于各种容器。这种foreach语法背后是怎么实现的呢?其实,编译器会将它转换为类似如下代码:
java
Iterator<Integer> it = intList.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
要想实现foreach循环,就必须要是实现Iterator接口
java
public interface Iterator<E> {
boolean hasNext();
E next();
void remove();
}
使用foreach循环中新手可能会遇见 ConcurrentModificationException可以看一下我写的这篇文章。
ArrayList还实现了ListIterator接口,这个接口在原有的Iterator的基础上扩展了一些能力。
java
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious();
E previous();
int nextIndex();
int previousIndex();
void remove();
void set(E e);
void add(E e);
}
** 迭代器的好处**
我们介绍了半天迭代器,其实到目前位置,我们还是不太清楚why? 知道怎么用很好,但是更多的是要知道why,我又想起之前高数的时候(虽然学的一般)一位高数老教师说的,不要记住冰冷的定理,要记住背后火热的思考。所以为什么由迭代器呢?
迭代器实际上是一种通用的遍历的方式,它适用于各种容器类;比如fori循环对于ArrayList是很合适,但是对于Set或者Map就不合适。所以我们搞出来了迭代器。迭代器实际上是一种关注点分离的思想,将数据的实际组织方式与数据的迭代遍历隔离开。这样我们只需要拿到迭代器接口的一个引用,不需要再关注数据的实际组织方式是list还是set还是Map了,就可以用一种统一的方式进行访问。
总结
ArrayList,它的特点是内部采用动态数组实现,这决定了以下几点。
- 可以随机访问,按照索引位置进行访问效率很高,时间复杂度是O(1)
- 如果非排序好的数组,查找的时间复杂度是O(N)
- 添加元素的效率还可以,虽然有时候需要重新分配并且要复制数组,不过平均来看添加N个元素的效率为O(N)。
- 插入和删除元素的效率比较低,因为需要移动元素,时间复杂度O(N)。