Java集合之List部分源码解析
Published on 2022-02-28 19:35 in 暂未分类 with 努力努力yyy

Java集合之List部分源码解析

Java集合之List源码解析

List是java重要的数据结构之一,而我们在生活中,常常用到ArrayList、LinkedList和Vector三种

它们都继承来自java.util.Collection接口

如图所示:

img

接下来了解一下,这三种List是如何实现的,以及它们之间存在什么不同点。

一、基本实现

1.ArrayList和Vector使用了数组实现,可以认为它们封装了对内部数组的操作,它们两个底层的实现基本可以认为是一致的,主要的一点区别在于对多线程的支持上。

ArrayList不是线程安全的,Vector是线程安全的。

2.LinkedList使用了双向链表数据结构

二、不同点

主要看ArrayList和LinkedList的区别

1.在尾端增加元素

ArrayList

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // 确保内部数组有足够的空间
        elementData[size++] = e; //将元素加入到数组的末尾,完成添加
        return true;
    }

从add函数可以看出,其性能主要与ensureCapacityInternal函数有关

我么继续追踪这个函数

//ensureCapacityInternal函数
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

calculateCapacity方法会根据两种情况返回数组的最小容量

(1).当elementData这个数组为空数组时,则返回ArrayList默认容量(10)和所需的最小容量(minCapacity)两者的最小值

(2).当elementData非空时,则直接返回所需的最小容量

ensureExplicitCapacity方法是去判断如果所需容量大于当前对象数组的长度则调用grow方法对数组进行扩容

从这里我们可以看出来,当ArrayList数组容量满足需求的时候,add()方法其实就是直接赋值,不需要额外的开销。而当容量不足的时候,还要进行数组复制和扩容的操作,此时的性能就会有所下降。

LinkedList

//尾端插入,即将节点值为e的节点设置为链表的尾节点
    void linkLast(E e) {
        final Node<E> l = last;
        //构建一个前驱prev值为l,节点值为e,后驱next值为null的新节点newNode
        final Node<E> newNode = new Node<>(l, e, null);
        //将newNode作为尾节点
        last = newNode;
        //如果原尾节点为null,即原链表为null,则链表首节点也设置为newNode
        if (l == null)
            first = newNode; 
        else  //否则,原尾节点的next设置为newNode
            l.next = newNode;
        size++;
        modCount++;
    }

根据链表的知识我们可以知道,LinkedList是不需要去维护容量大小的,但是每次元素的增加都要先创建一个Node对象,然后进行一系列操作才可以实现在LinkedList尾端添加元素,如果是大量数据的话,性能可能会下降。

2.增加元素到任意位置

void add(int index, E element)

由于ArrayList是数组实现的,所以在数组中间插入元素的时候,必然会导致其后面的元素后移,对性能影响大。

ArrayList

public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //数组复制
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

LinkedList

public void add(int index, E element) {
        checkPositionIndex(index);

        if (index == size)
            linkLast(element);
        else
            linkBefore(element, node(index));
    }

    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;
        }
    }
void linkBefore(E e, Node<E> succ) {
    // assert succ != null;
    //指定节点的前驱pred
    final Node<E> pred = succ.prev;
    //当前节点的前驱为指点节点的前驱,后继为指定的节点
    final Node<E> newNode = new Node<>(pred, e, succ);
    //更新指定节点的前驱为当前节点
    succ.prev = newNode;
    //更新前驱节点的后继
    if (pred == null)
        first = newNode;
    else
        pred.next = newNode;
    size++;
    modCount++;
}

3、删除任意位置元素

 public E remove(int index) 

对于ArrayList来说,remove()方法和add()方法思路上相同,在删除指定元素后,都要移动其他元素

ArrayList

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);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

LinkedList

public E remove(int index) {
        checkElementIndex(index);
        return unlink(node(index));
    }

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

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

4、随机访问

  public E get(int index)

ArrayList

public E get(int index) {
        rangeCheck(index);

        return elementData(index);
    }

    @SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

我们可以看到ArrayList是直接访问的

LinkedList

public E get(int index) {
        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;
        }
    }
posted @   努力努力yyy  阅读(156)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
点击右上角即可分享
微信分享提示