先附一张java集合框架图
下面根据面试中常问的关于集合的问题进行了梳理:
Arraylist 与 LinkedList 有什么不同?
-
1. 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
-
2. 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构(注意双向链表和双向循环链表的区别①);
-
3. 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行
add(E e)
方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element)
)时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。 -
4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于
get(int index)
方法)。 -
5. 内存空间占用: ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
补充内容:RandomAccess接口
1 public interface RandomAccess { 2 }
查看源码我们发现实际上 RandomAccess 接口中什么都没有定义。所以,在我看来 RandomAccess 接口不过是一个标识罢了。标识什么? 标识实现这个接口的类具有随机访问功能。在binarySearch()方法中,它要判断传入的list 是否RamdomAccess的实例,如果是,调用indexedBinarySearch()方法,如果不是,那么调用iteratorBinarySearch()方法
1 public static <T> 2 int binarySearch(List<? extends Comparable<? super T>> list, T key) { 3 if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD) 4 return Collections.indexedBinarySearch(list, key); 5 else 6 return Collections.iteratorBinarySearch(list, key); 7 }
ArraysList 实现了 RandomAccess 接口, 而 LinkedList 没有实现。为什么呢?还是和底层数据结构有关!ArraysList 底层是数组,而 LinkedList 底层是链表。数组天然支持随机访问,时间复杂度为 O(1),所以称为快速随机访问。链表需要遍历到特定位置才能访问特定位置的元素,时间复杂度为 O(n),所以不支持快速随机访问。,ArraysList 实现了 RandomAccess 接口,就表明了他具有快速随机访问功能。 RandomAccess 接口只是标识,并不是说 ArraysList 实现 RandomAccess 接口才具有快速随机访问功能的!
下面再总结一下 list 的遍历方式选择:
- 实现了RadomAcces接口的list,优先选择普通for循环 ,其次foreach (采用ArrayList对随机访问比较快,而for循环中的get()方法,采用的即是随机访问的方法,因此在ArrayList里,for循环较快)
- 未实现RadomAcces接口的ist,优先选择iterator遍历(foreach遍历底层也是通过iterator实现的,采用LinkedList则是顺序访问比较快,iterator中的next()方法,采用的即是顺序访问的方法,因此在LinkedList里,使用iterator较快)
ps:如果在涉及到集合元素的删除操作时,一般调用删除和添加方法都是具体集合的方法,例如:List list = new ArrayList(); list.add(...); list.remove(...);但是,如果在循环的过程中调用集合的remove()方法,就会导致循环出错,因为循环过程中list.size()的大小变化了,就导致了错误。 所以,如果想在循环语句中删除集合中的某个元素,就要用迭代器iterator的remove()方法,因为它的remove()方法不仅会删除元素,还会维护一个标志,用来记录目前是不是可删除状态,例如,你不能连续两次调用它的remove()方法,调用之前至少有一次next()方法的调用。
forEach不是关键字,关键字还是for,语句是由iterator实现的,它们最大的不同之处就在于remove()方法上。forEach就是为了让用iterator循环访问的形式简单,写起来更方便。当然功能不太全,所以但如有删除操作,还是要用它原来的形式
1 public static void remove(List<String> list, String target) { 2 for (String item : list) { 3 if (item.equals(target)) { 4 list.remove(item); 5 break; //增强 for 循环中删除元素后继续循环会报 java.util.ConcurrentModificationException 异常,因为元素在使用的时候发生了并发的修改,导致异常抛出,但是删除完毕马上使用 break 跳出,则不会触发报错。 6 } 7 } 8 } 9 10 11 public static void remove(List<String> list, String target) { 12 Iterator<String> iter = list.iterator(); 13 while (iter.hasNext()) { 14 String item = iter.next(); 15 if (item.equals(target)) { 16 iter.remove(); 17 } 18 } 19 }