《Java》ArrayList和LinkedList的区别

目录

 ArrayList

源码分析

基本属性 

初始化

新增元素

删除元素

遍历元素

LinkedList

实现类

节点查找

新增元素

删除元素 

遍历元素 

总结


今天依旧是回顾之前落下的知识总结


 ArrayList


源码分析

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

ArrayList实现了List接口,继承了AbstractList抽象类,底层是数组实现的,并且实现了自增扩容数组大小。

ArrayList还实现了Cloneable接口和Serializable接口,所以他可以实现克隆和序列化。

ArrayList还实现了RandomAccess接口,这个接口是一个标志接口,他标志着“只要实现该接口的List类,都能实现快速随机访问”。


基本属性 

ArrayList属性主要由数组长度size、对象数组elementData、初始化容量default_capacity等组成, 其中初始化容量默认大小为10。

//默认数组初始化的长度
private static final int DEFAULT_CAPACITY = 10;
//数组对象
transient Object[] elementData;
//数组中有效元素的个数
private int size;

 从ArrayList属性来看,elementData被关键字transient修饰了,transient关键字修饰该字段则表示该属性不会被序列化。


初始化

ArrayList有三种初始化方式:无参初始化、指定大小初始化、指定初始数据初始化 

//无参的构造方法
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//指定数组大小的构造方法
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);
    }
}
//指定初始数据初始化的构造方法
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;
    }
}

当ArrayList新增元素时,如果所存储的元素已经超过其已有大小,它会计算元素大小后再进行动态扩容,数组的扩容会导致整个数组进行一次内存复制。 

注意点:ArrayList 无参构造器初始化时,默认大小是空数组,并不是大家常说的 10,10 是在第一次 add 的时候扩容的数组值。


新增元素

ArrayList新增元素的方法有两种,一种是直接将元素加到数组的末尾,另外一种是添加元素到任意位置。 

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

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

两个方法是有相同点的:在每次进行添加元素之前都会先判断数组大小是否足够大,如果足够元素放得下,那么就不会进行扩容,如果数组大小不够放下新增元素,那么会进行1.5倍扩容,在扩容后会将数组内容重新复制到新分配的地址内存 


删除元素

ArrayList 删除元素有很多种方式,比如根据数组索引删除、根据值删除或批量删除等等,原理和思路都差不多。

ArrayList在每一次有效的删除元素操作之后,都要进行数组的重组,并且删除的元素位置越靠前,数组重组的开销就越大。


遍历元素

由于ArrayList是基于数组实现的,所以在获取元素的时候是非常快捷的。 


LinkedList


LinkedList是基于双向链表数据结构实现的。

这个双向链表结构,链表中的每个节点都可以向前或者向后追溯,有几个概念如下:

  • 链表每个节点我们叫做 Node,Node 有 prev 属性,代表前一个节点的位置,next 属性,代表后一个节点的位置;
  • first 是双向链表的头节点,它的前一个节点是 null。
  • last 是双向链表的尾节点,它的后一个节点是 null;
  • 当链表中没有数据时,first 和 last 是同一个节点,前后指向都是 null;
  • 因为是个双向链表,只要机器内存足够强大,是没有大小限制的。
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;
        }
}

实现类

LinkedList类实现了List接口、Deque接口,同时继承了AbstractSequentialList抽象类,LinkedList既实现了List类型又有Queue类型的特点;LinkedList也实现了Cloneable和Serializable接口,同ArrayList一样,可以实现克隆和序列化。

由于LinkedList存储数据的内存地址是不连续的,而是通过指针来定位不连续地址,因此,LinkedList不支持随机快速访问,LinkedList也就不能实现RandomAccess接口。

public class LinkedList
    extends AbstractSequentialList
    implements List, Deque, Cloneable, java.io.Serializable

节点查找

 链表查询某一个节点是比较慢的,需要挨个循环查找才行

值得提到的是

LinkedList 并没有采用从头循环到尾的做法,而是采取了简单二分法,首先看看 index 是在链表的前半部分,还是后半部分。

如果是前半部分,就从头开始寻找,反之亦然。通过这种方式,使循环的次数至少降低了一半,提高了查找的性能。


新增元素

LinkedList添加元素的实现很简洁,但添加的方式却有很多种。

默认的add (Ee)方法是将添加的元素加到队尾,首先是将last元素置换到临时变量中,生成一个新的Node节点对象,然后将last引用指向新节点对象,之前的last对象的前指针指向新节点对象。

LinkedList也有添加元素到任意位置的方法,如果我们是将元素添加到任意两个元素的中间位置,添加元素操作只会改变前后元素的前后指针,指针将会指向添加的新元素,所以相比ArrayList的添加操作来说,LinkedList的性能优势明显。


删除元素 

 在LinkedList删除元素的操作中,我们首先要通过循环找到要删除的元素,如果要删除的位置处于List的前半段,就从前往后找;若其位置处于后半段,就从后往前找。

这样做的话,无论要删除较为靠前或较为靠后的元素都是非常高效的,但如果List拥有大量元素,移除的元素又在List的中间段,那效率相对来说会很低。


遍历元素 

 LinkedList的获取元素操作实现跟LinkedList的删除元素操作基本类似,通过分前后半段来循环查找到对应的元素,但是通过这种方式来查询元素是非常低效的,特别是在for循环遍历的情况下,每一次循环都会去遍历半个List。


总结

1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据,而ArrayList移动数据会产生大量的开销。

猜你喜欢

转载自blog.csdn.net/m0_69996872/article/details/128442128