Java中List实现类的性能分析和应用场景(基于JDK1.8)

来源:互联网 发布:手机麻将源码带房卡 编辑:程序博客网 时间:2024/05/17 17:43
List的四个重要实现类

Stack是栈。它的特性是:先进后出(FILO, First In Last Out)。
java工具包中的Stack是继承于Vector(矢量队列)的,由于Vector是通过数组实现的,这就意味着,Stack也是通过数组实现的,而非链表。当然,我们也可以将LinkedList当作栈来使用。
Stack是线程安全的。

Vector 是矢量队列,它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。
Vector 继承了AbstractList,实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。
Vector 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。
和ArrayList不同,Vector中的操作是线程安全的,Vector存储底层原理是数组。

ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。

LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
LinkedList 实现 List 接口,能对它进行队列操作。
LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
LinkedList 是非同步的。


List实现类的应用场景

测试demo
  1. package com.anyco.listrush;
  2. import java.util.*;
  3. import java.lang.Class;
  4. /*
  5. * @desc 对比ArrayList、LinkedList、Vector、Stack的插入、随机读取效率、删除的效率
  6. */
  7. public class ListCompareTest {
  8. private static final int COUNT = 100000;
  9. private static LinkedList linkedList = new LinkedList();
  10. private static ArrayList arrayList = new ArrayList();
  11. private static Vector vector = new Vector();
  12. private static Stack stack = new Stack();
  13. public static void main(String[] args) {
  14. // 换行符
  15. System.out.println();
  16. // 插入
  17. insertByPosition(stack) ;
  18. insertByPosition(vector) ;
  19. insertByPosition(linkedList) ;
  20. insertByPosition(arrayList) ;
  21. // 换行符
  22. System.out.println();
  23. // 随机读取
  24. readByPosition(stack);
  25. readByPosition(vector);
  26. readByPosition(linkedList);
  27. readByPosition(arrayList);
  28. // 换行符
  29. System.out.println();
  30. // 删除
  31. deleteByPosition(stack);
  32. deleteByPosition(vector);
  33. deleteByPosition(linkedList);
  34. deleteByPosition(arrayList);
  35. }
  36. // 获取list的名称
  37. private static String getListName(List list) {
  38. if (list instanceof LinkedList) {
  39. return "LinkedList";
  40. } else if (list instanceof ArrayList) {
  41. return "ArrayList";
  42. } else if (list instanceof Stack) {
  43. return "Stack";
  44. } else if (list instanceof Vector) {
  45. return "Vector";
  46. } else {
  47. return "List";
  48. }
  49. }
  50. // 向list的指定位置插入COUNT个元素,并统计时间
  51. private static void insertByPosition(List list) {
  52. long startTime = System.currentTimeMillis();
  53. // 向list的位置0插入COUNT个数
  54. for (int i=0; i<COUNT; i++)
  55. list.add(0, i);
  56. long endTime = System.currentTimeMillis();
  57. long interval = endTime - startTime;
  58. System.out.println(getListName(list) + " : insert "+COUNT+" elements into the 1st position use time:" + interval+" ms");
  59. }
  60. // 从list的指定位置删除COUNT个元素,并统计时间
  61. private static void deleteByPosition(List list) {
  62. long startTime = System.currentTimeMillis();
  63. // 删除list第一个位置元素
  64. for (int i=0; i<COUNT; i++)
  65. list.remove(0);
  66. long endTime = System.currentTimeMillis();
  67. long interval = endTime - startTime;
  68. System.out.println(getListName(list) + " : delete "+COUNT+" elements from the 1st position use time:" + interval+" ms");
  69. }
  70. // 根据position,不断从list中读取元素,并统计时间
  71. private static void readByPosition(List list) {
  72. long startTime = System.currentTimeMillis();
  73. // 读取list元素
  74. for (int i=0; i<COUNT; i++)
  75. list.get(i);
  76. long endTime = System.currentTimeMillis();
  77. long interval = endTime - startTime;
  78. System.out.println(getListName(list) + " : read "+COUNT+" elements by position use time:" + interval+" ms");
  79. }
  80. }

控制台输出:
Stack : insert 100000 elements into the 1st position use time:1566 ms
Vector : insert 100000 elements into the 1st position use time:1101 ms
LinkedList : insert 100000 elements into the 1st position use time:16 ms
ArrayList : insert 100000 elements into the 1st position use time:1097 ms

Stack : read 100000 elements by position use time:5 ms
Vector : read 100000 elements by position use time:3 ms
LinkedList : read 100000 elements by position use time:6769 ms
ArrayList : read 100000 elements by position use time:2 ms

Stack : delete 100000 elements from the 1st position use time:1103 ms
Vector : delete 100000 elements from the 1st position use time:992 ms
LinkedList : delete 100000 elements from the 1st position use time:8 ms
ArrayList : delete 100000 elements from the 1st position use time:979 ms

可以看到LinkedList插入元素和删除元素时效率极高,而读取元素时效率远低于其他三个集合。
而读取元素时,ArrayList效率达到2ms的级别,但是其插入和删除元素的效率远低于LinkedList。
基于此,我们可以先给出结论。
对于需要快速插入,删除元素,应该使用LinkedList。
对于需要快速随机访问元素,应该使用ArrayList。
对于“单线程环境” 或者 “多线程环境,但List仅仅只会被单个线程操作”,此时应该使用非同步的类(如ArrayList),加锁会降低效率。
对于“多线程环境,且List可能同时被多个线程操作”,此时,应该使用同步的类(如Vector)。


LinkedList和ArrayList性能差异分析

为什么LinkedList修改元素时效率高?
LinkedList添加元素的第一种方法
  1. public boolean add(E e) {
  2. linkLast(e);
  3. return true;
  4. }
  1. void linkLast(E e) {
  2. final Node<E> l = last;
  3. final Node<E> newNode = new Node<>(l, e, null);
  4. last = newNode;
  5. if (l == null)
  6. first = newNode;
  7. else
  8. l.next = newNode;
  9. size++;
  10. modCount++;
  11. }
当使用public boolean add(E e)向LinkedList添加方法时,总是在链表末尾将新节点链接上去。

LinkedList添加元素的第二种方法
  1. public void add(int index, E element) {
  2. checkPositionIndex(index);
  3. if (index == size)
  4. linkLast(element);
  5. else
  6. linkBefore(element, node(index));
  7. }

关键在于linkBefore(element, node(index));
  1. Node<E> node(int index) {
  2. // assert isElementIndex(index);
  3. if (index < (size >> 1)) {
  4. Node<E> x = first;
  5. for (int i = 0; i < index; i++)
  6. x = x.next;
  7. return x;
  8. } else {
  9. Node<E> x = last;
  10. for (int i = size - 1; i > index; i--)
  11. x = x.prev;
  12. return x;
  13. }
  14. }
通过add(int index, E element)向LinkedList插入元素时。先是在双向链表中找到要插入节点的位置index;找到之后,再插入一个新节点。
双向链表查找index位置的节点时,有一个加速动作:若index < 双向链表长度的1/2(即判断index < (size >> 1)),则从前向后查找; 否则,从后向前查找。

而ArrayList(再次声明,ArrayList的底层实现是数组)向特定位置添加元素时,使用以下方法
  1. public void add(int index, E element) {
  2. rangeCheckForAdd(index);
  3. ensureCapacityInternal(size + 1); // Increments modCount!!
  4. System.arraycopy(elementData, index, elementData, index + 1,
  5. size - index);
  6. elementData[index] = element;
  7. size++;
  8. }
第一个耗时操作 ensureCapacityInternal(size + 1); 
先确保数组有足够的空间加入新的元素。关于ArrayList的扩容机制,在ArrayList源码探讨(基于JDK1.8)已有探讨。
扩容函数如下。
  1. private void grow(int minCapacity) {
  2. // overflow-conscious code
  3. int oldCapacity = elementData.length;
  4. int newCapacity = oldCapacity + (oldCapacity >> 1);
  5. if (newCapacity - minCapacity < 0)
  6. newCapacity = minCapacity;
  7. if (newCapacity - MAX_ARRAY_SIZE > 0)
  8. newCapacity = hugeCapacity(minCapacity);
  9. // minCapacity is usually close to size, so this is a win:
  10. elementData = Arrays.copyOf(elementData, newCapacity);
  11. }
可知,ArrayList扩容时,必须要经过一次数组的复制过程。

第二个耗时操作System.arraycopy(elementData, index, elementData, index + 1,size - index);这段代码依然来自ArrayList的方法public void add(int index, E element)。这其中又是一次数组的拷贝过程。


为什么ArrayList随机访问效率高?
  1. public E get(int index) {
  2. rangeCheck(index);
  3. return elementData(index);
  4. }
  1. E elementData(int index) {
  2. return (E) elementData[index];
  3. }
可以看到,获取特定位置的元素时,ArrayList直接通过数组下标获取,不需要经过一次遍历过程。


Vector和ArrayList比较
相同点:
它们都继承于AbstractList,并且实现List接口。
 它们都实现了RandomAccess和Cloneable接口。
   实现RandomAccess接口,意味着它们都支持快速随机访问;
   实现Cloneable接口,意味着它们能克隆自己。
③它们都是通过数组实现的,本质上都是动态数组。
④ 它们的默认数组容量是10

不同点:
① 线程安全性不一样
   ArrayList是非线程安全;
   而Vector是线程安全的,它的函数都是synchronized的,即都是支持同步的。
   ArrayList适用于单线程,Vector适用于多线程。
②对序列化支持不同
   ArrayList支持序列化,而Vector不支持;即ArrayList有实现java.io.Serializable接口,而Vector没有实现该接口。
③ 容量增加方式不同
ArrayList扩容时是在原来容量的基础上再扩容0.5倍,下面是ArrayList的扩容函数。
  1. private void grow(int minCapacity) {
  2. // overflow-conscious code
  3. int oldCapacity = elementData.length;
  4. int newCapacity = oldCapacity + (oldCapacity >> 1);
  5. if (newCapacity - minCapacity < 0)
  6. newCapacity = minCapacity;
  7. if (newCapacity - MAX_ARRAY_SIZE > 0)
  8. newCapacity = hugeCapacity(minCapacity);
  9. // minCapacity is usually close to size, so this is a win:
  10. elementData = Arrays.copyOf(elementData, newCapacity);
  11. }
Vector是在原来的基础上增大capacityIncrement个位置。如果一开始未指定capacityIncrement,那么在原来的基础上增大一倍容量。
  1. private void grow(int minCapacity) {
  2. // overflow-conscious code
  3. int oldCapacity = elementData.length;
  4. int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
  5. capacityIncrement : oldCapacity);
  6. if (newCapacity - minCapacity < 0)
  7. newCapacity = minCapacity;
  8. if (newCapacity - MAX_ARRAY_SIZE > 0)
  9. newCapacity = hugeCapacity(minCapacity);
  10. elementData = Arrays.copyOf(elementData, newCapacity);
  11. }









阅读全文
1 0
原创粉丝点击