Collection下的方法总结:
常用集合分类
Iterator
ArrayList的迭代器:
普通的Itr和ListItr,ListItr继承Itr,实现了更多的功能,ListItr继承Itr.Itr本身只能向后迭代。而ListItr既能
向后迭代,也能向前迭代。
Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
Iterator仅用于遍历集合,不存储对象
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayList_class {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
Iterator<Integer> itr = list.iterator();
while (itr.hasNext()){
Integer next = itr.next();
System.out.println(next);
}
}
}
1. 扩容
ArrayList的扩容过程如下:
- 当要添加一个新元素到ArrayList时,首先检查当前元素数量是否达到了数组的容量上限。如果达到了容量上限,则需要进行扩容操作。
- 扩容操作会创建一个新的更大容量的数组,并将原数组中的元素复制到新数组中。
- 扩容通常会选择一个合适的增长策略,例如每次扩容增加当前容量的一半或固定增加一定数量的元素空间。
- 复制元素到新数组后,ArrayList会开始使用新数组作为其底层数组,并更新容量信息。
- 添加新元素到扩容后的ArrayList中。
扩容操作可能会导致一定的性能开销,因为需要重新分配内存并复制元素。因此,在预先知道ArrayList可能需要存储大量元素时,可以通过初始化ArrayList时设置一个较大的初始容量,来减少扩容操作的频率,提高性能。
需要注意的是,虽然ArrayList会自动进行扩容操作,但频繁进行大量元素的插入和删除操作仍可能导致性能下降,因为每次操作都需要移动大量元素。在这种情况下,考虑使用LinkedList或其他数据结构可能更合适。
private void grow(int minCapacity) {
// 获取当前数组的容量oldCapacity
int oldCapacity = elementData.length;
//创建一个新容量,是原来容量的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//将newCapacity设置为minCapacity,以确保扩容后的容量能够容纳最小需求
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//调用hugeCapacity
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// Arrays.copyOf方法将原数组elementData复制到一个新的数组中,并将新数组赋值给elementData,完成扩容操作
elementData = Arrays.copyOf(elementData, newCapacity);
}
//边界检查和处理
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
2. Iterator底层
- 调用ArrayList的iterator()方法:这个方法会返回一个实现了Iterator接口的迭代器对象。
public Iterator<E> iterator() {
return new Itr();
}
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
- 在迭代器对象中,会维护一个指针,指向当前元素的位置。
private class Itr implements Iterator<E> {
int cursor; // 迭代器要返回的下一个元素的索引。
int lastRet = -1; // 迭代器返回的最后一个元素的索引。
int expectedModCount = modCount;//这个变量用于跟踪ArrayList的预期修改次数,以便检测并发修改。(判断内外部操作数是否一致,如果不一致说明在迭代过程中元素数量发生了变化)
Itr() {}
//将cursor(光标)与列表的size进行比较,检查是否还有下一个元素。
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//检测ArrayList是否发生了修改
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;
//返回当前cursor索引处的元素,并更新lastRet以记录最后返回元素的索引
return (E) elementData[lastRet = i];
}
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();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
//列表中的元素数量
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
//获取 ArrayList 的元素数组 elementData
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
//将元素数组 elementData 中的元素传递给传入的 consumer 对象进行处理,并将迭代索引 i 自增。
consumer.accept((E) elementData[i++]);
}
// 更新迭代器的 cursor 为 i,表示迭代器的下一个元素位置
cursor = i;
//lastRet 为 i - 1,表示迭代器最后一个返回元素的索引
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
- 使用迭代器的hasNext()方法:这个方法检查指针指向的位置是否有下一个元素。如果有,返回true;如果没有,返回false。
public boolean hasNext() {
return cursor != size;
}
- 使用迭代器的next()方法:这个方法返回指针指向的当前元素,并且将指针移动到下一个位置。
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
//获取 ArrayList 的元素数组 elementData
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//迭代器将移动到下一个位置
cursor = i + 1;
//即返回当前迭代位置的元素,并将 lastRet 更新为 i,表示当前迭代位置的索引
return (E) elementData[lastRet = i];
}
-
循环遍历:使用迭代器的hasNext()方法在每次循环迭代时检查是否还有下一个元素,如果有则调用next()方法取得该元素并进行相应的操作。
-
遍历结束:当迭代器指针移动到最后一个元素之后,再次调用hasNext()方法将返回false,循环结束。
迭代器实现了Iterator接口的方法,包括hasNext()和next()方法,通过这两个方法实现对ArrayList集合的遍历。
在使用迭代器遍历过程中,如果在迭代过程中对集合进行了结构性的修改(如添加或删除元素),迭代器会检测到并抛出ConcurrentModificationException异常,防止遍历过程中的并发修改错误。使用迭代器遍历ArrayList集合时,在底层会创建迭代器对象并维护一个指针,通过调用hasNext()和next()方法实现对集合元素的遍历
LinkedList(双向链表)
双链表结构
1. LinkedList底层的实现
- 通过节点(Node)来构建双向链表。每个节点包含三个部分:数据(element)、指向前一个节点的指针(previous)和指向后一个节点的指针(next)。
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;
}
}
- 在Java中的
LinkedList
类中,有一个内部类Node用于表示节点。LinkedList
类本身维护了两个特殊的节点,即头节点(first)和尾节点(last)。这两个节点分别表示链表的第一个节点和最后一个节点。
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
- 当在LinkedList中添加元素时,会创建一个新的节点,并将其插入到链表的末尾。新节点的前一个节点指针指向当前的尾节点,尾节点的后一个节点指针指向新节点,然后更新尾节点为新节点。
public boolean add(E e) {
linkLast(e);
return true;
}
void linkLast(E e) {
//l指向最后一个节点
final Node<E> l = last;
//创建一个新的Node对象,其中包含前一个节点的引用(l)、元素值(e)和下一个节点的引用(初始值为null)。
final Node<E> newNode = new Node<>(l, e, null);
//使newNode成为链表的新最后一个节点
last = newNode;
//如果l为null,表示链表为空,此时newNode也是链表的第一个节点。因此,将first设置为newNode。
if (l == null)
first = newNode;
else
//表示链表不为空,我们将更新l(前一个最后一个节点)的next引用,使其指向newNode,从而将新创建的节点链接到末尾。
l.next = newNode;
//将链表的大小增加一
size++;
//操作数加一
modCount++;
}
- 类似地,当从LinkedList中删除元素时,会更新相应的节点指针,将前一个节点的后一个节点指针指向后一个节点,将后一个节点的前一个节点指针指向前一个节点。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
//从链表中移除给定的节点
E unlink(Node<E> x) {
// 节点x的元素值存储到element变量中,以便在最后返回。
final E element = x.item;
//将节点x的下一个节点引用存储到next变量中。
final Node<E> next = x.next;
//将节点x的前一个节点引用存储到prev变量中。
final Node<E> prev = x.prev;
//如果prev为null,表示x是第一个节点,则将first引用更新为next,从而将第二个节点成为新的第一个节点。
if (prev == null) {
first = next;
} else {
//如果prev不为null,表示x不是第一个节点,则将prev的next引用更新为next,从而将x跳过,将prev和next直接连接起来。同时,将x的prev引用设置为null,断开与前一个节点的连接。
prev.next = next;
x.prev = null;
}
//如果next为null,表示x是最后一个节点,则将last引用更新为prev,将前一个节点成为新的最后一个节点。
if (next == null) {
last = prev;
} else {
//如果next不为null,表示x不是最后一个节点,则将next的prev引用更新为prev,从而将x跳过,将next和prev直接连接起来。同时,将x的next引用设置为null,断开与后一个节点的连接。
next.prev = prev;
x.next = null;
}
//节点x的元素值设置为null,以便帮助垃圾回收器回收节点对象。
x.item = null;
//链表大小减一
size--;
//计数器加一,表示元素数量发生了变化
modCount++;
//返回删除的元素
return element;
}
通过使用双向链表作为底层实现,LinkedList在插入和删除元素时具有较好的性能,但在访问和查找元素时需要遍历链表,因此访问和查找的性能相对较低。
2. ArrayList与LinkedList的区别
ArrayList
->AbstractList
->AbstractCollection
=>List
->Collection
->Iterable
Vector
Vector
是线程安全的集合,用法与ArrayList
是一样的
Vector
中特殊的方法
public class StackTest {
public static void main(String[] args) {
Vector<String> vector = new Vector<>();
vector.addElement("a");
vector.addElement("b");
vector.addElement("c");
vector.addElement("d");
vector.add("e");
String str = vector.elementAt(1);
System.out.println(str);
System.out.println("----------------");
//与迭代器用法相似
Enumeration<String> elements = vector.elements();
while (elements.hasMoreElements()){//判断枚举中是否有元素
String s = elements.nextElement();//取出下一个元素
System.out.println(s);
}
}
}
结果:
1. Stack
栈:先进后出
class Stack<E> extends Vector<E> {}//Stack继承了Vector也是线程安全的
栈的pop()与peek()的区别:pop调用了peek方法来获取最后一个元素,然后会把最后一个元素删除
import java.util.Stack;
public class Stack02Test {
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
stack.push(5);
while (!stack.empty()){
Integer pop = stack.pop();
System.out.println(pop);
}
}
}
结果: