集合(List、Set)

一、典型面试例题及思路分析

问题 1:“ArrayList 和 LinkedList 的的相同点和不同点分别是什么?”

相同点:ArrayList 和 LinkedList 都是 List 接口的实现类,因此都具有 List 的特点,即存取有序,可重复;而且都不是线程安全的。
不同点:ArrayList 基于数组实现,LinkedList 基于双向链表实现。

点评:

​这是一个典型的考察集合异同点的面试题。同类面试题还有很多,比如说

数组 (Array) 和列表 (ArrayList) 的差别是什么?
ArrayList 和 Vector 的差别是什么?

​这类题目考察的仍然是基础,同时还有候选人的知识深度。就参考答案而言,不同点部分抓住了二者最大的不同,即内部数据结构的差异,而相同点部分则从三个方面进行了展开:

“都是 List 接口的实现类”,体现侯选人对 JDK 源码是比较熟悉的;
“因此都具有 List 的特点,即存取有序,可重复”,是在 1 的基础上对 List 的具体阐述;
“都不是线程安全的”,体现的是线程安全方面的考虑。
​ 第 2 点和第 3 点单独拎出来进行说明,是因为其对应的正是另两类关于 List 的高频题,详情分别参见本章节的问题 2 和问题 3,先不展开。这里基于不同点还可以继续追问:

“既然 ArrayList 和 LinkedList 的内部数据结构不一样,那分别适用于什么场景呢?”
​ 不同的内部数据结构适应于不同的应用场景,这点无庸置疑。

​ ArrayList 基于数组存储数据,因此查询元素时可以直接按照数据下标进行索引,而插入元素时,通常涉及到数据元素的复制和移动,所以查询数据快而插入数据慢;

​ LinkedList 基于双向链表存储数据,因此查询元素时需要前向或后向遍历,而插入数据时只需要修改本元素的前后项即可,所以查询数据慢而插入数据快。

​ 所以,ArrayList 适合查询多(读多)的场景,LinkedList 适合插入多(写多)的场景。

问题 2:"List、Set、Map 之间的区别是什么?"

List 是有序集合,可以有重复元素;

Set 集合不能包括重复元素,实现类中 LinkedHashSet 按照插入顺序排序,SortedSet 可排序,HashSet 无序;

Map 存放键值对 (key-value pairs) 映射,映射关系可以是一对一或多对一,key 无序且唯一,value 可重复。实现类中 LinkedHashMap 按照插入顺序排序,SortedMap 可排序,HashMap 无序。
点评:

​本题属于自由发挥题,主要考察候选人两方面的能力:一是要真正熟悉对应的知识点,二是要有较强的总结和表述能力。如果自己心里明白但表述不清楚,于面试官而言也等同于你不明白。比如说基于参考答案之外,候选人也可以针对这些集合的异同添加自己的理解。

​回到题目本身,首先,List 与 Set 具有相似性,都继承共同的 Collection 接口,也都是单列元素的集合。List 的内部是数组,所以不断在数组后面追加元素即可,这是它为什么有序的原因;而 Set 里面不允许有重复的元素,这里的重复是指两个相等 (注意不是相同) 的对象 ,即 equals () 返回 true。如果 Set 集合 s 中有 A 元素,现在再向 s 集合插入 B 元素,此时 B 元素如果与 A 元素相等,则 B 元素存储不进去(add 方法返回 false)。

​其次,Map 与 List 和 Set 不同,它是双列的集合,值得注意的是并不继承 Collection。

问题 3:"ArrayList 和 LinkedList 都不是线程安全的,那有线程安全的 List 类吗?"

​线程安全 List 类有 Vector 和 CopyOnWriteList。

Vector 是通过在其几乎所有方法前加 synchronized 关键字来保证线程安全性;

CopyOnWriteList 则是通过数组复制的方法来保证线程安全的。

点评:

Vector 和 Collections.synchronizedList (new ArrayList ()) 类似,都是通过 synchronized 来保证集合的安全性。只不过 Vector 的 synchronized 关键字加在方法外面(图 1),Collections.synchronizedList (new ArrayList ()) 的 synchronized 关键字加在方法里面(图 2)。

在这里插入图片描述图 1 Vector 中的方法
在这里插入图片描述图 2 Collections.synchronizedList 中的方法

​CopyOnWriteList 是通过缩小锁有范围和数组复制来实现线程安全,看读(add 方法)、写(get 方法)部分的源码:

/**
* 添加元素
**/
public boolean add(E e) {
  // 1、加锁
 final ReentrantLock lock = this.lock;
 lock.lock();
 try {
     // 2、获取原数组及长度
     Object[] elements = getArray();
     int len = elements.length;
     // 3、复制到新数组
     Object[] newElements = Arrays.copyOf(elements, len + 1);
     // 4、添加元素到新数组
     newElements[len] = e;
     setArray(newElements);
     return true;
 } finally {
     // 5、释放锁
     lock.unlock();
 }
}

/**
* 读取元素
**/
public E get(int index) {
 return get(getArray(), index);
}

final Object[] getArray() {
//CopyOnWriteList的成员属性,通过private transient volatile来修饰
return array;
}

​可以看到写的时候加锁了而读的时候没有加锁。这是因为 CopyOnWriteList 在读的时候读的原数组,而原数组通过 volatile 修饰保证了可见性。这在迭代器上表现得更加明显。

/**重写List的迭代器实现*/
public Iterator<E> iterator() {
 return new COWIterator<E>(getArray(), 0);
}
... ...

private COWIterator(Object[] elements, int initialCursor) {
 //COWIterator的构造方法,cursor和snapshot是迭代器的成员属性
 cursor = initialCursor;
 snapshot = elements;
}

/**
* 迭代器中hasNext()和next()方法都是基于snapshot数组,而snapshot是传递进来的array原数组
*/
public boolean hasNext() {
return cursor < snapshot.length;
}

@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
 throw new NoSuchElementException();
return (E) snapshot[cursor++];
}

二、总结

​ 总体而言,List/Set 类的面试题的特点和 Map 类有点类似(深度上可能不及 Map 类),都是属于基础知识的部分,这类试题的答题要点在于:

基础知识的回答要表达清晰,知识准确。
厚积薄发,平常多问问为什么,注意学习源码。
面试技巧:回答时可以结合着源码、或者自己的工程实践展开,向面试官传递出自己热爱技术、勤于思考的形象。

三、扩展阅读

数组(Array)和列表(ArrayList)的差别是什么?

Array可以容纳基本类型和对象,而ArrayList只能容纳对象;

Array 是静态的,一旦创建就无法更改它的大小,ArrayList 是Java集合框架类的一员,可以称它为一个动态数组。

问:ArrayList和Vector有何异同点?

相同点:
(1)两者都是基于索引的,内部由一个数组支持。

(2)两者维护插入的顺序,我们可以根据插入顺序来获取元素。

(3)ArrayList和Vector的迭代器实现都是fail-fast的。

(4)ArrayList和Vector两者允许null值,也可以使用索引值对元素进行随机访问。

不同点:
(1)Vector是同步的,而ArrayList不是。然而,如果你寻求在迭代的时候对列表进行改变,你应该使用CopyOnWriteArrayList。

(2)ArrayList比Vector快,它因为有同步,不会过载。

(3)ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。

问:EnumSet是什么?

java.util.EnumSet是使用枚举类型的集合实现。当集合创建时,枚举集合中的所有元素必须来自单个指定的枚举类型,可以是显示的或隐示的。EnumSet是不同步的,不允许值为null的元素。

问:Java集合类中的Iterator和ListIterator的区别?

iterator()方法在set和list接口中都有定义,但是ListIterator()仅存在于list接口中(或实现类中);
ListIterator有add()方法,可以向List中添加对象,而Iterator不能;
ListIterator和Iterator都有hasNext()和next()方法,可以实现顺序向后遍历,但是ListIterator有hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以;
ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能;

都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值