在Java的List类型集合中,ArrayList和LinkedList大概是最常用到的2个了,细看了一下它们的实现,发现区别还是很大的,这里简单的列一下个人比较关心的区别。

类声明

ArrayList

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

LinkedList

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

二者的定义有些相近,除了都实现List、Cloneable和Serializable以外,继承的类不一样,以及接口有细微的区别。

public abstract class AbstractSequentialList<E> extends AbstractList<E>

AbstractSequentialList也继承自AbstractList,它只是多了一些实现的方法,参照API的doc,这个类用于按顺序访问的List的实现,所谓顺序访问(sequential access),可以与随即访问(random access)的ArrayList对比去理解。

Deque是一个双向(double ended queue)的Queue的接口,因为这个接口的区别,LinkedList里实现的方法要比ArrayList多一些。

元素存储方式

ArrayList:采用数组方式

private transient Object[] elementData;

LinedList:采用链表

 private transient Entry<E> header = new Entry<E>(null, null, null);
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;
}
}

很好理解,从字面都可以理解出来,一个是数组实现,一个是链表实现。

元素添加

二者都有几个add()方法,

void add(E item)  向滚动列表的末尾添加指定的项。 void add(E item, int index)  向滚动列表中索引指示的位置添加指定的项。

先看看ArrayList的实现:

 public void add(int index, E element) {
if (index > size || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
ensureCapacity(size+1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
} public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}

对于add(E e)方法,非常简单,首先确保数组容量,然后直接赋值。在不需要扩充数组容量的情况下,效率非常高,而一旦需要数组扩容,代价就会上升:

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

因为它需要将已有的数组复制到新的数组里去。由此便可以想到一个提高add()效率的方法,在一开始尽量设定一个合理的数组容量,那么可以有效地减少数组的扩容和大量的复制。

对于add(int index, E e),比起add(E e),多一个可能的复制操作,这样才能保证在合理的位置插入新的元素。

LinkedList的实现:

 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;
} public void add(int index, E element) {
addBefore(element, (index==size ? header : entry(index)));
} 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;
}

粗略看起来要复杂一些,因为LinkedList同时还是一个Deque(JDK 1.6新添加的),所以它的实现也要兼顾双向队列。

下面从一个空的LinkedList开始,看看新的元素是如何添加进来的:

 List<Integer> ints = new LinkedList<Integer>();
ints.add(1);
ints.add(2);
ints.add(3);
System.out.println(ints); //[1, 2, 3]

下面一步一步看List内部header和元素之间的关系:

  • 初始化: header.element = null; header.next=header.previous=header 这里是一个环状的结构,自己的p和n指针都指向自己

  • 添加第一个元素“1”:header.element=null;header.next=1;header.previous=1; 2个元素相互连接
  • 添加第二个元素“2” 这里很明显看来了,是一个环状结构
  • 添加第三个元素“3” 既然是一个环状,干脆用圆形显示好了,貌似画的不太圆。。。

这里总结一下两种的差别:

  • 对于元素的add()来说,LinkedList要比ArrayList要快一些,因为ArrayList可能需要额外的扩容操作,当然如果没有扩容,二者没有很大的差别
  • 对于元素的add(int, element),对于LinkedList来说,代价主要在遍历获取插入的位置的元素,而ArrayList的主要代价在于可能有额外的扩容和大量元素的移动
  • 小结:对于简单的元素添加,如果事先知道元素的个数,采用预置大小的ArrayList要更好,反之可以考虑LinkedList

元素移除

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;
} public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
} return false; } /* * Private remove method that skips bounds checking and does not * return the value removed. */ private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // Let gc do its work
}

remove(int)和remove(Object)两种方式的返回值是有区别的哦

对于ArrayList来说,主要是的仍然会有元素的移动(这里就是数组的复制),虽然采用的是System的arrayCopy,但是本质上还是复制的思路。还有一点需要注意的是,remove(Object)对null值进行单独处理,这里也说明ArrayList是可以存取null的。

LinkedList元素移除:

 public E remove(int index) {

      return remove(entry(index));

  }

  /**

   * Returns the indexed entry.

   */

  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;

  }

 public boolean remove(Object o) {

      if (o==null) {

          for (Entry<E> e = header.next; e != header; e = e.next) {

              if (e.element==null) {

                  remove(e);

                  return true;

              }

          }

      } else {

          for (Entry<E> e = header.next; e != header; e = e.next) {

              if (o.equals(e.element)) {

                  remove(e);

                  return true;

              }

          }

      }

      return false;

  }

这里的实现就是典型的链表删除的实现,其中有几个细节需要提一下:

  • modCount的处理,这个变量是用来存储List的修改的次数的,仅仅存储添加和删除的操作此书,用来在Iterator中判断List的状态和行为,防止不同步的修改,抛出ConcurrentModificationException
  • 通过索引访问元素的实现entry(int),这里有一个小细节,
     if (index < (size >> 1)) {

如果元素的位置在前半段,那么通过next指针查找,否则通过previous指针查找。这一行代码有2个值得学习的地方,第一查找的优化,根据位置判断查找的方向,第二移位操作的运用。不得不佩服Bloch的编程功底。

小结一下:

删除操作中,LinkedList更有优势,一旦找到了删除的节点,它仅仅只是断开链接关系,并没有元素复制移动的行为,而ArrayList不可避免的又要进行元素的移动。

元素索引

indexOf(Object o)  回此列表中第一次出现的指定元素的索引;如果此列表不包含该元素,则返回 -1。

ArrayList的实现:

 public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i; } else {
for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; }
return -1;
}

LinkedList的实现:

 public int indexOf(Object o) {

     int index = 0;

     if (o==null) {

         for (Entry e = header.next; e != header; e = e.next) {

             if (e.element==null)

                 return index;

             index++;

         }

     } else {

         for (Entry e = header.next; e != header; e = e.next) {

             if (o.equals(e.element))

                 return index;

             index++;

         }

     }

     return -1;

 }

ArrayList:基于数组的遍历查找

LinkedList:基于链表的遍历查找

按照对象在内存中存储的顺序去考虑,数组的访问要比链接表快,因为对象都存储在一起。

遍历

基于以上的分析,可以得出,按照索引遍历,ArrayList是更好的选择,按照Iterator遍历,也许LinkedList会好一些。

反过来理解,如果是ArrayList,Iterator和index遍历都可以,如果是LinkedList,优先选择Iterator比较好。

27、ArrayList和LinkedList的区别的更多相关文章

  1. LintCode Reverse LinkedList (ArrayList 和 LinkedList 的区别)

    1. ArrayList 和 LinkedList 的区别 http://pengcqu.iteye.com/blog/502676 2. How to reverse LinkedList http ...

  2. 你真的说的清楚ArrayList和LinkedList的区别吗

    参见java面试的程序员,十有八九会遇到ArrayList和LinkedList的区别?相信很多看到这个问题的人,都能回答个一二.但是,真正搞清楚的话,还得花费一番功夫. 下面我从4个方面来谈谈这个问 ...

  3. java集合框架05——ArrayList和LinkedList的区别

    前面已经学习完了List部分的源码,主要是ArrayList和LinkedList两部分内容,这一节主要总结下List部分的内容. List概括 先来回顾一下List在Collection中的的框架图 ...

  4. Java中ArrayList与LinkedList的区别

    Java中ArrayList与LinkedList的区别 一般大家都知道ArrayList和LinkedList的区别: 1. ArrayList的实现是基于数组,LinkedList的实现是基于双向 ...

  5. Java进阶(十七)ArrayList与LinkedList的区别

    ArrayList与LinkedList的区别 ArrayList ArrayList其实是包装了一个数组 Object[],当实例化一个ArrayList时,一个数组也被实例化,当向ArrayLis ...

  6. 【转】ArrayList与LinkedList的区别和适用场景

    ArrayList 优点:ArrayList是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询操作效率会比较高(在内存里是连着放的). 缺点:因为地址连续,当要插入和删除时,Arra ...

  7. JAVA中ArrayList与LinkedList的区别以及对应List使用foreach与使用下标遍历的效率问题

    近期在做一个对接京东的电商平台,所以对各个地方的效率考虑的比较多,今天深挖了一下ArrayList与LinkedList的区别以及对应List使用foreach与使用下标遍历的效率问题,首先说一下两种 ...

  8. 理解ArrayList与LinkedList的区别

    一.先来看看ArrayList与LinkedList 在JDK中所在的位置 从图中可以看出,ArrayList与LinkedList都是List接口的实现类,因此都实现了List的所有未实现的方法,只 ...

  9. java集合框架之ArrayList与LinkedList的区别

    参考http://how2j.cn/k/collection/collection-arraylist-vs-linkedlist/690.html#nowhere ArrayList和LinkedL ...

  10. Java自学-集合框架 ArrayList和LinkedList的区别

    ArrayList和LinkedList的区别 步骤 1 : ArrayList和LinkedList的区别 ArrayList ,插入,删除数据慢 LinkedList, 插入,删除数据快 Arra ...

随机推荐

  1. 微软正式发布VS2015和.Net为开发者提供数百个新功能

    今天,我很高兴地向大家宣布:Visual Studio 2015 和 .Net 4.6 的正式版本现已提供下载! 自去年十一月我们提出了微软开发技术的愿景:让所有开发者,无论他在什么平台,开发哪种应用 ...

  2. OLEDB Excel 与C# 的数据流通方法

    一.             名词解释: OleDbCommand 是对数据源执行各种操作的SQL语句或者存储过程,连接access.excel等数据文件进行数据操作时候用到的,其实和sqlcomma ...

  3. Extjs Ajax 分页

    var storeCpye = new Ext.data.GroupingStore({ proxy : new Ext.data.HttpProxy({ url : 'cxgl_cpye.app?d ...

  4. AutoMapper: Mapper.Initialize() 只能调用一次,Why?

    最开始的代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; using S ...

  5. pytest文档25-conftest.py作用范围

    前言 一个测试工程下是可以有多个conftest.py的文件,一般在工程根目录放一个conftest.py起到全局作用. 在不同的测试子目录也可以放conftest.py,作用范围只在该层级以及以下目 ...

  6. docker Redis的主从配置

    redis是k-v型nosql数据库,支持字符串(string).列表(list).集合(set).散列(hash).有序集合(zset:形如member:score的散列集合,其中member为成员 ...

  7. git 经常使用操作集锦

    创建仓库 新建普通仓库: jxdong@ubuntu-server:~/workspace/git$ git init Reinitialized existing Git repository in ...

  8. [PHP] ubuntu16.04下 Phpstorm发布项目到apache

    reference to : http://blog.csdn.net/qq_23937195/article/details/72953308 在网上找的不靠谱,倒腾了大半天的,终于找到正确姿势QA ...

  9. [MAC OS] 解压Assets.car获取资源图片

    reference to  : http://www.jianshu.com/p/a5dd75102467 今天想获取APP的资源,但是查看xxx.app文件夹里面,缺少了大部分资源.在文件夹里面发现 ...

  10. [转]C++之运算符重载(2)

    上一节主要讲解了C++里运算符重载函数,在看了单目运算符(++)重载的示例后,也许有些朋友会问这样的问题.++自增运算符在C或C++中既可以放在操作数之前,也可以放在操作数之后,但是前置和后置的作用又 ...