ArrayList是实现List接口的动态数组。因为它是基于数组实现的,主要有如下特点:
1、插入、删除比较慢,因为插入、删除需要移动数据位置。
2、可以重复插入数据、可以插入null。
3、查找比较快,可以直接使用下标。
ArrayList的底层实现是不同步,多线程操作会出现问题,这一点要注意。
1.add(E e):向集合中添加一个元素。
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
2.add(int index, E element):向集合index位置处添加一个元素。
ArrayList arrayList = new ArrayList();
arrayList.add(2, "张三");
3.addAll(Collection extends E> c):将某集合全部添加到另外一集合。
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
ArrayList allArrayList = new ArrayList();
allArrayList.addAll(arrayList);
4.addAll(int index, Collection extends E> c):将某集合全部添加到另外一集合的index位置处。
ArrayList arrayList=new ArrayList();
arrayList.add("张三");
ArrayList allarrayList=new ArrayList();
allarrayList.addAll(2,arrayList);
5.set(int index, E element):修改index位置对应的Object。
ArrayList arrayList = new ArrayList();
arrayList.set(0, "李四");
6.remove(int index):移除列表中指定位置上的元素。
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.remove(0);
7.remove(Object o):移除列表中指定元素。
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.remove("张三");
8.removeAll():清空集合
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.removeAll(arrayList);
9.get(int index)方法:获取index位置对应的Object
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
String s = arrayList.get(0);
10.subList(int fromIndex,int toIndex)方法:截取集合[fromIndex,toIndex)
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");
List list = arrayList.subList(1, 3);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Log.d("TAG", iterator.next() + "");
}
D/TAG: 李四
D/TAG: 王五
11.size()方法:集合长度
ArrayList arrayList = new ArrayList();
int num = arrayList.size();
12.isEmpty():集合是否为空
ArrayList arrayList = new ArrayList();
boolean b = arrayList.isEmpty();
13.contains(Object o):集合是否包含o
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");
boolean b = arrayList.contains("王五");
boolean c = arrayList.contains("222");
Log.d("TAG", "b----:" + b);
Log.d("TAG", "c----:" + c);
D/TAG: b----:true
D/TAG: c----:false
14.indexOf(Object o):集合中第一次出现o的位置
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("张三");
arrayList.add("刘六");
arrayList.add("王二麻子");
arrayList.add("张三");
int a = arrayList.indexOf("张三");
int b = arrayList.indexOf("刘六");
int c = arrayList.indexOf("222");
Log.d("TAG", "a----:" + a);
Log.d("TAG", "b----:" + b);
Log.d("TAG", "c----:" + c);
D/TAG: a----:0
D/TAG: b----:4
D/TAG: c----:-1
15.lastIndexOf(Object o):集合中最后一次出现o的位置
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("张三");
arrayList.add("刘六");
arrayList.add("王二麻子");
arrayList.add("张三");
int d = arrayList.lastIndexOf("张三");
int e = arrayList.lastIndexOf("刘六");
int f = arrayList.lastIndexOf("222");
Log.d("TAG", "d----:" + d);
Log.d("TAG", "e----:" + e);
Log.d("TAG", "f----:" + f);
D/TAG: d----:6
D/TAG: e----:4
D/TAG: f----:-1
我们从ArrayList的构造方法开始我们的源码之旅。
1.无参构造方法
使用
ArrayList arrayList = new ArrayList();
源码
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
2.指定大小的构造方法
使用
ArrayList arrayList = new ArrayList(10);
源码
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);
}
}
ArrayList最常用的两个构造方法就是上述的两个构造方法。
<1> 如果创建ArrayList时,构造方法中不指定ArrayList的大小。系统会默认创建一个默认的Object类型的数组(空的数组)。
transient Object[] elementData;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
也就是将elementData数组变成 默认空的数组。
<2> 如果创建ArrayList时,构造方法中指定了ArrayList的大小。则判断大小做不同的处理。
(1) 指定大小大于0
this.elementData = new Object[initialCapacity];
也就是将elementData数组 变成 指定大小长度的数组。
(2) 指定大小等于0
this.elementData = EMPTY_ELEMENTDATA;
private static final Object[] EMPTY_ELEMENTDATA = {};
也就是将elementData数组 变成 空的数组 即此时和无参构造方法创建ArrayList一样。
(3) 指定大小小于0
throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
抛出异常。
举例
ArrayList arrayList = new ArrayList();
//直接添加元素 该元素会被添加到ArrayList的最后一个
arrayList.add("张三");
源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
第一行代码执行ensureCapacityInternal()方法。
ensureCapacityInternal方法源码
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
该方法传参size+1
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
size:看注释可知,他是ArrayList的实际大小 即ArrayList中有多少元素。
如果,elementData是默认的Object类型的数组。则重新计算大小。那么elementData什么时候是默认的Object类型的数组呢?由构造方法源码可知,就是创建ArrayList时,使用无参构造方法创建ArrayList的时候赋值的。也就是说,如果使用ArrayList的无参构造方法创建ArrayList时。第一次add元素。会计算一下数组大小。
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;
这,也可以看出ArrayList默认创建的大小是10。
取传参的数值(size+1)(第一次添加时size+1=1) 和ArrayList的默认大小10。取两者最大的值。第一次肯定是10。然后执行ensureExplicitCapacity(minCapacity);方法。
ensureExplicitCapacity(minCapacity);方法源码
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
该方法中,传进来minCapacity是10。因为第一次添加元素,所以elementData是空的。所以显然if语句是成立的。所以会执行grow()方法。
grow()方法源码
private void grow(int minCapacity) {
// overflow-conscious code
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);
}
该方法中。
老的数组长度定义为elementData.length; 第一次即为0。
新的数组长度初始化为 0+(0>>1) 还是0。
0-minCapacity(传进来minCapacity是10)=-10 小于0 将新的数组长度赋值传进来的minCapacity(传进来minCapacity是10)。
而 MAX_ARRAY_SIZE
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
所以,此分支一般不会走到。
然后将elementData数组使用Arrays类的copyOf方法拷贝一份新的数组。这就是ArrayList的扩容。
最后执行
elementData[size++] = e;
即,将传进来的元素插到最后一个位置。
然后再次add方法添加元素时。
ensureCapacityInternal方法中
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
这个条件不成立。
ensureExplicitCapacity方法中
if (minCapacity - elementData.length > 0)
grow(minCapacity);
条件也不成立,所以就不会执行grow()方法 扩容了。
<1> 使用ArrayList的无参构造方法创建ArrayList对象。由于没有指定数组大小。所以默认创建了一个空的数组。
<2> 第一次add方法添加元素时,会最后执行到grow()方法来扩容。ArrayList默认数组大小是10。
<3> 当 minCapacity - elementData.length > 0 条件成立时,会执行扩容方法。
举例
ArrayList arrayList = new ArrayList();
arrayList.add(1, "李四");
源码
public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
同理,如果传进来的位置比当前数组长度还大,或者传进来的位置小于0。则抛出异常。
否者执行ensureCapacityInternal(size + 1);方法。和add()方法一样。这里就不再赘述了。
然后执行
System.arraycopy(elementData, index, elementData, index + 1,size - index);
即。向index位置插入一个元素,需要将该元素后面的所有元素都copy一遍。
最后将传进来的元素放到指定index的位置。
<1> add方法向指定位置插入元素,会使用System类的arraycopy方法。将index位置后面的元素全部copy一遍。
<2> 这样也能侧面证实ArrayList的插入元素。相对比较耗时。因为他需要操作该位置后面全部的元素。
举例
ArrayList arrayList = new ArrayList();
//删除下标2对应的元素
arrayList.remove(2);
源码
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices).
*
* @param index the index of the element to be removed
* @return the element that was removed from the list
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
if (index >= size)
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
modCount++;
E oldValue = (E) 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;
}
首先,判断传进来的index是否合法。
然后拿到index位置对应的元素。
计算需要移动的元素个数
int numMoved = size - index - 1;
也就是说。如果数组长度为20。删除的位置是10。那么需要移动的元素个数是20-10-1=9。其实也好理解。就是删除了index=10的元素。那么index=10后面的元素要全部前移一位。
如果计算的需要移动的元素大于0。也就是说有需要移动的元素。那么需要调用System.arraycopy方法。将index后面的元素全部拷贝一份。
最后执行
elementData[--size] = null; // clear to let GC do its work
因为已经重新拷贝,所以需要把原数组中的元素置空。
最后返回删除的元素
return oldValue;
<1> 删除指定位置的元素时,需要校验index位置是否合法,以免造成空指针。
<2> 删除指定位置的元素时,该位置后的全部元素都需要前移一位。这也侧面证实了ArrayList删除数据时比较耗时。因为要移动index后面全部的数据。
<3> 删除指定位置的元素时,会返回指定位置的元素值。
举例
ArrayList arrayList = new ArrayList();
//删除数组中“张三”
arrayList.remove("张三");
源码
/**
* Removes the first occurrence of the specified element from this list,
* if it is present. If the list does not contain the element, it is
* unchanged. More formally, removes the element with the lowest index
* i such that
* (o==null ? get(i)==null : o.equals(get(i)))
* (if such an element exists). Returns true if this list
* contained the specified element (or equivalently, if this list
* changed as a result of the call).
*
* @param o element to be removed from this list, if present
* @return true if this list contained the specified element
*/
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;
}
首先 判断需要删除的元素是否是null。
如果是空,则系统认为你要删除的元素是空元素。For循环中找出第一个空的元素的位置。执行fastRemove方法。
如果非空,则系统认为你要删除的元素是传参的元素。For循环中找出第一个传参的元素的位置。执行fastRemove方法。
fastRemove方法源码
/*
* Private remove method that skips bounds checking and does not
* return the value removed.
*/
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
}
该方法和删除指定位置的源码类似。这里就不再赘述了。
<1> 删除指定元素时,系统会找到数组中第一个出现指定元素的位置。然后按照删除指定位置的元素方法操作。
<2> 删除指定元素时,是删除的第一个元素。如果数组中有多个指定的元素。则其他的没有影响,只会删除第一个。因为代码中For循环找到一个指定元素位置后,就直接return了。
<3> 删除指定元素时,方法会有布尔类型的返回值。返回true表示删除成功,返回false表示删除失败。
ArrayList的删除除了上述讲解的remove(int index)和remove(Object o)外。还有一个迭代器的删除方法Iterator.remove()。
上述已经讲过,ArrayList删除方法有三个。remove(int index) 和remove(Object o)都是ArrayList的方法。源码分析可知,两个方法删除时,后面的元素都会重新操作前移一位。
代码
package com.example.test;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initArrayList();
}
public void initArrayList() {
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");
/**
* 删除“王五”后立刻取出“刘六”
* */
//方法1.remove(int index)
arrayList.remove(2);
String result = arrayList.get(3);
Log.i("TAG", "3位置上对应的结果----:" + result);
Log.i("TAG", "删除“王五”后立刻取出“刘六”遍历集合");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Log.i("TAG", iterator.next() + "");
}
}
}
结果
I/TAG: 3位置上对应的结果----:王二麻子
I/TAG: 删除“王五”后立刻取出“刘六”遍历集合
I/TAG: 张三
I/TAG: 李四
I/TAG: 刘六
I/TAG: 王二麻子
说明
remove(int inedex)方法删除集合元素,被删除的元素后面的元素全部前移。
代码
package com.example.test;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initArrayList();
}
public void initArrayList() {
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");
/**
* 删除“王五”后立刻取出“刘六”
* */
//方法2.remove(Object o)
arrayList.remove("王五");
String result = arrayList.get(3);
Log.i("TAG", "3位置上对应的结果----:" + result);
Log.i("TAG", "删除“王五”后立刻取出“刘六”遍历集合");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Log.i("TAG", iterator.next() + "");
}
}
}
结果
I/TAG: 3位置上对应的结果----:王二麻子
I/TAG: 删除“王五”后立刻取出“刘六”遍历集合
I/TAG: 张三
I/TAG: 李四
I/TAG: 刘六
I/TAG: 王二麻子
说明
remove(Object o)方法删除集合元素,被删除的元素后面的元素全部前移。
代码
package com.example.test;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initArrayList();
}
public void initArrayList() {
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");
/**
* 删除“王五”后立刻取出“刘六”
* */
//方法3.Iterator.remove()
Iterator iter = arrayList.iterator();
while (iter.hasNext()) {
if (iter.next().equals("王五")) {
iter.remove();
}
}
String result = arrayList.get(3);
Log.i("TAG", "3位置上对应的结果----:" + result);
Log.i("TAG", "删除“王五”后立刻取出“刘六”遍历集合");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Log.i("TAG", iterator.next() + "");
}
}
}
结果
I/TAG: 3位置上对应的结果----:王二麻子
I/TAG: 删除“王五”后立刻取出“刘六”遍历集合
I/TAG: 张三
I/TAG: 李四
I/TAG: 刘六
I/TAG: 王二麻子
说明
集合的删除 一般情况下使用remove(int inedex),remove(Object o)或是Iterator.remove()方法都可以。(比如上述删除某个已知的元素(要删除的元素位置和值都确定且只删除一个))。
然而要遍历集合删除循环删除时建议使用迭代器的删除即Iterator.remove()方法删除。
举例
代码
public class CollectionActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_collection);
initArrayList();
}
public void initArrayList() {
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");
int num = arrayList.size();
for (int i = 0; i < num; i++) {
String s = arrayList.get(i);
if ("李四".equals(s)) {
arrayList.remove("李四");
}
}
}
}
结果报错(数组下标越界)
因为集合中删除了一个元素,集合长度变小一个。而For循环时,数组的长度已经计算好。
解决方法
<1> For循环条件时,数组长度随时计算
for (int i = 0; i < arrayList.size(); i++)
<2>使用迭代器的删除方法
修改代码
public class CollectionActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_collection);
initArrayList();
}
public void initArrayList() {
ArrayList arrayList = new ArrayList();
arrayList.add("张三");
arrayList.add("李四");
arrayList.add("王五");
arrayList.add("刘六");
arrayList.add("王二麻子");
Iterator iter = arrayList.iterator();
while (iter.hasNext()) {
if ("李四".equals(iter.next())) {
iter.remove();
}
}
Log.i("TAG", "*********************遍历集合***************************");
Iterator iterator = arrayList.iterator();
while (iterator.hasNext()) {
Log.i("TAG", iterator.next() + "");
}
}
}
结果
I/TAG: *********************遍历集合***************************
I/TAG: 张三
I/TAG: 王五
I/TAG: 刘六
I/TAG: 王二麻子
正确
由源码可知,ArrayList是基于数组实现的集合。
添加和删除元素时,都需要将添加或者删除元素位置后面的元素全部使用System.arraycopy方法copy移动一遍。比较耗时。
但是由于基于数组实现,所以获取指定位置的元素时,比较快。因为可以直接使用下标找到指定位置上的元素。
所以。
如果使用集合时,更多的是通过下标找指定元素。那么建议使用ArrayList。
如果使用集合时,需要频繁的删除或者插入元素。那么建议使用LinkedList。
LinkedList详解:https://blog.csdn.net/weixin_37730482/article/details/73810685
附:ArrayList官方文档
https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html