ArrayList,LinkedList源码分析

1.ArrayList底层原理:

原理:基于底数组实现的

2.ArrayList源码分析:

2.1 add方法(在尾部添加元素):

public boolean add(E e) {
//构造函数时候size已经初始化,size初始化=10,这里这个方法作用:确保elementData的容量可以放入新加的一个元素
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

private void ensureCapacityInternal(int minCapacity) {
//minCapacity==11,elemetDate==null
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }
    
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        //minCapacity==11,判断新添加一个元素数组的长度是否比在没有添加
        //时候的长度的大小
        if (minCapacity - elementData.length > 0)
        	//如果大于0,代表需要扩容了
            grow(minCapacity);
    }
    //数组的扩容
 private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //例子:加入原先数组容量是10 现在是10 + 10/2 =15
        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);
    }

2.2 add(int index,E e)在指定位置添加数据的过程:

//将元素插入到指定位置,其右边的数据右移1位
public void add(int index, E element) {
         //检查索引,不能小于0,不能大于size
        rangeCheckForAdd(index);
		//确保容量
        ensureCapacityInternal(size + 1);
        //将index右边的数据右移
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
         //插入数据              
        elementData[index] = element;
        //增加size
        size++;
    }

在指定位置添加元素的方法中分为5步:

  1. 检查索引,不能小于0,不能大于size
  2. 将容量+1,确保容量
  3. 将index右边的数据右移
  4. 在index位置插入数据
  5. 增加size

2.3 get()方法:

public E get(int index) {
		//检查索引范围
        rangeCheck(index);
		//返回指定位置元素
        return elementData(index);
    }

3.LinkedList源码分析

3.1 基础变量:
LinkedList内部是一个双端链表结构,有两个变量,first指向链表头部,last指向链表尾部。
LinkedtList内部的成员变量如下:

transient int size = 0;

transient Node<E> first;//first指向链表头部

transient Node<E> last;//last指向链表尾部。

3.2添加操作:

	add(E e)用于将元素添加到链表尾部,实现如下:
public boolean add(E e) {
        linkLast(e);
        return true;
    }

void linkLast(E e) {
        final Node<E> l = last;//指向链表的尾部
        //以尾部为前驱节点创建一个新节点,新建节点的l
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;//将链表尾部指向新节点
        if (l == null)//如果链表为空,那么该节点既是头节点也是尾节点
            first = newNode;
        else//链表不为空,那么将该结点作为原链表尾部的后继节点
            l.next = newNode;
        size++;//增加尺寸
        modCount++;
    }

从上面代码可以看到,linkLast方法中就是一个链表尾部添加一个双端节点的操作,但是需要注意对链表为空时头节点的处理。

add(int index,E e)
add(int index,E e)用于在指定位置添加元素。实现如下:

public void add(int index, E element) {
		//检查索引是否处于[0-size]之间
        checkPositionIndex(index); 

        if (index == size)//添加在链表尾部
            linkLast(element);
        else//添加在链表中间
            linkBefore(element, node(index));
    }

从上面代码可以看到,主要分为3步:

  1. 检查index的范围,否则抛出异常
  2. 如果插入位置是链表尾部,那么调用linkLast方法
  3. 如果插入位置是链表中间,那么调用linkBefore方法

linkLast方法前面已经讨论了,下面看一下linkBefore的实现。在看linkBefore之前,先看一下node(int index)方法,该方法返回指定位置的节点,实现如下:

Node<E> node(int index) {
        // assert isElementIndex(index);

        //如果索引位置靠链表前半部分,从头开始遍历
        //size>>1 相当于size/2 整除2 计算链表
        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;
        }
    }

添加操作总结
LinkedList由于实现了List和Deque接口,所以有多种添加方法,下面总结了一下。

  • 将数据插入到链表尾部
  • boolean add(E e):
  • void addLast(E e)
  • boolean offerLast(E e)
  • 将数据插入到链表头部
  • void addFirst(E e)
  • boolean offerFirst(E e)
  • 将数据插入到指定索引位置
  • boolean add(int index,E e)

3.3 查找元素:get(E e)

 public E get(int index) {
 		//检查边界
        checkElementIndex(index);
        return node(index).item;
    }

3.4 获得位置为0的头节点数据

LinkedList中有多种方法可以获得头节点的数据,实现大同小异,区别在于对链表为空时的处理,是抛出异常还是返回null。主要方法有getFirst()、element()、peek()、peekFirst()、方法。其中getFirst()和element()方法将会在链表为空时,抛出异常,它们的实现如下:

public E getFirst() {
        final Node<E> f = first;
        if (f == null)
            throw new NoSuchElementException();
        return f.item;
    }
public E element() {
        return getFirst();
    }

从代码可以看到,element()方法的内部就是使用getFirst()实现的。它们会在链表为空时,抛出NoSuchElementException

peek()和peekFirst()的实现:

public E peek() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
    }

public E peekFirst() {
        final Node<E> f = first;
        return (f == null) ? null : f.item;
     }

当链表为空时,peek()和peekFirst()方法返回null

3.5 获得位置为size-1的尾节点数据

public E getLast() {
        final Node<E> l = last;
        if (l == null)
            throw new NoSuchElementException();
        return l.item;
    }
 public E peekLast() {
        final Node<E> l = last;
        return (l == null) ? null : l.item;
    }

getLast()方法在链表为空时,会抛出NoSuchElementException,而peekLast()则不会,只是会返回null

检索操作总结
检索操作分为按照位置得到对象以及按照对象得到位置两种方式,其中按照对象得到位置的方法有indexOf()和lastIndexOf();按照位置得到对象有如下方法:

  • 根据任意位置得到数据的get(int index)方法,当index越界会抛出异常
  • 获得头节点数据
  • getFirst()和element()方法在链表为空时会抛出NoSuchElementException
  • peek()和peekFirst()方法在链表为空时会返回null
  • 获得尾节点数据
  • getLast()在链表为空时会抛出NoSuchElementException
  • peekLast()在链表为空时会返回null

3.6 删除指定对象
当删除指定对象时,只需调用remove(Object o)即可,不过该方法一次只会删除一个匹配的对象,如果删除了匹配对象,返回true,否则false

public boolean remove(Object o) {
        //如果删除对象为null
        if (o == null) {
            //从前向后遍历
            for (Node<E> x = first; x != null; x = x.next) {
                //一旦匹配,调用unlink()方法和返回true
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            //从前向后遍历
            for (Node<E> x = first; x != null; x = x.next) {
                //一旦匹配,调用unlink()方法和返回true
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }

由于LinkedList可以存储null元素,所以对删除对象以是否为null做区分。然后从链表头开始遍历,一旦匹配,就会调用unlink()方法将该节点从链表中移除。下面是unlink()方法的实现:

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ArrayList的时间复杂度分析主要包括插入、删除和访问操作的时间复杂度。 对于插入和删除操作,如果是在数组的末尾进行操作,时间复杂度为O(1),因为只需要将元素添加到数组的末尾或者从数组的末尾删除元素。然而,如果是在数组的中间进行插入或删除操作,需要将后面的元素向后移动或者向前移动,所以时间复杂度为O(n),其中n是数组的长度。 对于访问操作,由于ArrayList底层使用数组实现,可以直接通过索引访问数组中的元素,所以时间复杂度为O(1)。 另外,当ArrayList需要扩容时,会创建一个新的数组,并将原有数组中的元素拷贝到新数组中。根据引用中的介绍,ArrayList的扩容时间复杂度为O(n),其中n是数组的长度。 综上所述,ArrayList的插入和删除操作的时间复杂度为O(n),访问操作的时间复杂度为O(1),扩容操作的时间复杂度为O(n)。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [算法 | JavaArrayList扩容时时间复杂度是多少?](https://blog.csdn.net/BASK2311/article/details/128464628)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [[集合]ArrayListLinkedList源码和时间复杂度](https://blog.csdn.net/weixin_39552097/article/details/120913160)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值