`
janson.java
  • 浏览: 29923 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

java数据结构 ArrayList 与 LinkedList 的区别

 
阅读更多
List在数据结构中表现为是线性表的方式,其元素以线性方式存储,集合中允许存放重复的对象,List接口主要的实现类有
ArrayList
ArrayList其实就是一组长度可变的数组,当实例化了一个ArrayList,该数据也被实例化了,当向集合中添加对象时,数组的大小也随着改变,这样它所带来的有优点是快速的随机访问,即使访问每个元素所带来的性能问题也是很小的,但缺点就是想其中添加或删除对象速度慢,当你创建的数组是不确定其容量,所以当我们改变这个数组时就必须在内存中做很多的处理,如你想要数组中任意两个元素中间添加对象,那么在内存中数组要移动所有后面的对象。

LinkedList
LinkedList是通过节点的连接实现链表的数据结构,向linkedList中插入或删除元素的速度是特别快,而随机访问的速度相对较慢,这个是由于链表本身的性质造成的,在链表中,每个节点都包含了前一个节点的引用,后一个节点的引用和节点存储值,当一个新节点插入式,只需要修改其中相关的前后关系节点引用即可,删除节点也是一样。操作对象只需要改变节点的链接,新节点可以存放在内存的任何位置,但也就是因为如此LinkedList虽然存在get()方法,但是这个方法通过遍历节点来定位所以速度很慢。LinkedList还单独具addFrist(),addLast(),getFrist(),getLast(),removeFirst(),removeLast()方法,这些方法使得LinkedList可以作为堆栈,队列,和双队列来使用。

说白了,ArrayList和LinkedList就是数据结构中的顺序存储表和链式存储表。

ArrayList构造原理
上面已经清楚ArrayList和LinkedList就是数据结构的顺序表和链表(不清楚的翻翻数据结构的书),下面简单分析一下它们的实现方式。
下表是摘自sum提供的ArrayList的api文档

ArrayList()
          构造一个初始容量为 10 的空列表。
ArrayList(Collection<? extends E> c)
          构造一个包含指定 collection 的元素的列表,这些元素是按照该 collection 的迭代器返回它们的顺序排列的。
ArrayList(int initialCapacity)
          构造一个具有指定初始容量的空列表。

第一个构造函数是没有默认构建了一个初始容量10的空列表,第二个构造函数是制定collection元素的列表,第三个构造函数是由用户指定构造的列表初始化容量多少,如果使用第一个构造函数则表示默认调用该参数为initialCapacity=10来构造一个列表对象。

ArrayList源码稍微进行分析

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;

    private transient Object[] elementData;

    private int size;

    public ArrayList(int initialCapacity) {
    super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
    this.elementData = new Object[initialCapacity];
    }

    public ArrayList() {
    this(10);
    }

    public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    size = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, size, Object[].class);
    }

    public int size() {
    return size;
    }

}


可以发现ArrayList中包含两个主要的属性

    private transient Object[] elementData;

    private int size;

其中elementData[]是列表的实现的核心数组,我们使用该数组来存放集合中的数据,而我们的构造函数所传递的initialCapacity参数是构建该数组的长度。
在看看size的实现形式,它的作用是返回size的属性值的大小,我们再看看另外一个构造函数public ArrayList(Collection<? extends E> c),该构造函数的作用是把另外一个容器对象中的元素放入当点的List对象中。首先是通过调用另外一个容器对象c的size()来设置当前List对象的size属性的长度大小。接下来就似乎对elementData[]数组进行初始化,最后通过Arrays.copyOf(U[] original, int newLength, Class<? extends T[]> newType)方法把当前容器中的对象都存放进新的数组elementData,主要就完成了一个列表的创建。

ArrayList容量扩充
还有一个问题就是我们所建立的ArrayList是使用数组来实现的,但数组的长度一旦被初始化就不能改变,而我们在给此列表对象添加元素时却没有受到长度的限制,所以,ArrayList的elementData属性一定是存在一个动态扩充容量的机制,下面把相关的部分源码贴出来再做研究

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

    protected transient int modCount = 0;    

    /**
     * Increases the capacity of this <tt>ArrayList</tt> instance, if
     * necessary, to ensure that it can hold at least the number of elements
     * specified by the minimum capacity argument.
     *
     * @param   minCapacity   the desired minimum capacity
     */
    public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity)
        newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
    }

看看public boolean add(E e)方法,可以发现在添加一个元素到容器中的时候,我们会先通过ensureCapacity(size + 1)判断该数组是否需要扩充。
public void ensureCapacity(int minCapacity)这个方法是用来判断当前的数组是否需要扩充,并且该扩充多少。modCount++; 表示当前的对象对elementData数组进行了多少次扩充,清空和移除等操作,相当于是一个对当前List对象的一个操作记录数。
int oldCapacity = elementData.length; 初始化oldCapacity,表示为当前elementData数组的长度。
if (minCapacity > oldCapacity) 判断minCapacity和oldCapacity谁大,来决定是否需要扩充。
int newCapacity = (oldCapacity * 3)/2 + 1; 扩充的策列是判断(oldCapacity * 3)/2 + 1和minCapacity两者之间谁更大,取更大的数作为扩充后数组的initialCapacity值,然后使用数组拷贝的方式,把以前的数据转移到新的数组对象中
如果minCapacity 小于 oldCapacity 就不需要再扩充。

ArrayList删除元素

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                 numMoved);
    elementData[--size] = null; // Let gc do its work

    return oldValue;
    }

    private void RangeCheck(int index) {
    if (index >= size)
        throw new IndexOutOfBoundsException(
        "Index: "+index+", Size: "+size);
    }

在看看ArrayList移除元素是怎么实现的,首先判断需要删除的index是否在elementData数组的下标内,如不存在则抛出IndexOutOfBoundsException。
modCount++; 与扩充元素一个,删除元素也记下来操作数。
E oldValue = (E) elementData[index]; 获取需要删除元素的对象。
int numMoved = size - index - 1; 获取需要被删除元素的下标,删除该元素后,数组需要在此元素下标后的所有对象进行内存的移动。
System.arraycopy(elementData, index+1, elementData, index,numMoved);对numMoved后面的所有对象通过copy的方式进行内存的移动重新构建数组。

说完ArrayList的实现,再说说linkedList

构建双链表(LinkedList)
LinkedList是类似于C语言的双链表,双链表比单链表多了一个域,这个双链表就有了三个域,一个域来用存储数据元素,一个用来指向后续节点,另一个是指向结点的直接前驱节点。

public class LinkedList<E>
    extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{
    private transient Entry<E> header = new Entry<E>(null, null, null);
    private transient int size = 0;

    public LinkedList() {
        header.next = header.previous = header;
    }

    private static class Entry<E> {
    E element;
    Entry<E> next;
    Entry<E> previous;

    Entry(E element, Entry<E> next, Entry<E> previous) {
        this.element = element;
        this.next = next;
        this.previous = previous;
    }
    }

}

在Entry类中,定义了三个属性,分别为E element 表示数据与,Entry<E> next为后续指针域,Entry<E> previous为前驱指针域。
在LinkedList中定义了一个重要的属性header,头结点,不会纳入链表的总元素,该节点的previous是指向最后节点,next是指向第一节点。
构造函数LinkedList() 构造一个空列表。将header的前驱指针域和后续指针域都指向了自己,看到这里可以发现,next和previous就是一个引用,其实也相等于C里面的指针,不过C不会处理空指针,直接放操作系统处理了,java就直接抛出NullPointerException,根本不让它破坏系统的机会。

LinkedList元素变动
上面说到了LinkedList的新增和删除的效率比ArrayList的高,实际上在 链表操作这些方法时,只需要改变2个节点各自的前驱指针和后续指针域,而ArrayList是需要移动很多的元素。

public boolean add(E e) {
    addBefore(e, header);
        return true;
    }

    private Entry<E> addBefore(E e, Entry<E> entry) {
    Entry<E> newEntry = new Entry<E>(e, entry, entry.previous);
    newEntry.previous.next = newEntry;
    newEntry.next.previous = newEntry;
    size++;
    modCount++;
    return newEntry;
    }

    private E remove(Entry<E> e) {
    if (e == header)
        throw new NoSuchElementException();

        E result = e.element;
    e.previous.next = e.next;
    e.next.previous = e.previous;
        e.next = e.previous = null;
        e.element = null;
    size--;
    modCount++;
        return result;
    }

相比ArrayList的add()方法,LinkedList实现起来非常简单,主要是两行代码:
newEntry.previous.next = newEntry;将上一节点的后续节点指向新增的节点
newEntry.next.previous = newEntry;头节点的前驱节点指向新增节点,size和modCount自增记录。

同样remove的实现也非常简单
e.previous.next = e.next;该节点的后一节点的后去节点指向该节点的后驱节点,
e.next.previous = e.previous;该节点的后一节点的前驱节点指向该节点的前驱节点。
e.next = e.previous = null;把该节点的前驱节点和后驱节点全部指向null。
e.element = null;把该节点的数据域设置为null。

随机访问
相比顺序表,链表的随机访问效率要低得多(理论说法,不是绝对),ArrayList可以根据索引号进行随机访问,而LinkedList则不要遍历访问。

public E get(int index) {
        return entry(index).element;
    }

    private Entry<E> entry(int index) {
        if (index < 0 || index >= size)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+size);
        Entry<E> e = header;
        if (index < (size >> 1)) {
            for (int i = 0; i <= index; i++)
                e = e.next;
        } else {
            for (int i = size; i > index; i--)
                e = e.previous;
        }
        return e;
    }

上列的代码是对一个链表的遍历,其中包含了一个算法,如果给的索引号小于总节点数的一半,则在链表的前半部分第一个节点完进行遍历,如果给的索引号大于总节点数的一半,则从最后一个节点往前进行遍历直到索引号。

最后总结一下ArrayList和LinkedList的各自特点
1.ArrayList是基于线性表的顺序存储表,LinkedList是基本线性表的链表存储表。
2.对于新增和删除元素,LinkedList比较占有优势,只需要变前后2个节点,而ArrayList要移动数据。
3.对于随机访问来说,ArrayList比较占有优势,可以根据索引号快速访问,而LinkedList则需要遍历集合的元素来定位。
4.而对于迭代操作(iterate)和查找操作(indexOf),两者是差不多。
不过上面都是基于理论,具体问题还是要根据事实进行分析,如ArrayList删除的元素刚好是队列的最后一个元素,那么是无需要移动数据的,大体我们可以认为需要随机访问较多的那么比较适合用ArrayList,如果是插入和删除(如消息队列)较多的那么就需要考虑LinkedList。
分享到:
评论
1 楼 793059909 2014-06-13  
从数据库中取对象存放在LinkedList中然后转换成ArrayList进行遍历,发现用的时间是一样的
数据量在20000条左右
就这种场景,有没有好的解决办法

LinkedList到ArrayList:
List llist=new LinkedList();
        ...
ArrayList list=new ArrayList(llist);

相关推荐

    Java中ArrayList和LinkedList区别 时间复杂度 与空间复杂度1

    在 Java 中,ArrayList 和 LinkedList 是两种常用的集合类,它们各自具有不同的特性和适用场景,主要体现在数据结构、访问效率和操作性能上。 1. 数据结构: - ArrayList 实现了一个动态数组,它内部是一个 Object...

    ArrayList LinkedList Vector区别

    ArrayList、LinkedList、Vector 是 Java 中常用的数据结构实现类,它们都实现了 List 接口,但它们在存储方式、性能、线程安全性等方面有着不同特点。 首先,ArrayList 和 Vector 都是采用数组方式存储数据的,这种...

    Java中ArrayList和LinkedList区别

    在Java编程语言中,ArrayList和LinkedList都是集合框架中两种重要的列表实现,它们分别基于不同的数据结构,具有不同的特性和性能特点。以下是对这两个类的详细分析: 1. 数据结构: - ArrayList是基于动态数组的...

    ArrayList和Linkedlist1

    在IT领域,特别是Java编程中,ArrayList和LinkedList是两种非常重要的数据结构,它们都是List接口的实现类。理解这两者的区别对于优化程序性能至关重要。面试官询问这些知识点,旨在评估应聘者的理论基础和实践能力...

    关于arraylist和linkedList的区别

    ### 关于ArrayList与LinkedList的区别 在Java编程语言中,`ArrayList`与`LinkedList`都是`List`接口的具体实现类,用于存储元素集合。虽然它们都实现了同样的接口并且提供了相同的基本功能,但在内部实现机制、性能...

    ArrayList Vector LinkedList 区别与用法.

    ### ArrayList、Vector、LinkedList 的区别与用法详解 在Java编程中,选择合适的数据结构对于程序的性能至关重要。本文将深入探讨ArrayList、Vector和LinkedList三种集合类的特点与使用场景,帮助开发者更好地理解...

    合理运用ArrayList与LinkedList

    为了更好地理解这两种数据结构的性能差异,我们可以参考给定的代码示例。第一个代码示例展示了向ArrayList和LinkedList中插入100000个元素的时间消耗。结果表明,ArrayList的插入操作相对较慢,因为它需要为每个...

    java中ArrayList与LinkedList对比详情

    Java 中 ArrayList 与 LinkedList 对比详情 在 Java 中,ArrayList 和 LinkedList 是两个常用的集合实现方式,它们都实现了 Collection 接口,但是它们之间存在着一些关键的差异。本文将通过实例对比 Java 中 ...

    ArrayList LinkedList Vector性能对比

    3. **内存消耗**:LinkedList比ArrayList和Vector占用更多的内存,因为它需要存储额外的指针来维护链表结构。 4. **初始容量和增长策略**:预估集合大小并合理设置初始容量,可以减少不必要的扩容操作,提高性能。 ...

    Map+List+ArrayList+LinkedList Java源码

    Java编程语言中的`Map`, `List`, `ArrayList` 和 `LinkedList` 是四个核心的数据结构,它们在实际开发中被广泛使用。了解它们的源码对于深入理解Java集合框架的内部工作原理至关重要,尤其是对初学者而言,这有助于...

    ArrayList LinkedList Vector性能测试

    在Java编程语言中,ArrayList、LinkedList和Vector是三种常见的动态数组实现,它们都在java.util包中,用于存储和管理对象的集合。这三个类都实现了List接口,提供了多种操作方法,但它们在内部实现和性能特性上有所...

    java基础--list(ArrayList、LinkedList、匿名类).docx

    【Java基础——List接口详解...在实际开发中,根据具体需求选择合适的数据结构,可以提高程序性能并降低复杂度。同时,掌握如何对List进行排序,无论是通过Comparable接口还是Comparator接口,都能使数据处理更加灵活。

    arraylist-linkedlist-test.zip

    ArrayList和LinkedList是Java集合框架中两种重要的动态数组实现,它们都是List接口的实现类,但它们在存储和操作数据方面有着显著的区别。本文件"arraylist-linkedlist-test.zip"主要探讨了在执行添加和删除元素操作...

    ArrayList-LinkedList-源码.rar

    总的来说,理解ArrayList和LinkedList的源码有助于我们更好地利用这两种数据结构,根据具体需求做出更优的选择。通过深入学习源码,我们可以看到Java集合框架的精妙设计,提升我们的编程技巧和问题解决能力。

    java数据结构与算法中文版

    《Java数据结构与算法中文版》是一本深入探讨编程核心领域的书籍,主要针对Java程序员,旨在提升他们在数据处理和问题解决能力上的技能。这本书详细介绍了数据结构和算法的基础理论及其在Java语言中的实现,是Java...

    比较ArrayList、LinkedList、Vector1

    【ArrayList、LinkedList、Vector对比分析】 1. **List概述** List接口是Java集合框架中的重要组成部分,它...在实际应用中,应根据具体需求来选择适合的数据结构。在处理大量数据或频繁操作时,性能差异会更加显著。

    区分Java中的ArrayList和LinkedList

    然而,它们在数据结构、性能和使用场景方面存在着很大的区别。 ArrayList ArrayList是基于动态数组的数据结构,它实现了长度可变的数组,在内存中分配连续的空间。遍历元素和随机访问元素的效率比较高。ArrayList...

    list集合案例增、删、改、查,ArrayList与LinkedList的区别,LinkedList堆栈/队列的开发

    二、`ArrayList`与`LinkedList`的区别 `ArrayList`和`LinkedList`都是`List`接口的实现,但它们在内部实现和性能上有所不同: 1. 内存结构:`ArrayList`基于动态数组实现,元素存储在连续的内存空间;`LinkedList`...

    当面试官问我ArrayList和LinkedList哪个更占空间时,我这么答让他眼前一亮

    在 Java 中,ArrayList 和 LinkedList 是两个常用的集合类,它们都是 List 接口的实现类,但它们之间有着鲜明的区别。今天,我们将深入探讨这两个集合类的实现原理和比较。 ArrayList 的实现原理 ArrayList 是基于...

    分析Java中ArrayList与LinkedList列表结构的

    ArrayList与LinkedList是Java集合框架中两种常见的列表结构,它们都实现了List接口,但在实现方式和性能上有所不同。 **ArrayList源码分析** ArrayList内部基于动态数组实现,它的核心数据成员是一个Object类型的...

Global site tag (gtag.js) - Google Analytics