JAVA面试常问知识总结(四)——集合

先附一张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 }

HashMap的底层是如何实现?

猜你喜欢

转载自www.cnblogs.com/king-brook/p/9969354.html