Java 集合面试真题22道
1. ArrayList 和 Vector 的区别。
ArrayList是异步线程,线程不安全;
Vector支持线程同步,线程安全
2. 说说 ArrayList,Vector, LinkedList 的存储性能和特性。
1)首先,ArrayList 和 Vector 的底层都是采用数组的来存储数据,而且都是根据索引来取数据,这样设计使得获取数据快而插入数据慢。另外,每次扩容都要移动数组中的元素,存储数据量较大的时候会影响读写性能。
2)其次,由于Vector 中的方法都使用了 synchronized 修饰,因此 ,Vector 中对数据操作都是线程安全的,但性能上比ArrayList 差。
3)然后,LinkedList 的底层是采用双向链表来存储数据的,也就是说将内存中零散的内存单元通过附加的引用关联起来,形成一个可以按序号索引的线性结构,这种链式存储方式与数组的连续存储方式相比,内存的利用率更高。LinkedList获取数据需要根据索引序号,向前或者向后遍历,但是插入数据时只需要记录本项的前后项即可,所以,LinkedList插入数据的速度更快。
谈谈ArrayList、Vector和LinkedList 的存储性能及特性
4. 快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?
Iterator的安全失败是基于对底层集合做拷贝,因此,它不受源集合上修改的影响。java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常。
快速失败(fail-fast)和安全失败(fail-safe)的区别
fail-fast 机制,即快速失败机制,是java集合(Collection)中的一种错误检测机制。
当在迭代集合的过程中该集合在结构上发生改变的时候,就有可能会发生fail-fast,即抛出ConcurrentModificationException异常。
java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
解决方法:通过util.concurrent集合包下的相应类去处理,则不会产生fail-fast事件。如使用ArrayList会产生fail-fast,使用CopyOnWriteArrayList则不会。
安全失败(fail-safe)原理:
迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception。因此缺点就是,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。
快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么?
5. HashMap 的数据结构。
由数组和链表组合构成的数据结构
6. HashMap 的工作原理是什么?
- 判断数组是否为空,为空进行初始化;
- 不为空,计算 k 的 hash 值,通过(n - 1) & hash计算应当存放在数组中的下标 index;
- 查看 table[index] 是否存在数据,没有数据就构造一个Node节点存放在 table[index] 中;
- 存在数据,说明发生了hash冲突(存在二个节点key的hash值一样), 继续判断key是否相等,相等,用新的value替换原数据(onlyIfAbsent为false);
- 如果不相等,判断当前节点类型是不是树型节点,如果是树型节点,创造树型节点插入红黑树中;(如果当前节点是树型节点证明当前已经是红黑树了)
- 如果不是树型节点,创建普通Node加入链表中;判断链表长度是否大于 8并且数组长度大于64, 大于的话链表转换为红黑树;
- 插入完成之后判断当前节点数是否大于阈值,如果大于开始扩容为原数组的二倍。
7. HashMap 什么时候进行扩容呢?如何扩容?
1)什么时候扩容
HashMap的容量是有限的。当经过多次元素插入的时候,使得HashMap达到一定的饱和度,Key映射位置的几率不断变大。这个时候,HashMap就需要扩容了,也就是resize()
。
HashMap扩容的条件:
1、HashMap中的数据达到阈值。
2、出现hash碰撞的情况。
2)怎么扩容
1)创建一个新的Entry空数组,使用的是2次幂的扩展(长度是原来的2倍)。
2)ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。
为什么要重新Hash呢?因为长度扩大以后,Hash的规则也随之改变。
Hash的公式—> index = HashCode(Key) & (Length - 1)
扩容前:
扩容后:
8. List、Map、Set 三个接口,存取元素时,各有什么特点?
存放时:
1.List以特定的索引(有顺序的存放)来存放元素,可以有重复的元素
2.Set存放元素是无序的,而且不可重复(用对象的equals()方法来区分元素是否重复)
3.Map保存键值对的映射,映射关系可以是一对一(键值)或者多对一,需要注意到的是:键无序不可重复,值可以重复
取出时:
(1)List取出元素for循环,foreach循环,Iterator迭代器迭代
(2)Set取出元素foreach循环,Iterator迭代器迭代
(3)Map取出元素需转换为Set,然后进行Iterator迭代器迭代,或转换为Entry对象进行Iterator迭代器迭代
【Java】List,Set,Map存取元素各有什么特点?
9. Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用 == 还是 equals()? 它们有何区别?
元素重复与否是使用 equals() 方法进行判断的
== 操作符专门用来比较两个变量的值是否相等,也就是用于比较变量所对应的内存中所存储的数值是否
相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用 == 操作符。
equals 方法是用于比较两个独立对象的内容是否相同,就好比去比较两个人的长相是否相同,它比较的
两个对象是独立的。
10. 两个对象值相同 (x.equals(y) == true),但却可有不同的 hashcode,这句话对不对?
得看情况,如果该对象重写了equals方法,那么可能会出现equals相同,但hashcode不同的情况,但假如没有重写equals方法,那么它默认继承是Object的equals方法,根据源码可知,此时equals相同,hashcode一定相同。
public boolean equals(Object obj) {
return (this == obj);
}
总结:
如果两个对象相同(equals方法返回true),那么它们的hashCode值一定要相同;
如果两个对象的hashCode相同,它们并不一定相同。
11. heap 和 stack 有什么区别。
一、堆栈空间分配区别:
1、栈(操作系统):由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈;
2、堆(操作系统): 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收,分配方式倒是类似于链表。
二、堆栈缓存方式区别:
1、栈使用的是一级缓存, 他们通常都是被调用时处于存储空间中,调用完毕立即释放;
2、堆是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。
三、堆栈数据结构区别:
堆(数据结构):堆可以被看成是一棵树,如:堆排序;
栈(数据结构):一种先进后出的数据结构。
heap和stack有什么区别
12. Java 集合类框架的基本接口有哪些?
总共有两大接口:Collection 和Map ,一个元素集合,一个是键值对集合; 其中List和Set接口继承了Collection接口,一个是有序元素集合,一个是无序元素集合; 而ArrayList和 LinkedList 实现了List接口,HashSet实现了Set接口,这几个都比较常用; HashMap 和HashTable实现了Map接口,并且HashTable是线程安全的,但是HashMap性能更好;
13. HashSet 和 TreeSet 有什么区别?
HashSet 和 TreeSet 的区别在于:1、速度和内部实现不同;2、排序方式不同;3、空对象不同;4、比较方式不同。HashSet用于搜索、插入和删除等操作。这些操作平均需要花费固定时间。HashSet比TreeSet快。HashSet是使用哈希表实现的。TreeSet以O(Log n)进行搜索,插入和删除,该值高于HashSet。
14. HashSet 的底层实现是什么?
它是基于 HashMap 实现的,底层采用 HashMap 来保存元素,HashSet其实就是在操作HashMap的key
- 因为HashMap是无序的,因此HashSet也不能保证元素的顺序
- 因为HashSet中没有对应同步的操作,因此是线程不安全的
- 支持null元素(因为hashMap也支持null键和null值)