使用arraylist和linkedlist

本文比较了ArrayList和LinkedList在不同操作下的性能表现,如随机访问、折半查找及频繁的插入和删除操作。实验结果显示,ArrayList适合随机访问和查找,而LinkedList在频繁插入删除时表现更佳。

ArrayList 和 LinkedList是两个Collections类,用于存储对象引用列表。举例说,你可以创建Strings类的ArrayList,或者Integers类的LinkedList.这篇文章会比较ArrayList和LinkedList他们的性能,以及提供一些在特定的情况下如何使用正确它们的选择建议。

第一要点是ArrayList返回的是原始的Object数组。正因如此,ArrayList对于随机访问比LinkedList要快许多,就是说,当访问任意的列表元素可以使用get()方法。注意,get()方法也应用在LinkedLists上,但是它需要在列表的头到尾顺序扫描。这种扫描是非常低效的,对于一个LinkedList,没有更快的方法去访问列表中的第N个元素。

思考下面的例子,假如你有一组庞大的分类元素要使用ArrayList或者LinkedList,你也要在这个列表中使用折半查找法。标准的折半查找算法靠一个数值在列表的中间开始检查这个搜索的数值,如果中间值太高,列表中后半部就会被淘汰。然而,如果中间值太低,前半部会被忽略。这个过程会一直继续直到这个值在列表中被找到,或者直到搜索的下界变得大于上界。

这里的程序是在ArrayList或者LinkedList列表中元素使用折半查找法:

[code]

  import java.util.*; 
  
  public class ListDemo1 { 
  static final int N = 10000; 
  
  static List values; 
  
  // make List of increasing Integer values 
  
  static { 
  Integer vals[] = new Integer[N]; 
  
  Random rn = new Random(); 
  
  for (int i = 0, currval = 0; i < N; i++) { 
  vals = new Integer(currval); 
  currval += rn.nextInt(100) + 1; 
  } 
  
  values = Arrays.asList(vals); 
  } 
  
  // iterate across a list and look up every 
  // value in the list using binary search 
  
  static long timeList(List lst) { 
  long start = System.currentTimeMillis(); 
  
  for (int i = 0; i < N; i++) { 
  
  // look up a value in the list 
  // using binary search 
  
  int indx = Collections.binarySearch( 
  lst, values.get(i)); 
  
  // sanity check for result 
  // of binary search 
  
  if (indx != i) { 
  System.out.println("*** error ***/n"); 
  } 
  } 
  
  return System.currentTimeMillis() - start; 
  } 
  
  public static void main(String args[]) { 
  
  // do lookups in an ArrayList 
  
  System.out.println("time for ArrayList = " + 
  timeList(new ArrayList(values))); 
  
  // do lookups in a LinkedList 
  
  System.out.println( 
  "time for LinkedList = " + 
  timeList(new LinkedList(values))); 
  } 
  } 

[/code]
ListDemo1类创建了一个分类的Integer数值列表。然后向ArrayList或者LinkedList添加了数值。Collections.binarySearch()方法用来查寻每个在列表中的值。
当你运行这个程序,你应该能看到下面的结果:
time for ArrayList = 31 
time for LinkedList = 4640
ArrayList速度比LinkedList快了150倍左右。(你的结果可能与这里不一样,这取决于你机器的特性,但是你应该看到ArrayList的与LinkedList比较结果的明显的区别。对于其它程序用这种方法也是同样的。)很明显,在这种情形下用LinkedList是一种坏的选择。折半查找算法天生使用随机访问,而LinkedList不支持快速随机访问。LinkedList使用随机访问的时间与列表大小成正比例。比较起来,在ArrayList中使用随机访问有一个比较固定的时间。
你能用RandomAccess接口去检查一个列表是否支持快速随机访问:
void f(List lst) { 
  if (lst instanceof RandomAccess) { 
  // supports fast random access 
  } 
  } 
ArrayList实现了RandomAccess接口,而LinkedList则没有。注意Collections.binarySearch()方法利用RandomAccess的特性使搜索变得完善。
这些结果表明,ArrayList始终是一个更好的选择?未必。也有许多LinkedList做得更好的例子。而且注意在许多情况下,关于LinkedList的算法能实现的更有效。举一个例子,反转LinkedList利用Collections.reverse。内置算法能做这些,并且利用iterators的前移和后移获得更合理的性能,
来看看另一个例子。假如你有一组元素,你向这个列表中插入和删除许多元素。如果这样,LinkedList是比较好的选择。为了证明,考虑下面“worst case”的情形。在这个演示中,程序重复地在列表的开始处插入元素。代码是这样的:
import java.util.*; 
  
  public class ListDemo2 { 
  static final int N = 50000; 
  
  // time how long it takes to add 
  // N objects to a list 
  
  static long timeList(List lst) { 
  long start = System.currentTimeMillis(); 
  
  Object obj = new Object(); 
  
  for (int i = 0; i < N; i++) { 
  lst.add(0, obj); 
  } 
  
  return System.currentTimeMillis() - start; 
  } 
  
  public static void main(String args[]) { 
  
  // do timing for ArrayList 
  
  System.out.println( 
  "time for ArrayList = " + 
  timeList(new ArrayList())); 
  
  // do timing for LinkedList 
  
  System.out.println( 
  "time for LinkedList = " + 
  timeList(new LinkedList())); 
  } 
  } 
当你运行这个程序,你应该能看到下面的结果:
time for ArrayList = 4859  
time for LinkedList = 125 

这结果看起来与前一个例子几乎颠倒了。
当在ArrayList的起始处添加一个元素,所有存在的元素必须把自己向后推,这意味着许多珍贵的数据需要复制和移动。相反,在LinkedList起始处添加一个元素只是简单的意味分配一条内部记录给这个元素,然后调整两条链记录。在LinkedList起始添加元素有固定的成本,但是在ArrayList起始处添加元素则成本与列表的大小成正比。
目前为止,这些方法看起来只是速度问题,那关于空间大小怎么样呢?让我来看看ArrayList和LinkedList在Java 2 SDK,Standard Edition v 1.4中实现的内部细节。这些细节并非这些类的外部说明的一部分。但说明了这些类如何在内部工作。
  LinkedList类有一个私有的内部类定义:
 private static class Entry { 
  Object element; 
  Entry next; 
  Entry previous; 
  } 
每一个Entry对象引用一个列表元素,和previous元素一起在LinkedList中。--换句话说,就是一个双链表。一个有1000个元素的LinkedList就与1000个Entry对象连在一起,参考实际的列表元素。因为这些Entry对象,LinkedList结构中开销了重要的空间。
ArrayList则拥有一个支持对象数组的东西。

### 多线程环境下安全使用 ArrayList LinkedList 的方法及注意事项 #### 1. 使用 `Collections.synchronizedList` 包装 在多线程环境中,可以通过 `Collections.synchronizedList` 方法将普通的 `ArrayList` 或 `LinkedList` 转换为线程安全的列表。此方法会返回个同步包装器,确保对列表的所有访问操作都通过同步锁进行[^3]。 ```java List<Integer> synchronizedArrayList = Collections.synchronizedList(new ArrayList<>()); List<Integer> synchronizedLinkedList = Collections.synchronizedList(new LinkedList<>()); ``` 需要注意的是,尽管 `Collections.synchronizedList` 提供了基本的线程安全性,但在迭代过程中仍然需要手动加锁以避免并发修改异常。 ```java synchronized (synchronizedArrayList) { Iterator<Integer> iterator = synchronizedArrayList.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } ``` #### 2. 使用 `CopyOnWriteArrayList` 对于读多写少的场景,可以考虑使用 `CopyOnWriteArrayList`。这是个线程安全的变体,其核心思想是在每次修改操作(如添加或删除元素)时创建个新的副本,从而避免了对原列表的直接修改[^1]。 ```java List<Integer> copyOnWriteArrayList = new CopyOnWriteArrayList<>(); copyOnWriteArrayList.add(1); copyOnWriteArrayList.add(2); ``` 需要注意的是,`CopyOnWriteArrayList` 在频繁写入的情况下性能较差,因为它需要不断创建新数组并复制数据[^1]。 #### 3. 使用 `ConcurrentLinkedQueue` 或其他并发集合 如果主要需求是高效的插入删除操作,可以考虑使用 `ConcurrentLinkedQueue` 或 `ConcurrentLinkedDeque` 等并发集合类。这些类基于无锁算法实现,提供了更高的并发性能[^4]。 ```java ConcurrentLinkedQueue<Integer> concurrentQueue = new ConcurrentLinkedQueue<>(); concurrentQueue.add(1); concurrentQueue.add(2); ``` #### 4. 注意事项 - **避免死锁**:在多线程环境中使用同步列表时,应避免在持有锁的情况下执行耗时操作,否则可能导致死锁或性能瓶颈[^3]。 - **迭代器的安全性**:即使使用了 `Collections.synchronizedList`,迭代器仍然是快速失败的(fail-fast),因此在多线程环境下需要额外加锁[^3]。 - **选择合适的集合类型**:根据具体应用场景选择适合的集合类型。例如,如果需要频繁的随机访问,可以选择 `CopyOnWriteArrayList`;如果需要高效的插入删除操作,则可以选择 `ConcurrentLinkedQueue`。 ```java // 示例代码:多线程环境下的安全使用 public class SafeListExample { public static void main(String[] args) { List<Integer> safeList = Collections.synchronizedList(new ArrayList<>()); // 多线程写入 Thread writerThread = new Thread(() -> { for (int i = 0; i < 100; i++) { safeList.add(i); } }); // 多线程读取 Thread readerThread = new Thread(() -> { synchronized (safeList) { for (Integer value : safeList) { System.out.println(value); } } }); writerThread.start(); readerThread.start(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值