一 简介
在上一章:java-01基础篇-04 Java集合-02-List接口(一);讲解到List, ArrayList,并发修改异常等等;这一章主要讲解LinkedList,Vector两个集合。
LinkedList是一个通过链表的方式实现列表集合(List); 从它的继承结构可以看到它实现Deque双向链表的接口;意味着它拥有双向链表的能力;
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{}
1.1 继承图
二,AbstractSequentialList
在LinkedList的继承当中,并不像ArrayList 是直接就实现AbstractList抽象类;因为我们知道在抽象类AbstractList当中已经定义好List集合需要公共业务和一些公共业务流程;比如CRUD基本业务;修改业务;迭代器业务;分割器业务;相当于给了一个基本骨架;那为什么还需要在下面再定义一个AbstractSequentialList;这个抽象类是干嘛用的?
从JDK17 的官方文档介绍得知;
JDK17 官网DOC: https://docs.oracle.com/en/java/javase/17/docs/api/index.html
由于链表的访问只能是从头到尾或者从尾到头这个顺序一路访问到底;这个AbstractSequentialList抽象类是为了减少从头到尾或者从尾到头顺序访问次数;而建立的一个类;也就是说这个抽象类是为了减少链表顺序访问次数而建立的骨架;具体怎么实现可以继承这个类;实现一个具体可以减少链表访问次数子类;
官网文档还建议如果是需要用到随机访问,推荐实现数组的List也就是ArrayList这一类;
对于减少链表访问次数怎么实现?官方文档是推荐程序员扩展该类的时候提供ListIterator接口和size方法; 对于不可修改的集合;只需要实现hasNext, next,hasPrevious,previous和index方法。
2.1 AbstractSequentialList源码分析
package java.util;
/**
* @author Josh Bloch
* @author Neal Gafter
* @see Collection
* @see List
* @see AbstractList
* @see AbstractCollection
* @since 1.2
*/
public abstract class AbstractSequentialList<E> extends AbstractList<E> {
protected AbstractSequentialList() {}
/** 获取索引指定的元素 */
public E get(int index) {
try {
return listIterator(index).next();
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
/** 为索引设置指定的元素;
* @throws UnsupportedOperationException 如果获取的迭代器不支持set 则抛出UnsupportedOperationException 不支持操作异常
*/
public E set(int index, E element) {
try {
ListIterator<E> e = listIterator(index);
E oldVal = e.next();
e.set(element);
return oldVal;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
/** 为指定索引添加指定的元素;
*
* @throws UnsupportedOperationException 如果获取的迭代器不支持add 则抛出UnsupportedOperationException 不支持操作异常
*/
public void add(int index, E element) {
try {
listIterator(index).add(element);
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
/**
* 删除指定索引的元素,然后后面的元素向左移动,索引减一
* @throws UnsupportedOperationException 如果获取的迭代器不支持 remove 则抛出UnsupportedOperationException 不支持操作异常
*/
public E remove(int index) {
try {
ListIterator<E> e = listIterator(index);
E outCast = e.next();
e.remove();
return outCast;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
/** 批量添加指定的元素集,并且后面的元素也相应的向后移动;索引相应的加元素集的size;
* 批量添加,里面直接for循环调用迭代器的add()方法
* @throws UnsupportedOperationException 如果获取的迭代器不支持 set 则抛出UnsupportedOperationException 不支持操作异常
*/
public boolean addAll(int index, Collection<? extends E> c) {
try {
boolean modified = false;
ListIterator<E> e1 = listIterator(index);
for (E e : c) {
e1.add(e);
modified = true;
}
return modified;
} catch (NoSuchElementException exc) {
throw new IndexOutOfBoundsException("Index: "+index);
}
}
/** 获取迭代器 */
public Iterator<E> iterator() {
return listIterator();
}
/** 获取ListIterator 迭代器*/
public abstract ListIterator<E> listIterator(int index);
}
从上面的源码分析,很简单所谓的通过让链表减少顺序访问次数就是通过迭代器来实现的,不同前提是这个迭代器是ListIterator 迭起器;所以LinkedList 想要减少顺序访问次数必须需要根据自己的链表业务实现一个ListIterator 迭代器;
三 LinkedList
好最终来到本章主角之一,LinkedList 链表集合;首先它既是一个List 列表集合;也是Deque双向队列集合;先讲一讲LinkedList的列表集合; 也就是List这一脉的业务方法;之后小编同学讲到队列集合Queue;再将LinkedList集合的列表业务方法统一讲解
3.1 LinkedList 属性分析
package java.util;
import java.util.function.Consumer;
/**
* @see List
* @see ArrayList
* @since 1.2
* @param <E> the type of elements held in this collection
*/
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
transient int size = 0;
/** 链表头节点*/
transient Node<E> first;
/** 链表尾节点*/
transient Node<E> last;
// ... ...
}
3.2 LinkedList 构造器
/**无参构造,创建一个空链表*/
public LinkedList() {}
/**
* 创建一个元素集指定大小的链表
*/
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
3.3 Node 节点
在LinkedList 链表集合当中,所有的元素都被看成一个节点;链表就是一个节点连接着一个节点而形成的。列队的两种一种是单向队列,一种是双向队列;如下图
在线列表绘画网址:https://visualgo.net/en
得知Node 的基本概念之后,看一看LinkedList 里面的链表的节点是怎么定义的,源码如下:
private static class Node<E> {
E item; // 真正的元素
Node<E> next; // 后置节点
Node<E> prev; // 前置节点
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
真正的元素是什么意思?在使用LinkedList的时候,进行add添加元素的时候;比如添加一个字符串"TOAST" 内容时,会将字符串内容封装成一个Node节点对象;在LinkedList里面是通过一个一个的Node节点类连接形成一个链表;
那怎么拿数据;看源码如下:
这里给小编同学,有点小收获,就是在其内部类的时候,可以按照实际情况来对属性提供对应的getting和setting方法。在平日里,小编同学一般属性都直接getting和setting 一把梭哈到位!如果是我那代码可能就是 node(index).getItem(); 而不是上面源码中 node(index).item; 好像源码简洁一点;
好了节点业务也分析完了,知道了一个节点类里面设置了三个属性: item(真正的元素),next(后置节点),prev(前置节点)三个属性;现在在来回头看一看 LinkedList 属性分析的时候;提到链表头节点,和链表尾节点这两个属性。这两个属性确定了链表的一头一尾;Node节点里面的后置节点和前置节点提供链表之间的连接作用;这样一条链表的就形成了。
3.4 LinkedList 基本CRUD业务
3.3.1 新增链表元素业务
/** 新增元素追加到链表尾部 */
public boolean add(E e) {
linkLast(e);
return true;
}
/** 新增元素追加到指定的链表位置*/
public void add(int index, E element) {
checkPositionIndex(index); // 校验索引是否越界
if (index == size) // 判断指定的索引是否在尾部;在尾部直接从链表尾部追加节点
linkLast(element); // 尾部追加节点业务
else
linkBefore(element, node(index)); // 在头部追加节点
}
在上述源码当中,两个add方法都是借助linkLast(尾部追加)和linkBefore(头部追加)进行元素的添加,也就意味着真正的添加业务在这两个方法里面;
3.3.1.1 尾部追加方法-linkLast(E e)
LinkedList 默认last节点为null;从上图可知
/** 在链表尾部添加 toast*/
void linkLast(E e) {
// 获取当前尾部节点; 如果链表为空,则last为null;
final Node<E> l = last;
// 新元素封装成一个新的节点;并且是在之前的尾部节点成为了newNode的前置节点;
final Node<E> newNode = new Node<>(l, e, null);
// 新节点newNode成为现在追加的尾部节点;
last = newNode;
// l 为null,意味着当前链表为空;那么该节点就是链表头;
if (l == null)
first = newNode;
/**
* 否则就是在尾部的后置节点next设置为新节点newNode;
* 看到这里的同学可能会有点晕,不是在final Node<E> newNode = new Node<>(l, e, null);
* 在封装节点的时候就已经将l 设置新节点(newNode)的前置节点了吗?为什么这里还要设置一次
* 首先Linked是双向链表;上面将元素封装成节点的这一步;实际上是【方向:l <--- newNode】
* 这里是 l.next = newNode; 【方向:l ---> newNode】两个不一样的方向
* 两个方向的指针需要更新;保证链表的正确性
*/
else
l.next = newNode;
size++; // linkedList大小加一
modCount++; // 修改数量加一; 并发修改异常处理
}
3.3.1.2 头部追加方法-linkBefor(E e)
/** 在链表头部添加 taost; 这个和上面的一样,只不过是从头部添加*/
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev; // 当前节点的前置节点
/** 新元素封装成一个Node节点;该节点包含要插入的元素,并且还确定了节点的前置节点和后置节点*/
final Node<E> newNode = new Node<>(pred, e, succ);
// 更新另一个方向的指针的指向;
succ.prev = newNode;
// null;意味着没有元素,则当前元素为第一个元素
if (pred == null)
first = newNode;
else //不为空;意味着有元素,则在prod.next 追加;
pred.next = newNode;
size++;
modCount++;
}
3.3.2 批量新增链表元素
public boolean addAll(Collection<? extends E> c) {
return addAll(size, c);
}
public boolean addAll(int index, Collection<? extends E> c) {
checkPositionIndex(index); // 校验索引是否越界
Object[] a = c.toArray();
int numNew = a.length; // 获取本次添加元素集大小;用于计算集合大小;
if (numNew == 0) return false;
// 第一步:找到链表节点位置;确定prod和succ
Node<E> pred/*前置节点*/, succ /*后置节点*/;
if (index == size) { /*指定的索引在链表的尾部*/
succ = null; // 链表尾部没有后置节点
pred = last; // 本次添加的前置节点就是 last;因为是在尾部追加
} else {/* 当前指定的索引位置不在链表的尾部 */
succ = node(index); // 找到指定索引的节点;该节点设置为后置节点
pred = succ.prev; // succ的前置节点;设置为本次的前置节点;
}
// 第二部:循环将元素集,插入到指定位置的链表当中
for (Object o : a) {
@SuppressWarnings("unchecked") E e = (E) o;
// 要添加的元素,封装成节点;
Node<E> newNode = new Node<>(pred, e, null);
if (pred == null) // 如果本次的前置节点为空,意味着链表为空;在表头追加
first = newNode;
else // 否则,在本次的前置节点的后面追加本次元素;
pred.next = newNode;
pred = newNode; // 并更新本次前置节点
}
// 第三步:更新链表指针,确保链表的正确性
if (succ == null) { // 当前指定的索引位置在链表的尾部时
last = pred; // 循环添加完之后,pred就是尾部节点
} else {
pred.next = succ; 【方向:pred --> succ】
succ.prev = pred; 【方向:pred <-- succ】
}
size += numNew;
modCount++;
return true;
}
3.3.2 删除链表业务业务
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
3.3.3 设置链表元素业务
/**为指定位置的索引设置元素*/
public E set(int index, E element) {
checkElementIndex(index); // 校验索引是否越界
Node<E> x = node(index); // 找到元素指定的节点
E oldVal = x.item; // 获取旧值
x.item = element;// 新增替换节点旧的值
return oldVal; // 返回旧值
}
3.3.4 查询链表元素业务
public E get(int index) {
checkElementIndex(index); // 校验索引是否越界
return node(index).item; // 查找到指定索引的节点,并返回节点所包含的元素
}
Node<E> node(int index) {
// 首先先判断index是在前半部分还是在后半部分;通过右移运算符 >> 2 表示将链表长度除以2;这个一个优化查询方案;
if (index < (size >> 1)) { // index 在前半部分;将从头节点开始查找
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else { // index 在后半部分;将从尾节点开始查找
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
3.3.5 清空链表元素业务
public void clear() {
// 从头部开始遍历;逐个对节点的内容赋值为null
for (Node<E> x = first; x != null; ) {
Node<E> next = x.next;
x.item = null;
x.next = null;
x.prev = null;
x = next;
}
first = last = null; // 最后将first 和 last 都赋值为null
size = 0;
modCount++;
}
3.5 LinkedList的迭代器
迭代器是将遍历元素的一种形式;也是集合提供遍历方式的一种实现标准;LinkedList里面的迭代器是通过Node节点来实现的,毕竟迭代的东西是节点;
3.5.1 LinkedList 的内置 ListItr迭代器
public ListIterator<E> listIterator(int index) {
checkPositionIndex(index);
return new ListItr(index);
}
可以看出,ListIItr是ListIterator迭代器的子类,意味着该迭代器也拥有在迭代的时候也拥有新增和设置元素的方法。
private class ListItr implements ListIterator<E> {
private Node<E> lastReturned; // 最后确认返回的节点
private Node<E> next; // 遍历需要的内容;由于链表是节点形成的;所以next的类型就是节点
private int nextIndex; // 下一个索引位置
private int expectedModCount = modCount;
// 单参构造指定迭代开始位置;
ListItr(int index) {
next = (index == size) ? null : node(index); // 通过索引获取下次遍历需要内容
nextIndex = index;
}
// 标准迭代器业务接口;下一个是否有元素
public boolean hasNext() {
return nextIndex < size;
}
// 获取下一个节点元素
public E next() {
checkForComodification(); // 校验是否并发修改异常
if (!hasNext())
throw new NoSuchElementException();
lastReturned = next; // 确定返回的内容
next = next.next; // 更新下次需要迭代的内容;其实就是本次内容(next)的后置节点(next); 这里的第一个next是内容;next.next是内容(next)的后置节点;
nextIndex++; // 索引+1
return lastReturned.item; // 返回对应的元素
}
// 上一个节点是否有元素
public boolean hasPrevious() {
return nextIndex > 0;
}
/**
* 获取上一个节点元素
* 【从头到尾迭代元素是通过next后置节点来完成的,
* 同样的从尾到头是通过prev前置节点来完成的】
*/
public E previous() {
checkForComodification(); // 校验并发修改异常
if (!hasPrevious())
throw new NoSuchElementException();
// 确定下一个返回的元素
// (next == null) 确定从尾到头是否遍历完,或者是之前遍历一次从头到尾,next才会等于null,需要重新赋值last;从尾到头开始
lastReturned = next = (next == null) ? last : next.prev;
nextIndex--; // 索引减一
return lastReturned.item;
}
public int nextIndex() {
return nextIndex;
}
public int previousIndex() {
return nextIndex - 1;
}
public void remove() {
checkForComodification();
if (lastReturned == null)
throw new IllegalStateException();
// 将要移除的元素的下一个节点赋值给 lastNext,这是为了在移除后能够正确地更新迭代器的状态
Node<E> lastNext = lastReturned.next;
unlink(lastReturned);// 删除指定节点
// lastReturned该内容是不是下次迭代需要的内容;如果是则将它的后置节点赋值给你
if (next == lastReturned)
next = lastNext;
else
nextIndex--; // 否则,就索引减一,因为不影响;内容的正常迭代
lastReturned = null; //将 lastReturned 置为 null,表示当前没有迭代器指向的元素。
expectedModCount++;
}
// 为当前节点设置指定内容
public void set(E e) {
if (lastReturned == null)
throw new IllegalStateException();
checkForComodification();
lastReturned.item = e; // 更新当前内容的元素
}
// 添加元素
public void add(E e) {
checkForComodification();
lastReturned = null;
if (next == null) // 迭代器内容为空,尾部追加
linkLast(e);
else // 否则头部追加
linkBefore(e, next);
nextIndex++;
expectedModCount++;
}
/** 通过消费者接口进行迭代输出 */
public void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (modCount == expectedModCount && nextIndex < size) {
action.accept(next.item);
lastReturned = next;
next = next.next;
nextIndex++;
}
checkForComodification();
}
/** 用于校验是否触发并发修改异常 关于并发修改异常:小编同系列的java基础篇;的List1篇章中讲解完了*/
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
3.5.2 LinkedList内置的DescendingIterator
该迭代器只提供倒叙迭代,只支持从尾部到头部进行迭代;并且从源码得知,该接口还是借助上面刚刚节点ListItr 迭代器实现的
private class DescendingIterator implements Iterator<E> {
private final ListItr itr = new ListItr(size());
public boolean hasNext() {
return itr.hasPrevious();
}
public E next() {
return itr.previous();
}
public void remove() {
itr.remove();
}
}
3.6 LinkedList内置的分割器(LLSpliterator)
分割器它是由JDK8,提供的一个标准接口;之前在将ArrayList的时候,ArrayList 集合接口也有着属于自己的分割器,名称叫做(ArrayListSpliterator);而LinkedList也不另外;也有自己专属的分割器(LLSpliterator ) "LL" 就是LinkedList的缩写;
3.6.1 【分割器迭代案例】
package org.toast.collection.spliterator;
import java.util.LinkedList;
import java.util.Spliterator;
/**
* @author toast
* @time 2024/4/20
* @remark
*/
public class TestLinkedListSpliterator {
public static void main(String[] args) {
LinkedList<String> data = new LinkedList<>();
data.add("TOAST-1");
data.add("TOAST-2");
data.add("TOAST-3");
data.add("TOAST-4");
data.add("TOAST-5");
data.add("TOAST-6");
data.add("TOAST-7");
data.add("TOAST-8");
data.add("TOAST-9");
data.add("TOAST-10");
data.add("TOAST-11");
data.add("TOAST-12");
data.add("TOAST-13");
data.add("TOAST-14");
data.add("TOAST-15");
data.add("TOAST-16");
data.add("TOAST-17");
Spliterator<String> spliterator = data.spliterator();
System.out.println("===========================使用 分割器迭代=============================");
spliterator.forEachRemaining(System.out::println);
}
}
3.6.2 【多个分割器迭代案例】
package org.toast.collection.spliterator;
import java.util.LinkedList;
import java.util.Spliterator;
/**
* @author toast
* @time 2024/4/20
* @remark
*/
public class TestLinkedListSpliterator {
public static void main(String[] args) {
LinkedList<String> data = new LinkedList<>();
data.add("TOAST-1");
data.add("TOAST-2");
data.add("TOAST-3");
data.add("TOAST-4");
data.add("TOAST-5");
data.add("TOAST-6");
data.add("TOAST-7");
data.add("TOAST-8");
data.add("TOAST-9");
data.add("TOAST-10");
data.add("TOAST-11");
data.add("TOAST-12");
data.add("TOAST-13");
Spliterator<String> spliterator = data.spliterator();
System.out.println("===========================使用 分割器创建多个分割器=============================");
Spliterator<String> subSpliter1 = spliterator.trySplit();
Spliterator<String> subSpliter2 = spliterator.trySplit();
System.out.println("==华丽的分界线==");
if (subSpliter1 != null) subSpliter1.forEachRemaining(System.out::println);
System.out.println("==华丽的分界线==");
if (subSpliter2 != null) subSpliter2.forEachRemaining(System.out::println);
}
}
如果不进行if (subSpliter2 != null) 空判断,则会出现空指针异常,因为LinkedList并不像ArrayList一样支持随机访问进行分割;链表只有从头到尾/从尾到头。两个方向;无法进行分割器分割成多个并行迭代;
四 Vector
Vector 集合,也List接口一脉的一个子类集合;之前我们已经讲解了数组和链表实现List 列表集合的方式,而Vector 也是用数组实现的,那它和同样是ArrayList数组实现的有什么区别吗?其主要区别就是Vector是线程安全的,ArrayList是线程不安全的;现在实际应用场景当中不需要考虑多线程访问确保数据的安全性的时候;可以使用ArrayList集合;在需要考虑的线程安全的时候也不会用到Vector; 那这个集合有什么用?现在一般都除非废弃状态;
毕竟在集合线程安全方面;在JDK1.2同集合一起出台的Collections.synchronizedXxx 方法就可以将线程不安全的集合包装成线程安全的;更重要的是在JDK1.5的 JUC包下提供线程安全类比传统的性能,安全性都高一等;所以就很少用Vector这个集合了。
4.1 Vector 属性分析
package java.util;
/**
* @author Lee Boynton
* @author Jonathan Payne
* @see Collection
* @see LinkedList
* @since 1.0
*/
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
/** 元素容器 */
@SuppressWarnings("serial") // Conditionally serializable
protected Object[] elementData;
/** 容器大小 */
protected int elementCount;
/** 动态扩容数量 如果被指定扩容数量则按指定的扩展;没有如果指定/指定的数量小于0 ;则直接按照 本身容器大小数量直接扩一倍 */
protected int capacityIncrement;
/** 序列号,支持序列化,毕竟实现Serializable接口 */
private static final long serialVersionUID = -2767605614048989439L;
}
4.2 Vector 构造器
/** 默认大小:10*/
public Vector() {
this(10);
}
/**
* @param initialCapacity 初始容量
*/
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
/**
* @param initialCapacity 初始容量
* @param capacityIncrement 扩容容量
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
/**
* 将指定的元素集添加到Vector里面
* @since 1.2
*/
public Vector(Collection<? extends E> c) {
Object[] a = c.toArray();
elementCount = a.length;
// 如果c是数组,则直接设置一样的大小空间
if (c.getClass() == ArrayList.class) {
elementData = a;
} else {
// 不是数组,则按照元素集的大小再扩大一倍
elementData = Arrays.copyOf(a, elementCount, Object[].class);
}
}
4.3 Vector业务方法
4.3.1 新增业务
public synchronized boolean add(E e) {
modCount++; // 记录修改次数
add(e, elementData, elementCount);
return true;
}
/**
* @param e 要新增的元素
* @param elementData 容器
* @param s 容器数量
*/
private void add(E e, Object[] elementData, int s) {
// 容器是否已存满,满之后调用grow()方法进行扩容处理
if (s == elementData.length) elementData = grow();
elementData[s] = e;
elementCount = s + 1;
}
public synchronized void addElement(E obj) {
modCount++;
add(obj, elementData, elementCount);
}
public void add(int index, E element) {
insertElementAt(element, index);
}
/**
* 在指定的索引位置添加元素
* @param obj 要添加的元素
* @param index 索引位置
*/
public synchronized void insertElementAt(E obj, int index) {
// 索引是否越界
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " > " + elementCount);
modCount++; // 修改记录加一
final int s = elementCount;
Object[] elementData = this.elementData;
if (s == elementData.length) elementData = grow(); // 容器是否已满,已满调用扩容处理
// 在指定的索引位置插入元素,并后面的元素索引都+1
System.arraycopy(elementData, index, elementData, index + 1, s - index);
elementData[index] = obj;
elementCount = s + 1;
}
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
modCount++;
int numNew = a.length;
if (numNew == 0) return false;
synchronized (this) {
Object[] elementData = this.elementData;
final int s = elementCount;
// 要添加的元素集内容大于容器剩余空间数量,不足,进行容量
if (numNew > elementData.length - s) elementData = grow(s + numNew);
// 将a的元素复制到容器(elementData)里面,从s到numNew
System.arraycopy(a, 0, elementData, s, numNew);
elementCount = s + numNew;
return true;
}
}
Vector集合提供三种新增业务的方法,一种是直接在数组后面追加,一种是指定索引位置进行元素的添加,一种就是批量新增元素;但是需要注意的是这些操作都有synchronized 锁修饰着;说明是线程安全的。
4.3.2 删除业务
/** 删除指定元素 */
public boolean remove(Object o) {
return removeElement(o);
}
/** 删除指定元素 */
public synchronized boolean removeElement(Object obj) {
modCount++; // 修改记录次数+1
int i = indexOf(obj); // 根据元素找到对应的索引
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
/** 根据指定索引删除元素 */
public synchronized void removeElementAt(int index) {
if (index >= elementCount) { // 索引是否越界
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1; // 计算出需要移动的数量
if (j > 0) {
// 将index + 1 位置的之后的元素都复制到index索引位置;这样就都向前移动一位,覆盖了要删除的元素
System.arraycopy(elementData, index + 1, elementData, index, j);
}
modCount++;
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
/** 删除所有元素 */
public synchronized void removeAllElements() {
final Object[] es = elementData;
for (int to = elementCount, i = elementCount = 0; i < to; i++) es[i] = null;
modCount++;
}
/** 删除指定的元素集内容 */
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return bulkRemove(e -> c.contains(e));
}
private synchronized boolean bulkRemove(Predicate<? super E> filter) {
int expectedModCount = modCount; // 当前修改次数
final Object[] es = elementData; // 当前容量
final int end = elementCount; // 当前大小
int i;
// 寻找不满足的点
for (i = 0; i < end && !filter.test(elementAt(es, i)); i++);
if (i < end) { // 找到不满足的点,进行删除操作
final int beg = i; // 记录删除开始位置,就是不满足的点的位置
// 创建一个删除元素的数组;大小是end-beg
final long[] deathRow = nBits(end - beg);
// 将 deathRow 的第一个位设置为 1,表示第一个不满足条件的元素要移除
deathRow[0] = 1L; // set bit 0
// 遍历数组中的剩余元素,如果满足条件,则在 deathRow 中设置相应的位。
for (i = beg + 1; i < end; i++)
if (filter.test(elementAt(es, i)))
setBit(deathRow, i - beg);
// 检查在标记元素过程中是否有其他线程对数组进行了修改,如果有,则抛出并发修改异常。
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
modCount++;
// 创建一个指针 w,指向要保留的元素的位置,然后遍历数组,将不需要移除的元素移到数组前面。
int w = beg;
for (i = beg; i < end; i++)
if (isClear(deathRow, i - beg))
es[w++] = es[i];
// 更新数组的元素数量 elementCount 为 w,然后将多余的位置设置为 null
for (i = elementCount = w; i < end; i++) es[i] = null;
return true;
} else { // 如果未找到满足条件的元素,则直接返回 false
// 如果发现有其他线程对数组进行了修改,则抛出并发修改异常。
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return false;
}
}
删除业务也对应的提供了按元素删除,按索引删除,删除指定元素集,删除全部元素等等,还提供通过JDK8 提供的Predicate 谓词来判断参考条件;也都是线程安全操作
4.3.3 修改业务
/** 修改指定索引的元素 */
public synchronized void setElementAt(E obj, int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
elementData[index] = obj;
}
/** 修改指定索引的元素 */
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
4.3.4 查询业务
/** 根据指定的索引获取元素 */
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
/** 根据指定的索引获取元素 */
public synchronized E elementAt(int index) {
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
}
return elementData(index);
}
/** 获取第一个元素 */
public synchronized E firstElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(0);
}
/** 获取最后一个元素 */
public synchronized E lastElement() {
if (elementCount == 0) {
throw new NoSuchElementException();
}
return elementData(elementCount - 1);
}
E elementData(int index) {
return (E) elementData[index];
}
4.3.5 复制业务
public synchronized Object clone() {
try {
@SuppressWarnings("unchecked")
Vector<E> v = (Vector<E>) super.clone();
v.elementData = Arrays.copyOf(elementData, elementCount);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
4.3.6 扩容机制
/** 默认扩容 */
private Object[] grow() {
return grow(elementCount + 1);
}
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
// capacityIncrement 扩容容量大于0 则使用指定的进行扩容;否则直接扩容一倍
int newCapacity = ArraysSupport.newLength(
oldCapacity,
minCapacity - oldCapacity,
capacityIncrement > 0 ? capacityIncrement : oldCapacity);
return elementData = Arrays.copyOf(elementData, newCapacity);
}