一、ArrayList的作用及优缺点
ArrayList是通过数组作为存储为实现的列表,它是一种顺序存储结构
- 优点
- 通过位置(索引)查找数据较快
- 缺点
- 插入数据需要移动插入位置后面所有的元素
- 删除数据需要移动删除位置后面所有的元素
二、ArrayList类与其他数据结构之间的关系
1.ArrayList与其他集合类型的关系
2.ArrayList与具体的集合类型的关联
- JAVA数据结构大致可分为两大类:Collection(集合)和Map,Collection属于线性结构,而ArrayList作为一种List,也属于Collection
- Iterable接口:可迭代,主要要实现一个类型为Iterator的迭代器方法iterator
三、ArrayList的使用及具体实现
1.ArrayList的数据项
- size : 数组列表的元素个数
- elementData : 数组元素的存储的集合(是一个对象数组)
transient Object[] elementData;
private int size;
2.ArrayList的初始化
(1)使用
- new ArrayList()
- 初始化一个容量为0的数组列表
- new ArrayList(int capacity);
- 初始化一个容量为capacity的数组列表(后期可通过add扩容)
- new ArrayList(Collection
c) - 指定初始化内容进行初始化
//1.不指定初始化容量
List<Integer> intList = new ArrayList<Integer>();
intList.add(2);
intList.add(8);
intList.add(9);
System.out.println(intList); //[2, 8, 9]
//2.指定初始化容量
List<String> list = new ArrayList<String>(5);
list.add("A");
list.add("B");
list.add("C");
System.out.println(list); //[A, B, C]
//3.通过另外一个ArrayList作为元素集合来实例化一个新的ArrayList
List<String> books = new ArrayList<String>();
books.add("大话数据结构");
books.add("算法");
books.add("数据结构与算法分析");
List<String> bookNameList = new ArrayList<String>(books);
System.out.println(bookNameList); //[大话数据结构, 算法, 数据结构与算法分析]
//4.通过另外一个HashSet作为元素集合来实例化一个新的ArrayList;
Set<String> languages = new HashSet<>(3);
languages.add("JAVA");
languages.add("Javascript");
languages.add("PHP");
languages.add("GoLang");
languages.add("Python");
languages.add("Lua");
List<String> langList = new ArrayList<>(languages);
System.out.println(langList); //[JAVA, Javascript, PHP, Lua, GoLang, Python]
(2)实现原理
- (1)指定数据容量:则初始化一个包含容量的对象数组用来存储元素集合
public ArrayList(int initialCapacity) {
//初始化容量大于0,则申请一个容量为capacity的数组来存储列表元素
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//如果容量等于0,则申请一个空数组来存储元素列表
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
- (2)未指定数据容量:则初始化一个空的对象数组来存储元素集合
public ArrayList() {
//不指定初始化容量时,以默认的空数组来存储
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
- (3)通过集合对象初始化:通过一个集合对象来初始化,将集合的数据转化为数组
public ArrayList(Collection<? extends E> c) {
//1.将集合元素转化为数组对象
elementData = c.toArray();
if ((size = elementData.length) != 0) {
//2.如果集合元素的个数不为0,而且集合元素不为对象数组(因为默认的就是对象数组来存储)
if (elementData.getClass() != Object[].class)
//通过复制的方式将集合的元素值存入到新的数组中
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//申请空的数组来存储元素
this.elementData = EMPTY_ELEMENTDATA;
}
}
3.ArrayList实现的迭代器接口
(1)迭代器方法iterator的用法
List<String> list = new ArrayList<String>(5);
list.add("A");
list.add("B");
list.add("C");
System.out.println(list); //[A, B, C]
//通过iterator可以获取元素迭代器,通过迭代器,可以遍历列表元素
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()) {
//调用iterator.next方法不仅可以获取下一个元素的值,而且还将迭代器指向下一个元素
String val = iterator.next();
System.out.println(val); //A,B,C
}
(2)实现hasNext方法
public boolean hasNext() {
return cursor != size;
}
- cursor表示当前返回元素下一个元素的索引值
- 当cursor等于size时,表示已经到表尾,所以只要没到表尾,就还有下一个元素
(3)实现next方法
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
- lastRet : 表示上次返回的索引值
cursor = i + 1
表示每迭代一次,往后移动一位- elementData[lastRet = i]相当于两步:
- lastRet = i,假如i=0表示第1次迭代
- elementData[lastRet],返回索引为lastRet的值
(4)实现remove方法
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
- 调用ArrayList的remove方法,根据索引值删除元素
4.ArrayList实现Collection接口
(1)实现toArray方法
toArray方法的使用:
Set<String> languages = new HashSet<>(3);
languages.add("JAVA");
languages.add("Javascript");
languages.add("PHP");
languages.add("GoLang");
languages.add("Python");
languages.add("Lua");
String[] langArr = new String[langList.size()];
langList.toArray(langArr);
for (int i = 0; i< langArr.length; i++) {
System.out.println(langArr[i]);
}
toArray的实现原理:
public <T> T[] toArray(T[] a) {
//1.如果用来存储元素值的数组长度不够,则直接返回一个新的数组(将原存储数据集合的值复制到新的数组中)
if (a.length < size)
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
//2.将存储的元素的值复制到返回的a数组中
System.arraycopy(elementData, 0, a, 0, size);
//3.如果a的长度超过原数组长度,则让a数组的size索引对应的值设置为null
if (a.length > size)
a[size] = null;
return a;
}
- Arrays.copyof参数说明
- 第1个参数:srcArray 源数组,被复制的数组
- 第2个参数:newLength 复制存储的数组的长度(被复制的长度)
- 第3个参数:newType 新数组的元素类型
- System.arraycopy参数说明
- 第1个参数:srcArray 源数组(被复制的数组),是一个数组对象
- 第2个参数:srcPos 源数组的起始索引,即从哪个位置开始复制
- 第3个参数:dstArray 复制的目的数组,即复制存储到哪个数组元素中
- 第4个参数:dstPos 目标数组的起始索引,复制的存储的起始位置
- 第5个参数:size 被复制的长度
(2)实现add方法
add方法的使用:
- add(T value):在列表末尾添加一个元素
- add(int index, T value):在指定位置(index)插入一个元素
List<Integer> scores = new ArrayList<>(5);
scores.add(59);
scores.add(65);
scores.add(78);
scores.add(98);
scores.add(3, 88);
System.out.println(scores); //[59, 65, 78, 88, 98]
add方法的实现解读:
- 不指定索引位置,添加元素,即在数组末尾添加元素
public boolean add(E e) {
//1.检查是否需要扩容,如果需要扩容,则扩容
ensureCapacityInternal(size + 1);
//2.存储插入的值,并增加元素个数
elementData[size++] = e;
return true;
}
- 指定索引位置,添加元素,在指定位置插入元素
public void add(int index, E element) {
//1.检查插入的索引值是否合法
rangeCheckForAdd(index);
//2.检查是否需要扩容,如果需要,则扩容
ensureCapacityInternal(size + 1);
//3.通过复制的方式将数组元素依次往后移动
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
//4.设置插入的值
elementData[index] = element;
//5.增加元素个数
size++;
}
(3)实现remove方法
remove方法使用
- remove(int index) : 通过索引值删除
- remove(Object obj) : 通过对象删除
List<Integer> scores = new ArrayList<>(5);
scores.add(59);
scores.add(65);
scores.add(78);
scores.add(98);
scores.add(3, 88);
System.out.println(scores); //[59, 65, 78, 88, 98]
scores.remove((Object)78); //这里将78强制转换为Object,是因为如果直接使用78,会被当成是索引值
System.out.println(scores); //[59, 65, 88, 98]
scores.remove(3);
System.out.println(scores); //[59, 65, 88]
remove方法实现解读
通过索引值删除
public E remove(int index) {
//1.检查索引值的合法性
rangeCheck(index);
//增加修改次数
modCount++;
//2.通过索引获取要删除的元素值
E oldValue = elementData(index);
//3.计算出需要移动的元素个数
int numMoved = size - index - 1;
if (numMoved > 0)
//4.通过复制的方式将元素向前移动
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//5.将最后一位的元素值设置为null,而且将元素个数减1
elementData[--size] = null; // clear to let GC do its work
//返回被删除的对象的值
return oldValue;
}
- 通过元素对象删除
public boolean remove(Object o) {
//通过元素对象找到元素对应的索引值,再通过索引值来删除元素
//1.如果要删除的对象是null
if (o == null) {
//遍历整个数组
for (int index = 0; index < size; index++)
//如果某个元素的值是null,则删除,并且退出循环返回
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;
}
注:fastRemove的实现:
private void fastRemove(int index) {
//增加修改次数
modCount++;
//1.计算出要移动的元素的个数
int numMoved = size - index - 1;
if (numMoved > 0)
//2.通过复制的方式来删除元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//3.最后一个元素的值为null,并且减少元素个数
elementData[--size] = null; // clear to let GC do its work
}
(4)实现addAll方法
addAll的使用:
- addAll(Collection c) : 在列表末尾批量添加元素
- addAll(int index, Collection c) : 在指定位置批量插入元素
List<String> charList = new ArrayList<String>(5);
charList.add("A");
charList.add("B");
List<String> addition = new ArrayList<>(5);
addition.add("C");
addition.add("D");
addition.add("E");
charList.addAll(addition);
System.out.println(charList); //[A, B, C, D, E]
Set<String> charSet = new HashSet<String>(2);
charSet.add("B1");
charSet.add("B2");
charSet.add("B3");
charList.addAll(2, charSet);
System.out.println(charList); //[A, B, B2, B3, B1, C, D, E]
addAll的实现解读:
- 不指定位置批量添加元素
public boolean addAll(Collection<? extends E> c) {
//1.将集合对象转化为数组对象
Object[] a = c.toArray();
//2.获取要添加的元素个数
int numNew = a.length;
//3.存储扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//3.通过复制的方式将插入的数组元素复制到原数组的末尾
System.arraycopy(a, 0, elementData, size, numNew);
//4.增加列表的个数
size += numNew;
return numNew != 0;
}
- 指定位置批量添加元素
public boolean addAll(int index, Collection<? extends E> c) {
//1.检查索引值
rangeCheckForAdd(index);
//2.将插入的集合对象转化为数组对象
Object[] a = c.toArray();
//3.获取新增元素的个数
int numNew = a.length;
//4.存储扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//5.计算出要移动的元素个数
int numMoved = size - index;
if (numMoved > 0)
//6.通过数组复制的方式将要移动的元素移动到相应的空间上
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
//7.通过数组复制的方式将新插入的元素插入到数组中
System.arraycopy(a, 0, elementData, index, numNew);
//8.增加数组元素的个数
size += numNew;
return numNew != 0;
}
(5)实现removeAll方法
removeAll用法
- removeAll(Collection c) : 批量删除ArrayList在指定集合中元素
List<String> charList = new ArrayList<String>(5);
charList.add("A");
charList.add("B");
charList.add("C");
charList.add("D");
charList.add("E");
System.out.println(charList); //[A, B, C, D, E]
Set<String> charSet = new HashSet<String>(2);
charSet.add("B1");
charSet.add("B2");
charSet.add("B3");
charList.addAll(2, charSet);
System.out.println(charList); //[A, B, B2, B3, B1, C, D, E]
charList.removeAll(charSet);
System.out.println(charList); //[A, B, C, D, E]
removeAll实现解读
public boolean removeAll(Collection<?> c) {
//1.检测集合元素是否为null
Objects.requireNonNull(c);
//调用批量删除的方法
return batchRemove(c, false);
}
注:batchRemove的实现
private boolean batchRemove(Collection<?> c, boolean complement) {
final Object[] elementData = this.elementData;
int r = 0, w = 0;
boolean modified = false;
try {
for (; r < size; r++)
//1.过滤出那些不在删除数组中的元素(即保存的元素个数)
if (c.contains(elementData[r]) == complement)
elementData[w++] = elementData[r];
} finally {
//2.当过滤保存元素操作时出现异常时
if (r != size) {
//将出现异常后面的元素放入到数组中
System.arraycopy(elementData, r,
elementData, w,
size - r);
w += size - r;
}
//3.当有要删除的元素时(w是保存的元素集合的索引)
if (w != size) {
//保留元素之后的所有元素都是要删除的元素
for (int i = w; i < size; i++)
elementData[i] = null;
modCount += size - w;
//4.重新设置列表的长度
size = w;
modified = true;
}
}
return modified;
}
(6)实现clear方法
clear方法用法
- clear():清除列表中的元素,使列表成为一个空列表
List<String> charList = new ArrayList<String>(5);
charList.add("A");
charList.add("B");
charList.clear();
System.out.println(charList.isEmpty()); //true
clear方法实现解读
public void clear() {
//1.增加修改次数
modCount++;
//2.将所有元素的值设置为null
for (int i = 0; i < size; i++)
elementData[i] = null;
//将数组列表的元素个数设置为0
size = 0;
}
- 将所有元素的值设置为null
- 将数组列表的元素个数设置为0
5.ArrayList实现List接口
(1)实现set方法
set方法的使用
- set(int index, E element):设置某个索引的值为某个对象
List<String> list = new ArrayList<String>(5);
list.add("A");
list.add("B");
System.out.println(list); //[A, B, C]
list.set(1, "D");
System.out.println(list.get(1)); //D
System.out.println(list); //[A, D, C]
set方法的实现解读
public E set(int index, E element) {
//1.检查索引值
rangeCheck(index);
//2.获取该索引的对应的旧元素的值
E oldValue = elementData(index);
//3.通过数组的索引设置新的值
elementData[index] = element;
return oldValue;
}
(2)实现get方法
get方法的使用
- get(int index):获取某个索引的对应的元素
List<String> list = new ArrayList<String>(5);
list.add("A");
list.add("B");
System.out.println(list); //[A, B, C]
System.out.println(list.get(1)); //B
get方法的实现解读
public E get(int index) {
//1.检查索引
rangeCheck(index);
//2.通过elementData方法获取元素值
return elementData(index);
}
注:ElementData方法
E elementData(int index) {
//通过数组的索引获取其对应的值
return (E) elementData[index];
}
(5)实现subList方法
subList方法的使用
- subList(int fromIndex, int toIndex):获取从frontIndex到toIndex的子列表([fromIndex, toIndex))
List<String> list = new ArrayList<String>(5);
list.add("A");
list.add("B");
list.add("C");
list.add("D");
list.add("E");
list.add("F");
list.add("G");
List<String> subList = list.subList(1, 5);
System.out.println(subList); //[B, C, D, E]
subList方法的实现解读
public List<E> subList(int fromIndex, int toIndex) {
//参数检查
subListRangeCheck(fromIndex, toIndex, size);
return new SubList(this, 0, fromIndex, toIndex);
}
(6)实现indexOf方法
indexOf的使用:
- indexOf(E element):获取指定元素的索引值(第一次出现的)
ArrayList<Integer> numList = new ArrayList<Integer>(5);
numList.add(12);
numList.add(8);
numList.add(18);
numList.add(2);
numList.add(29);
numList.add(18);
System.out.println(numList.indexOf(18)); //2
indexOf的实现解读
public int indexOf(Object o) {
//1.如果元素为null,那么就查找元素值为null的元素
if (o == null) {
//2.依次遍历循环比较,第一个出现后就返回
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//1.如果元素不为null,依次遍历循环比较,第一个出现后就返回
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
//未找到相应元素
return -1;
}
(7)实现lastIndexOf方法
lastIndexOf的使用:
- lastIndexOf(E element):获取指定元素的最后一次出现的索引值
ArrayList<Integer> numList = new ArrayList<Integer>(5);
numList.add(12);
numList.add(8);
numList.add(18);
numList.add(2);
numList.add(29);
numList.add(18);
System.out.println(numList.lastIndexOf(18)); //5
lastIndexOf的实现的解读
public int lastIndexOf(Object o) {
//1.如果元素为null,那么就查找元素值为null的元素
if (o == null) {
//2.从后往前依次比较
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
//1.如果元素不为null,依次(从后往前)遍历循环比较,相等后就返回
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
//未找到相应元素
return -1;
}
(8)其他方法使用
- sort:排序
- 参数说明:它的参数是一个实现Comparator接口的对象
public class NumSort implements Comparator {
public int compare(Object o1, Object o2) {
int num1 = (int) o1;
int num2 = (int) o2;
return num1 - num2;
}
}
ArrayList<Integer> numList = new ArrayList<Integer>(5);
numList.add(12);
numList.add(8);
numList.add(18);
numList.add(2);
numList.add(29);
numList.sort(new NumSort());
System.out.println(numList); //[2, 8, 12, 18, 29]
- clone:数组克隆
ArrayList<Integer> numList = new ArrayList<Integer>(5);
numList.add(12);
numList.add(8);
numList.add(18);
numList.add(2);
numList.add(29);
List<Integer> digitalList = (ArrayList<Integer>) numList.clone();
System.out.println(digitalList); //[12, 8, 18, 2, 29]
- contains:是否包含某个元素对象
List<Integer> numList = new ArrayList<Integer>(5);
numList.add(12);
numList.add(8);
numList.add(18);
numList.add(2);
numList.add(29);
System.out.println(numList.contains(2)); // true
- size : 获取列表的元素个数
List<Integer> numList = new ArrayList<Integer>(5);
numList.add(12);
numList.add(8);
numList.add(18);
numList.add(2);
numList.add(29);
System.out.println(numList.size()); //5
- isEmpty : 数组列表是否为空列表
List<Integer> numList = new ArrayList<Integer>(5);
numList.add(12);
numList.add(8);
numList.add(18);
numList.add(2);
numList.add(29);
System.out.println(numList.isEmpty()); //false