java.util.List 接口的部分实现解析(jdk1.8)

本文深入剖析了Java中ArrayList、LinkedList、CopyOnWriteArrayList三种集合类的源码,包括构造方法、增删改查等核心操作的实现细节,旨在帮助开发者理解底层原理,提升编码效率。

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

源码解析ArrayList,LinkedList,CopyOnWriteArrayList

以下是我的blog,采用前后端分析,后端springboot,前端vue+iview实现,也算是原文地址了.点击跳转

1. 环境及介绍
1.1 环境
  • JDK环境: 1.8
  • 工具: IDEA
1.2 介绍

List集合是我们日常编码中常用的类型,但是并不是每个人都会有一个看源码的习惯.虽然我们也是应用层开发,但是理解底层实现更能帮助我们写出高效的代码.本文就对常用的一些类型通过源码来进行备注.如有错漏,烦请同行告知.


2. 类层级及结构分析

本文只是介绍部分实现,囊括范围为常用类型
本文将要介绍的接口及实现类

2.1 Iterable接口

Iterable类结构

2.1.1 描述

官方文档如是说: “实现此接口,允许一个对象使用for-each循环.”,这就是讲,我们实现了该接口就能够进行遍历元素操作.

2.1.2 功能

我们可以把它看做一个标志,是否支持遍历

2.2 Collection接口

Collection类结构图

2.2.1 描述

集合结构中的根接口,也称为集合最上层.集合表示一组对象.单个对象被称为元素.一些集合允许重复元素存在,而另一些则不允许.一些集合是有序的,一些是无序的.JDK没有提供该接口的任何具体实现.但是提供了子接口.它们实现了Collection接口.而本接口通常用于传递集合,在没有具体类型出现的时候不需要转换就可以.这也是java的特性多态的场景.

2.2.2 功能

作为一个集合结构的制定者,这就是抽象的魅力.它囊括了集合的所有基本操作.虽然没有提供具体实现,但是它却是一种规范化.可以把它看做一个模板,后边的具体实现是这个模板的衍生.

2.3 List接口

List接口类结构图

2.3.1 描述

有序集合,接前文Collection接口中有有序和无序之差.而List接口则是有序,可重复集合的根接口.我们可以在集合的任何位置进行插入操作.也可以通过下标进行随机访问.

2.3.2 功能

所有有序可重复集合父类.在没有确定子类的具体类型时我们会使用,而常用的是用子类引用指向父类对象.eg. List list = new ArrayList();

2.4 AbstractList

AbstractList类结构图

2.3.1 描述

AbstractList类是抽象类,此类提供了List接口的基本实现.最大限度支持"随机访问", 对于顺序访问数据,建议优先使用AbstractSequentialList.

2.3.2 方法解析
2.3.2.1 未实现方法

本类中有很多方法是没有具体实现的,因为本来是抽象类.它把具体的方法实现交给了子类.以下列出未实现方法.
add(E e);add(int index, E element);remove(int index);set(int index, E element);
当调用这些方法,而子类又没有具体实现时,会抛出 throw new UnsupportedOperationException()异常,不支持操作异常
此中 get(int index); 方法是没有实现,子类实现必须实现该方法.

2.3.2.2 已经实现方法

这里我们选一个通过元素寻找下标方法解析.

	public int indexOf(Object o) {
        ListIterator<E> it = listIterator();
        if (o==null) {
            while (it.hasNext())
                if (it.next()==null)
                    return it.previousIndex();
        } else {
            while (it.hasNext())
                if (o.equals(it.next()))
                    return it.previousIndex();
        }
        return -1;
    }
    public ListIterator<E> listIterator() {
        return listIterator(0);
    }
    public ListIterator<E> listIterator(final int index) {
        rangeCheckForAdd(index);

        return new ListItr(index);
    }
    private void rangeCheckForAdd(int index) {
        if (index < 0 || index > size())
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

以上代码ListItr 为内部类.首先通过listIterator获取遍历器.然后判断传入对象是否为空,如果为空则获取下一个,直到下一个为null,返回当前元素的前一个下标.因为是返回下标,所以是从0开始.如果传入对象不为空,则进行判断是否下一个元素等于传入对象.如果等于,则返回前一个下标.

2.5 ArrayList

ArrayList类结构图

2.3.1 描述

ArrayList是由可调整大小的数组实现,实现了所有List中的方法,此类还提供了操纵内部数组大小的方法.
ArrayList不同步.每个ArrayList 都有容量,是可以动态增加的,其扩容是通过数组复制实现的,即新建一个数组,将原数组复制.如果频繁扩容效率极低.扩容的阈值为负载因子*容量.具体参数请看构造方法

2.3.2 方法解析

构造方法

	// 无参构造,生成对象,将对象中目标数据定义为默认 空对象数组.
	public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    // 携带集合入参,将集合转换为数组,然后通过Arrays.copyOf 将原数组复制到新数组中.
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA;
        }
    }
    // 携带容量入参.入参大于0则取入参,如果等于0,则设置目标数组为空(后续增加就会触发一次扩容),否则异常抛出
    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);
        }
    }

操作方法
add-添加一个元素

	// 将一个元素插入到数组末位
	public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    // 判断数组是否需要扩容
    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	// 如果当前数组为默认空数组,则进行使用入参容量与默认容量(16)比较取最大值
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }
    
    // 
    private void ensureExplicitCapacity(int minCapacity) {
    	// 结构变化计数字段,调用add,remove都会操作该字段
        modCount++;

        // overflow-conscious code 
        if (minCapacity - elementData.length > 0)
        	// 当传入增加后容量大于当前数组容量,此处容量即长度
            grow(minCapacity);
    }
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 能看出,集合的扩容是1.5倍,将原长度右位移一位再加原长度
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 如果扩容后长度小于传入长度,则取传入长度
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        	// 如果扩容后长度大于默认的最大长度(Integer.MAX_VALUE - 8 21亿多吧)则取Integer.MAX_VALUE
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

get-获取一个元素

	// 获取指定位置的元素
	public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }
    // 如果长度大于数组的长度,异常抛出
    private void rangeCheck(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
    // 这里就可以验证,list集合底层是数组结构了,通过下标取元素
    E elementData(int index) {
        return (E) elementData[index];
    }

remove-删除一个元素

	// 可以通过下标删除,也可以通过元素进行删除,此处用下标解析
	public E remove(int index) {
		// 已经看过源码,判断当前下标是否超过数组长度,超过则抛出下标越界
        rangeCheck(index);
		// 上文讲过,会修改数组结构的计数
        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
        	// 将原数组进行复制,是为了移除指定下标元素
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        // 将尾部元素置为null,这是类似手动提示GC的方式,不一定会被马上回收                     
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }
2.6 LinkedList

LinkedList类结构图

2.3.1 描述

非同步容器,双链表结构,可以通过头部查找或者尾部查找

2.3.2 方法解析

构造方法

	// 此处省略无参构造,本方法是集合类入参
	public LinkedList(Collection<? extends E> c) {
        this();
        addAll(c);
    }
    public boolean addAll(Collection<? extends E> c) {
    	// size为链表长度,即大小
        return addAll(size, c);
    }
    public boolean addAll(int index, Collection<? extends E> c) {
    	// 长度校验,如果不满足大于0小于等于链表长度,则异常
        checkPositionIndex(index);
		// 将集合转换为数组
        Object[] a = c.toArray();
        int numNew = a.length;
        if (numNew == 0)
            return false;
		// Node为静态内部类,其结构由三部分组成,自身数组,下一元素引用,上一元素引用.这就是被称为双向链表的原因
        Node<E> pred, succ;
        if (index == size) {
            succ = null;
            pred = last;
        } else {
            succ = node(index);
            pred = succ.prev;
        }

        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node<E> newNode = new Node<>(pred, e, null);
            // 如果链表末位为null,则表明该链表为空,直接设置为链头
            if (pred == null)
                first = newNode;
            else
            	// 将新元素查到末位
                pred.next = newNode;
            // 设置为末位    
            pred = newNode;
        }

        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }

        size += numNew;
        modCount++;
        return true;
    }
    private void checkPositionIndex(int index) {
        if (!isPositionIndex(index))
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

add-增加或者插入一个元素

	// 还可以通过下标加元素,添加指定位置,此处只讲元素插入末位
	public boolean add(E e) {
        linkLast(e);
        return true;
    }
    // 新元素插到末位
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

get-获取一个元素

	public E get(int index) {
		// 判断元素下标是否大于等于0,小于当前长度,不满足抛出下标越界异常
        checkElementIndex(index);
        return node(index).item;
    }
    Node<E> node(int index) {
        // assert isElementIndex(index);
		// 这里使用了二分查找,即判断下标是否超过半数,超过则从末位开始查找,否则从首位开始
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

poll-获取并移除链头元素

	// 获取并移除首位
	public E poll() {
        final Node<E> f = first;
        return (f == null) ? null : unlinkFirst(f);
    }
    // 移除头尾元素,并将下一位置位链头
    private E unlinkFirst(Node<E> f) {
        // assert f == first && f != null;
        final E element = f.item;
        final Node<E> next = f.next;
        f.item = null;
        f.next = null; // help GC
        first = next;
        if (next == null)
            last = null;
        else
            next.prev = null;
        size--;
        modCount++;
        return element;
    }

peel-获取链头元素,不移除

	// 简单的只是获取元素
	public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

注: 这里面其实还有很多方法,需要去看源码或者api,不过建议jdk还是看源码,三方框架看手册

2.7 CopyOnWriteCopyOnWriteArrayList

CopyOnWriteCopyOnWriteArrayList类结构图

2.3.1 描述

线程安全的集合,使用lock可重入锁实现。基于集合的增加和删除,都是通过数组的复制来做的,频繁扩容会导致效率低下。

2.3.2 方法解析

构造方法

	// 此处省略无参构造,讲解集合入参
	public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
        	// 如果入参类型与当前类一致,就直接转数组赋值
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
        	// 如果不一致,先转数组赋值
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
            	// 如果不是Object对象,则进行复制
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

add-添加一个元素至集合末位

	// 这里使用了lock锁,lock是基于api层面的,优化空间较小了.
	public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 底层任然是数组的复制
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            // 设置回当前类的目标数据数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

get-获取指定下标元素

	// 这里居然没有使用锁,我们可能会考虑是否真的能够线程安全.其实可以仔细看看private transient volatile Object[] array; 它的目标数组是volatile修饰,也就是说内存透明,让工作内存每次去读取主内存数据,这样一来就能够做到数据及时性.因为我们的锁更多的是锁写入.这里不适用锁,就会更高效了
	public E get(int index) {
        return get(getArray(), index);
    }
    // 数组方式通过小标获取元素
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

remove-删除一个指定下标元素

	public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 在目标数组取下标返回元素
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
            	// 如果删除的元素不是末尾,则进行数组的复制
                setArray(Arrays.copyOf(elements, len - 1));
            else {
            	// 如果是末尾,进行复制,抛弃末尾即可
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                // 将变更后数组赋值到目标数组                 
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
3. 小结

本文主要以源码为根基,浅略的讲解了一些方法和它的一点点描述,其实更多的部分还是需要以源码入手,自己看一看才能够有所收获。jdk中的很多思路其实跟现有软件中的一些实现是相通的。本文浅显,如有遗漏错论,希望各位同行提意见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值