常用的两个List——ArrayList和LinkedList基础用法和比较

本文详细对比了ArrayList与LinkedList这两种Java集合框架中List接口的具体实现,包括它们的数据结构、添加、插入、删除等基本操作的原理及性能分析。

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

一 、ArrayList基础使用

1、ArrayList结构:

顾名思义ArrayList的结构就是数组型的List。

2、添加元素:

public class Main {

    public static void main(String[] args) {
      ArrayList<String> arrayList=new ArrayList<>();
      arrayList.add("s");
        System.out.println(arrayList);
    }
}

运行结果:

[s]

咱们看一下add的源码:

public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

这样就很清楚具体的过程了,先检查数组的容量是否足够,用ensureCapacityInternal()函数检查,如果不够则会:

elementData = Arrays.copyOf(elementData, newCapacity);

将原数组复制到一个容量为newCapacity的新数组里以扩充容量,然后再将元素e添加到到数组末尾。

过程很清晰了,所以其缺点也就很明显了,当数组容量不够时需要复制生成新的数组这个开销挺大的。

3、添加元素到任意位置:

public class Main {

    public static void main(String[] args) {
      ArrayList<String> arrayList=new ArrayList<>();
      arrayList.add("a");
      arrayList.add("b");
      arrayList.add("c");
      arrayList.add(0,"d");//将d插入到位置0
      System.out.println(arrayList);
    }
}

运行结果:

[d, a, b, c]

其中的细节为从将要插入的下标开始到数组最后一个元素整体向后移一格即为复制过程,所以该方式的缺点也十分明显,如果需要将一个元素插入到一个较大数组的前排位置,则需要复制大量元素,开销会很大。

4、删除某个元素

public class Main {

    public static void main(String[] args) {
      ArrayList<String> arrayList=new ArrayList<>();
      arrayList.add("a");
      arrayList.add("b");
      arrayList.add("c");
      arrayList.add(0,"d");
      System.out.println(arrayList);

      arrayList.remove(0);//删除下标为0的元素
      System.out.println(arrayList);
    }
}

运行结果:

[d, a, b, c]
[a, b, c]

删除过程和上一个插入任何位置过程类似,当删除某个下标的元素后,需要该下标后面的元素都前移一格即复制过程,所以当一个很大的数组需要删除前排某个元素的时候会产生很大的开销。


二、LinkedList基础使用

1、LinkedList结构

顾名思义,Linked为连接的意思所以LinkedList是以链表方式实现的List。

2、添加元素:

public class Main {

    public static void main(String[] args) {
        LinkedList<String> linkedList=new LinkedList<>();
        linkedList.add("a");
        linkedList.add("b");
        linkedList.add("c");
        System.out.println(linkedList);
    }
}

运行结果:

[a, b, c]

咱们看一下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++;
    }

从这段代码中可以很清楚地看出LinkedList是一条双链表,往里面添加元素需要新建一个Node对象然后连接好前后对象,这样的好处是不需要像ArrayList那样考虑容量问题,但是如果添加很多元素进来这样的方式开销也不小。

3、添加元素到任意位置:

public class Main {

    public static void main(String[] args) {
        LinkedList<String> linkedList=new LinkedList<>();
        linkedList.add("a");
        linkedList.add("b");
        linkedList.add("c");
        System.out.println(linkedList);

        linkedList.add(0,"d");//把d插入到下标为0处
        System.out.println(linkedList);
    }
}

运行结果:

[a, b, c]
[d, a, b, c]

根据链表的特点,需要先找到要插入位置的后一个Node,因为链表没有真正的下标:

   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)) {  //根据index大小分成从头或从尾开始找
            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; //找到后返回
        }
    }

所以主要开销是花费在找Node上面,当元素很多的情况下,要插入的元素又在中间位置时查找的开销会比较大。

4、删除元素:

public class Main {

    public static void main(String[] args) {
        LinkedList<String> linkedList=new LinkedList<>();
        linkedList.add("a");
        linkedList.add("b");
        linkedList.add("c");
        System.out.println(linkedList);

        linkedList.add(0,"d");
        System.out.println(linkedList);

        linkedList.remove(0);//删除下标为0位置的元素
        System.out.println(linkedList);
    }
}

运行结果:

[a, b, c]
[d, a, b, c]
[a, b, c]

同样的道理根据链表的特点,删除一个元素需要先找到该元素Node,因为链表没有真正的下标:

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

        if (index < (size >> 1)) {  //根据index大小分成从头或从尾开始找
            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; //找到后返回
        }
    }

找到后就修改下前后Node的指针就可以了。如果元素很多而且要删除的元素在中间位置的话可能找起来会花费很多开销。

### JavaArrayListLinkedList的初始容量及扩容机制 #### ArrayList 的初始容量与扩容机制 对于 `ArrayList`,当创建对象时不指定初始容量,默认情况下其初始容量为10[^2]。每当尝试添加元素而当前内部数组无法容纳更多元素时,就会触发扩容操作。扩容策略是将现有容量扩大至原来的1.5倍(即新的容量等于原容量加上原容量的一半),并把原有数据复制到更大的新空间内[^3]。 ```java // 创建一个默认容量为10的ArrayList实例 ArrayList<String> list = new ArrayList<>(); ``` #### LinkedList 的初始容量与扩容机制 不同于 `ArrayList` 使用固定大小的数组作为底层存储结构,`LinkedList` 实际上并不依赖于固定的容量概念,因为它是基于双向链表实现的数据结构[^1]。这意味着每次插入或移除节点时,只需调整相邻结点之间的链接关系即可完成相应操作,无需像 `ArrayList` 那样考虑整体容量的增长问题。因此,在讨论 `LinkedList` 时通常不会提及具体的“初始容量”,而是关注于如何高效地管理动态变化中的节点连接。 然而值得注意的是,虽然 `LinkedList` 不涉及传统意义上的容量扩展过程,但在实际应用过程中仍然存在内存分配行为——每当我们调用方法来增加一个新的节点时,JVM 就会在堆区开辟一块适当的空间用于保存这个新加入的对象实体及其前后指向其他节点的信息字段。 综上所述: - **ArrayList**: 默认初始容量为10;当需要增长时按照约1.5的比例进行扩容。 - **LinkedList**: 没有严格定义的初始容量,依靠链式结构按需分配资源给各个独立节点。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值