ArrayList与LinkedList区别

一、List

public interface List<E> extends Collection<E>

List是一个有序集合(也称为序列),其特点是有序可重复
在这里插入图片描述
从上图可以看出List 继承自Collection接口,除了复用了Collection接口定义的方法,还定义了自己的方法,如下所示:

  1. 将指定集合中的所有元素插入到此列表中的指定位置

    boolean addAll(int index, Collection<? extends E> c);

  2. 将此列表的每个元素替换为将运算符应用于该元素的结果(从jdk8开始)

    default void replaceAll(UnaryOperator<E> operator) {
         Objects.requireNonNull(operator);
         final ListIterator<E> li = this.listIterator();
        while (li.hasNext()) {
              li.set(operator.apply(li.next()));
         }
    }
    举例如下:

      ArrayList<Integer> integers = new ArrayList<>();
            integers.add(1);
            integers.add(2);
            integers.add(3);
    
            //将集合中的元素分别乘以10
            integers.replaceAll(i -> i * 10);
    
            integers.forEach(s->{
                System.out.println(s);
            });
            
             /* 输入结果如下:
            10
            20
            30
            */
    
  3. 根据Comparator对集合进行排序

    default void sort(Comparator<? super E> c) {
         Object[] a = this.toArray();
         Arrays.sort(a, (Comparator) c);
         ListIterator<E> i = this.listIterator();
         for (Object e : a) {
              i.next();
              i.set((E) e);
         }
    }

  4. 根据索引获取集合元素

    E get(int index);

  5. 根据索引修改集合对应元素

    E set(int index, E element);

  6. 根据索引为集合添加元素
    将当前位于该位置的元素(如果有)和任何后续元素向右移动(在其索引中添加 1)。

    void add(int index, E element);

  7. 根据索引移除集合元素
    将任何后续元素向左移动(从其索引中减去 1)。

    E remove(int index);

  8. 返回此集合中首次出现的指定元素的索引,如果此列表不包含该元素,则返回 -1

    int indexOf(Object o);

  9. 返回此集合中指定元素的最后出现的索引,如果此列表不包含该元素,则返回 -1

    int lastIndexOf(Object o);

  10. 返回一个列表迭代器

    ListIterator<E> listIterator();

  11. 从指定索引开始返回一个列表迭代器
    返回此列表中元素的列表迭代器(按正确的顺序),从列表中的指定位置开始。指定的索引指示对 next 的初始调用将返回的第一个元素。对 previous 的初始调用将返回具有指定索引减 1 的元素。
    ListIterator<E> listIterator(int index);

  12. 返回此集合在指定的 fromIndex(含)和 toIndex(不含)之间的部分的视图

    List<E> subList(int fromIndex, int toIndex);

    注意:此处对subList返回的集合进行操作会影响到原来的集合

      ArrayList<Integer> integers = new ArrayList<>();
            integers.add(1);
            integers.add(2);
            integers.add(3);
    
            List<Integer> subList = integers.subList(0, 1);
            subList.add(99);
    
            integers.forEach(System.out::println);
            System.out.println("========================");
            subList.forEach(System.out::println);
            
           /* 输出结果:
            1
            99
            2
            3
            ========================
            1
            99
            */
    

二、ArrayList

1、基本定义

ArrayList是List 接口的 可动态调整数组大小的实现。实现所有可选的 list 操作,并允许所有元素,包括 null。
在这里插入图片描述

2、主要特点

  • ArrayList是基于object数组的,在初始化ArrayList时,会构建空数组(Object[] elementData={})。

  • ArrayList是一个无序的,它是按照添加的先后顺序排列,当然,他也提供了sort方法,如果需要对ArrayList进行排序,只需要调用这个方法,提供Comparator比较器即可。

  • size、isEmpty、get、set、iterator 和 listIterator 操作以恒定时间 O ( 1 ) O(1) O(1)运行。

       例如,访问数组中的一个特定元素(例如 array[index])通常是一个 O(1)操作,因为无论数组有多大,你都可以在相同的时间内访问它。相比之下,遍历整个数组通常是一个 O(n) 操作,因为需要遍历的元素数量与数组的大小成正比。
    
  • add 操作在末尾添加元素是在摊销常数时间 O ( 1 ) O(1) O(1)内运行,即添加 n 个元素需要 O ( n ) O(n) On 时间。所有其他操作都以线性时间运行(粗略地说)

3、数组扩容

private static final int DEFAULT_CAPACITY = 10;

如果是第一次添加元素,数组的长度被扩容到默认的capacity,也就是10.
关于扩容分两种:

  • ①原来数组的长度为10,新加的元素长度 < 10/2 ,那么扩容后长度为(10+10/2) =15
  • ②原来数组的长度为10,新加的元素长度 > 10/2 ,那么扩容后长度为(10+新加元素长度)

4、线程安全

ArrayList 是一个非常常用的列表类,但它是线程不安全的。当多个线程同时对 ArrayList 进行修改操作时,可能会导致数据不一致、数组越界异常等问题。

如何保证线程安全:

  1. 使用 Vector 类:Vector 是 ArrayList 的一个线程安全的变体,它的方法都是同步的,但性能较低。
  2. 使用 Collections.synchronizedList 方法:这个方法可以将现有的 ArrayList 包装成一个线程安全的列表。使用时需要注意,迭代器的遍历操作也需要手动同步。
  3. 手动同步:可以在操作 ArrayList 的代码块上使用 synchronized 关键字或显式锁来保证线程安全。
  4. 避免共享:如果可能,避免在多个线程间共享同一个 ArrayList 实例。

三、LinkedList

1、基本定义

LinkedList 是一个实现了 List 接口的双向链表。与 ArrayList 不同,LinkedList 的每个元素都由一个节点表示,每个节点都包含数据和指向前一个节点以及后一个节点的引用。这种数据结构使得在列表的任何位置添加或删除元素都变得高效,因为这些操作只需要改变几个节点的引用。
在这里插入图片描述

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

2、主要特点

  • 动态数组:LinkedList 内部使用链表结构,可以动态地增长和缩小。
  • 双向链表:每个元素都有指向前一个和后一个元素的引用,这使得向前或向后遍历都很方便。
  • 内存占用:相比于 ArrayList,LinkedList 在每个元素上都有一定的额外内存开销,因为它需要存储额外的引用。
  • 操作性能:在列表的头部或中间插入或删除元素时,LinkedList 通常比 ArrayList 更高效,因为不需要像 ArrayList 那样移动元素来维护数组的连续性。

3、链表扩容

对于 LinkedList,添加元素的操作通常只需要创建一个新的节点,并将其链接到链表中的适当位置。由于 LinkedList 是一个链式数据结构,它不需要像数组那样预留固定大小的空间,因此不会因为容量问题而导致性能瓶颈。

在 LinkedList 中添加元素通常涉及以下步骤:

  • 创建新节点:创建一个新的节点,并将元素存储在这个节点中。
  • 链接节点:将新节点插入到链表中的适当位置。如果是在末尾添加,就将新节点的 next 引用设置为 null,并将链表中最后一个节点的 previous 引用指向新节点。如果是在头部添加,就更新链表的头节点引用。
  • 更新引用:如果添加的位置在链表中间,还需要更新前一个节点的 next 引用和后一个节点的 previous 引用

总的来说,LinkedList 不需要扩容,因为它不是基于数组实现的。它的动态性质使得在任何位置添加或删除元素都相对高效,但这种数据结构在内存使用和遍历速度上可能不如 ArrayList 高效。

4、线程安全

和 ArrayList 一样,LinkedList 也是非线程安全的。在多线程环境中,直接使用 LinkedList 可能会导致数据不一致和其它线程安全问题。

如何保证线程安全:

  1. 使用 Collections.synchronizedList 方法:这个方法可以将 LinkedList 包装成一个线程安全的列表,但需要注意,迭代器的遍历操作也需要手动同步。
  2. 手动同步:在操作 LinkedList 的代码块上使用 synchronized 关键字或显式锁来保证线程安全。
  3. 避免共享:如果可能,避免在多个线程间共享同一个 LinkedList 实例。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值