浅谈ArrayList和 LinkedList区别
来源:互联网 发布:控制网络连接管理 编辑:程序博客网 时间:2024/06/06 09:59
ArrayList 和 LinkedList 都是List接口的实现类,都实现了List接口相关的方法.
但是两者实现的逻辑完全不同,ArrayList 是基于动态数组数据结构实现,LinkedList 是基于双向链表数据结构实现,接下来从这两种完全不同的结构比较 ArrayList 和 LinkedList 的性能和 适用场景。
一、简单分析两者数据结构和提供的相关方法
1.ArrayList 是基于数据实现的,源码如下:
/** * Default initial capacity. */ private static final int DEFAULT_CAPACITY = 10;
/** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == EMPTY_ELEMENTDATA will be expanded to * DEFAULT_CAPACITY when the first element is added. */ private transient Object[] elementData;
/** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { super(); this.elementData = EMPTY_ELEMENTDATA; }
建议的理由:大家都知道数组初始化时指定了大小,然后根据初始化大小在内存分配一段连续的空间,以后再也不能修改数组大小,ArrayList便是基于动态数据的实现(就是改变了引用变量的指向),比如不断向ArrayList 里面添加元素,当ArrayList 的size不够时变开始进行扩容,扩容肯定损耗性能,如果指定大小可避免.扩容源码如下:
/** * Increases the capacity to ensure that it can hold at least the * number of elements specified by the minimum capacity argument. * * @param minCapacity the desired minimum capacity */ private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
从上面源码可以看出,每次扩容后长度为原来的1.5倍,就是每次增加50%(oldCapacity >>1), 即重新创建一个1.5倍原来数据长度的新数据,然后将老的数据内容拷贝到新的里面,所以如果一开始没有设置大小或者设置得过小,后期频繁的插入,会导致经常需要扩容,这对性能有一定的影响,还有一个经常也被人吐槽的就是,当Size很大的时候,再扩容一次,就会造成很多内存的浪费,
例如:list 的size 已经到达1000万了,再插入一个数据时,发现不够了,便开始扩容,list 大小变为1500万,可是在这之后不插入数据或者很少的数据,这样就会造成几乎500万大小的内存浪费。
ArrayList 提供了一个方法trimToSize(),这个方法可以多余的内存去掉,如果在知道数据量很大的时候,可以用一下这个方法,源码如下:
/** * Trims the capacity of this <tt>ArrayList</tt> instance to be the * list's current size. An application can use this operation to minimize * the storage of an <tt>ArrayList</tt> instance. */ public void trimToSize() { modCount++; if (size < elementData.length) { elementData = Arrays.copyOf(elementData, size); } }
接下来看一下ArrayList 的几个主要方法 ,indexOf,set,get add(E e),add(int index,E element),remove,根据时间复杂度来,indexOf,add(int index,E element),remove 归为一类,都是o(n),get,set add(E e) 归为一类,都是o(1),相关源码如下:
/**这个一看就是o(n),遍历查询比较 * Returns the index of the first occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the lowest index <tt>i</tt> such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. */ 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; }
add(int index,E element) 在指定位置插入后,需要将后面的数据都后移一位,导致时间复杂度为0(n)/** * Inserts the specified element at the specified position in this * list. Shifts the element currently at that position (if any) and * any subsequent elements to the right (adds one to their indices). * * @param index index at which the specified element is to be inserted * @param element element to be inserted * @throws IndexOutOfBoundsException {@inheritDoc} */ 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++; }remove 同理,需要将删除之后的数据前移一位
/** * Removes the element at the specified position in this list. * Shifts any subsequent elements to the left (subtracts one from their * indices). * * @param index the index of the element to be removed * @return the element that was removed from the list * @throws IndexOutOfBoundsException {@inheritDoc} */ public E remove(int index) { rangeCheck(index); modCount++; E oldValue = elementData(index); int numMoved = size - index - 1; if (numMoved > 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its work return oldValue; }get() 和set() 方法都是 根据下标去取数,所以复杂度为O(1),而 add(E e) 是在数组最后插入,不需要移动,源码如下:
public E set(int index, E e) { rangeCheck(index); checkForComodification(); E oldValue = ArrayList.this.elementData(offset + index); ArrayList.this.elementData[offset + index] = e; return oldValue; } public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); }
/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
2. 接下来看LinkedList ,LinkedList 是一个基于链表的,每一个节点都存放本身的内容item,一个指向前节点的指针prev,指向后节点的指针next。
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; } }从这上面可以看出是一个双向的,每个节点都会保存前一个节点prev和后一个节点信息next,这样就会导致浪费空间,据说网页初始化页面刚开始用的是LinkedList存储数据,后来改为ArrayList,节省了2G内存,只是据说,据说,
但是,但是存在肯定有他的合理性,这里先不讨论,我们继续往下看LinkedList其他方法,LinkedList提供的方法还比较多,大体过一下源码:
1.linkFirst 就是在开始节点插入一个节点,first节点就变为当前新接点,再判断当前list是否 为空,为空将last指向这个新节点,否则将原来的第一节点的prev指向现在的第一节点,新节点的prev 为null
/** * Links e as first element. */ private void linkFirst(E e) { final Node<E> f = first; final Node<E> newNode = new Node<>(null, e, f); first = newNode; if (f == null) last = newNode; else f.prev = newNode; size++; modCount++; }linkLast在最后添加一个节点,先将last 指向新节点,再先判断是否为空,如果为空,就把firs指向这个节点,否则就把原来的last的next指向新节点,把新节点作为新的last节点
/** * Links e as last element. */ 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++; }indexOf 是通过循环遍历去比较,时间复杂度为 O(n),这个 和 ArrayList 的indexOf 大体是一样的,
/** * Returns the index of the first occurrence of the specified element * in this list, or -1 if this list does not contain the element. * More formally, returns the lowest index {@code i} such that * <tt>(o==null ? get(i)==null : o.equals(get(i)))</tt>, * or -1 if there is no such index. * * @param o element to search for * @return the index of the first occurrence of the specified element in * this list, or -1 if this list does not contain the element */ public int indexOf(Object o) { int index = 0; if (o == null) { for (Node<E> x = first; x != null; x = x.next) { if (x.item == null) return index; index++; } } else { for (Node<E> x = first; x != null; x = x.next) { if (o.equals(x.item)) return index; index++; } } return -1; }接下来这个方法比较核心了,在指定位置插入节点和删除节点,get(int index),这些方法都会调用node(int index),这个方法就是获取指定位置,这里获取定位是,还做了一点的优化,就是比较获取的位置index是在前半部分,还是在后半部分,前半部分就从头开始遍历,后半部分就从尾开始遍历,不可避免的是时间复杂度还是 O(n).
很多的观点说 频繁的插入删除,用Linklist比比ArrayList快,确实是这样吗?其实这种说法是完全不对的。例如频繁的插入: Linklist 需要重头遍历一遍定位,而ArrayList 需要将后面的前移,到底谁快还不一定呢,需要具体情况具体分析了。
/** * Returns the (non-null) Node at the specified element index. */ Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { 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; } }此外,LinkedList 还提供了很多 类似队列和栈的操作方法,如果需求实现需要类似 栈或者队列时,这个是LinkedList比ArrayList的巨大优势,源码比较多,贴一部分如下:
/** * Retrieves, but does not remove, the head (first element) of this list. * * @return the head of this list, or {@code null} if this list is empty * @since 1.5 */ public E peek() { final Node<E> f = first; return (f == null) ? null : f.item; } /** * Retrieves, but does not remove, the head (first element) of this list. * * @return the head of this list * @throws NoSuchElementException if this list is empty * @since 1.5 */ public E element() { return getFirst(); } /** * Retrieves and removes the head (first element) of this list. * * @return the head of this list, or {@code null} if this list is empty * @since 1.5 */ public E poll() { final Node<E> f = first; return (f == null) ? null : unlinkFirst(f); } /** * Retrieves and removes the head (first element) of this list. * * @return the head of this list * @throws NoSuchElementException if this list is empty * @since 1.5 */ public E remove() { return removeFirst(); }
二、简单分析ArrayList 和LinkedList 的性能
上面已经简单介绍了ArrayList 和LinkedList 的结构和提供的方法,接下来看一下两者的使用性能.
1. 首先比较一下随机查询,测试代码如下:
public class Test1 {static List<Integer> arraylist=new ArrayList<Integer>(); static List<Integer> linkedlist=new LinkedList<Integer>(); static Integer num= 100000; public static void main(String[] args) { for(int i=0;i<num;i++){ arraylist.add(i); linkedlist.add(i); } System.out.println("arrayList 消耗时间:"+getData(arraylist)); System.out.println("linkedList 消耗时间:"+getData(linkedlist)); } public static long getData(List<Integer> list) { long time=System.currentTimeMillis(); int temp; for(int i=0;i<list.size();i++){ temp = list.get(i); } return System.currentTimeMillis()-time; }}测试结果如下:
从结果可以看出,如果 是随机查询比较多的,绝对不建议用LinkedList ,LinkedList 是从头或者从尾部一个一个的去遍历比较,然后获取,这样肯定慢.
2.接下来比较插入,代码如下:
public class Test1 {static List<Integer> arraylist = new ArrayList<Integer>();static List<Integer> linkedlist = new LinkedList<Integer>();static Integer num = 100000; //list的大小static Integer position = 1; //插入的位置public static void main(String[] args) {for (int i = 0; i < num; i++) {arraylist.add(i);linkedlist.add(i);}System.out.println("array insert time:" + insertData(arraylist));System.out.println("linked insert time:" + insertData(linkedlist));}public static long insertData(List<Integer> list) {long time = System.currentTimeMillis();for (int i = 100; i < 100000; i++) {list.add(position, i);}return System.currentTimeMillis() - time;}}当positon =1时,结果 ArrayList 消耗的时间 > LinkedList 消耗的时间:
当position =10000时,结果 ArrayList 消耗的时间 > LinkedList 消耗的时间:
当position= 20000,结果 ArrayList 消耗的时间 < LinkedList 消耗的时间:
当postion= 30000时,结果 ArrayList 消耗的时间 < LinkedList 消耗的时间:
........
当postion= 80000时,结果 ArrayList 消耗的时间 < LinkedList 消耗的时间:
当postion= 100000时,结果 ArrayList 消耗的时间 < LinkedList 消耗的时间:
结论:当list 数据量比较大时,测试用的10万数据量,当在集合的前部 20%左右插入数据(具体还是要看数据量,如果数据量100万估计就10%,意思就是:数据量越大,比例越小),LinkedList 比ArrayList 快(想想也知道,ArrayList 需要拷贝的比较多,LinkedList 遍历数据比较少),当在集合中后部插入时,ArrayList 性能更好.当数量比较小时比如就几百几十的,那就没有必要比较了。
3.比较一下remove 的性能比较:
public class Test1 {static List<Integer> arraylist = new ArrayList<Integer>();static List<Integer> linkedlist = new LinkedList<Integer>();static Integer num = 100000; //list的大小static int position = 1; //插入的位置public static void main(String[] args) {for (int i = 0; i < num; i++) {arraylist.add(i);linkedlist.add(i);}System.out.println("array insert time:" + removeData(arraylist));System.out.println("linked insert time:" + removeData(linkedlist));}public static long removeData(List<Integer> list) {long time = System.currentTimeMillis();for (int i = 100; i < 10000; i++) {list.remove(position);}return System.currentTimeMillis() - time;}}
当postion=1时,结果 ArrayList 消耗的时间 > LinkedList 消耗的时间:
当postion=10000时,结果 ArrayList 消耗的时间 > LinkedList 消耗的时间:
当postion=20000时,结果 ArrayList 消耗的时间 < LinkedList 消耗的时间:
当postion=30000时,结果 ArrayList 消耗的时间 < LinkedList 消耗的时间:
当postion=90000时,结果 ArrayList 消耗的时间 < LinkedList 消耗的时间:
结论和插入的类似,即当在集合的前部 20%左右插入数据(具体还是要看数据量),LinkedList 比ArrayList 快(想想也知道,ArrayList 需要拷贝的比较多,LinkedList 遍历数据比较少),当在集合中后部插入时,ArrayList 性能更好。
此外,如果需要在头部不断的插入数据,LinkedList 的优势是巨大的。
如果需要类似队列的功能是,LinkedList也比ArrayList 更好,所以具体使用哪个,还是看具体的应用场景.
- 浅谈ArrayList和 LinkedList区别
- ArrayList和LinkedList浅谈
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别?
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- LinkedList 和 ArrayList 区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- ArrayList和LinkedList区别
- 池化层对神经网络的运算速度有什么影响
- 添加了 JavaScript 的图像映射
- PopupWindow完全解析
- jQuery-用jquery中的ajax()代替传统的json传值
- Word representations: A simple and general method for semi-supervised learning
- 浅谈ArrayList和 LinkedList区别
- Zsh 入门(安装及使用)
- 简单的通讯录管理系统
- 云端的SRE发展与实践
- QT控件大全 四十五 QSclock
- 洛谷1879 [USACO06NOV]玉米田Corn Fields
- ssh远程后台执行matlab程序(可并行优化)
- 【LeetCode】39.Combination Sum(Medium)解题报告
- 2017.12.11 Date格式化