2023年7月15日,Iterator,LinkedList底层,Vector

文章详细介绍了Java中ArrayList的迭代器使用,包括Itr和ListItr的区别,以及ArrayList的扩容过程。同时,分析了LinkedList的内部节点结构,如何添加和删除元素,以及其在插入和删除操作上的优势。此外,还提到了ArrayList和LinkedList的区别以及线程安全的Vector和Stack类。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Collection下的方法总结:

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的扩容过程如下:

  1. 当要添加一个新元素到ArrayList时,首先检查当前元素数量是否达到了数组的容量上限。如果达到了容量上限,则需要进行扩容操作。
  2. 扩容操作会创建一个新的更大容量的数组,并将原数组中的元素复制到新数组中。
  3. 扩容通常会选择一个合适的增长策略,例如每次扩容增加当前容量的一半或固定增加一定数量的元素空间。
  4. 复制元素到新数组后,ArrayList会开始使用新数组作为其底层数组,并更新容量信息。
  5. 添加新元素到扩容后的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底层

  1. 调用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());
    }
}
  1. 在迭代器对象中,会维护一个指针,指向当前元素的位置。
   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();
        }
    }
  1. 使用迭代器的hasNext()方法:这个方法检查指针指向的位置是否有下一个元素。如果有,返回true;如果没有,返回false。
public boolean hasNext() {
            return cursor != size;
        }
  1. 使用迭代器的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];
        }
  1. 循环遍历:使用迭代器的hasNext()方法在每次循环迭代时检查是否还有下一个元素,如果有则调用next()方法取得该元素并进行相应的操作。

  2. 遍历结束:当迭代器指针移动到最后一个元素之后,再次调用hasNext()方法将返回false,循环结束。

迭代器实现了Iterator接口的方法,包括hasNext()和next()方法,通过这两个方法实现对ArrayList集合的遍历。
在使用迭代器遍历过程中,如果在迭代过程中对集合进行了结构性的修改(如添加或删除元素),迭代器会检测到并抛出ConcurrentModificationException异常,防止遍历过程中的并发修改错误。

使用迭代器遍历ArrayList集合时,在底层会创建迭代器对象并维护一个指针,通过调用hasNext()和next()方法实现对集合元素的遍历

LinkedList(双向链表)

双链表结构

双向链表

1. LinkedList底层的实现

  1. 通过节点(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;
        }
    }
  1. 在Java中的LinkedList类中,有一个内部类Node用于表示节点。LinkedList类本身维护了两个特殊的节点,即头节点(first)和尾节点(last)。这两个节点分别表示链表的第一个节点和最后一个节点。
    transient int size = 0;

    transient Node<E> first;

    transient Node<E> last;
  1. 当在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++;
    }

  1. 类似地,当从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

ArrayList与LinkedList的区别

Vector

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);
        }
    }
}

结果:

请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值