首页 > 编程语言 > 详细

Java面试题-Collection框架

时间:2020-08-05 16:48:08      阅读:84      评论:0      收藏:0      [点我收藏+]

1,ArrayList中基础数据域Object[] elementData使用transient修饰的,那它是怎么序列化的?为什么?

答案:ArrayList实现了Serializable接口的writeObject方法,这个方法把elementData中的元素全部序列化到文件中了。这个方法时私有的,但序列化时是反射调用。之所以不使用elementData直接来序列化,主要原因是elementData是一个缓存数组,为了性能考虑,它通常会预留一些容量,当容量不足时又扩容,因此可能有大量空间没有实际存储元素。不直接序列化elementData可以保证序列化实际有值的那些元素,而不是序列化整个数组。

2,ArrayList的扩容机制是什么?

答案:ArrayList在创建的时候,如果不指定初始容量,默认容量为10,之后随着add中ensureCapacity方法的需要,存不下新数据时进行扩容,grow的逻辑很简单。首先找出当前容量,把新容量设置为旧容量的1.5倍(通过位运算左移实现),如果新容量比可用最小容量小,那么设置新容量为最小容量,如果新容量比极限容量要大,那么设置极限容量常量和最大整型数中的最大值,接着使用该新容量初始化一个新的数组,将原有elementData中的元素等位复制过去。

3,LinkedList和ArrayList有什么区别?

答案:LinkedList与ArrayList的比较实际上是顺序访问序列和随机访问序列的比较,LinkedList有两个主要特性:顺序访问和写快读慢。

引申:

       LinkedList继承AbstractSequentialList,提供了顺序访问的存储结构。实现了Deque双向队列接口,因为Queue特性是FIFO,而Deque则可以同时在头尾处完成读写操作,而LinkedList还可以操作头尾之间的任意节点。

4,介绍一下Vector和Stack

答案:Vector的实现和ArrayList基本一致,vector的所有public方法都是用了synchronized关键字,保证线程安全。Vector根据capacityIncrement的数值扩容(new时指定),而不是每次扩容1.5倍。Stack时Vector的子类,提供了一些栈操作的方法。

引申:

       Vector和Stack使用了大量synchronized关键字来保证线程安全,但这并不是当下推荐的方式,因为这种实现效率比较低,在java.util.Collections工具类中提供了synchronizedList方法可以提供线程安全的列表,拥有更好的性能,因此这两个类被看作已经过时的容器。

5,jdk1.8之前和之后的HashMap底层实现有什么不同?他们put的流程各是什么?

答案:在jdk1.8以前,HashMap的底层实现是数组和链表,而从1.8开始使用数组+链表+红黑树来实现。

Jkd1.8以前的put流程:

a)      计算键key的hash值。(利用异或操作与无符号右移操作构建特定哈希松散算法hash(Object k),尽可能减少哈希碰撞)

b)      根据hash值和table长度来确定下标。(利用hashCode与table的长度使用&运算构建索引算法indexFor(int,int),检测哈希冲突,确定数据地址),根据key值和hash值对比,确定是存入数组,还是创建链表节点还是替代之前的链表值。

c)      通过size>=threshold,确定是否需要扩容resize(2*table.length),确定下一个扩容阈值size*loadFactor(加载因子,常量0.75),确定useAltHashing改变算法标志,决定是否需要rehash重构哈希,使用新容量构建新的Entry<K,V>table数组,调用transfer(newTable,rehash)来重新计算所有节点并转移到新数组table下。

Jdk1.8以后的put流程:

a)      获取key值的hashCode。(简化了哈希松散算法,之前版本多次位移异或并不能避免过多哈希碰撞,反倒增加了计算次数,Hashmap的效率问题主要集中在链表部分的遍历,这是一个经验性质的改进。)

b)      调用putVal方法进行存值。该方法主要流程为:使用table的长度-1&hash来计算下标,table为空或超过阈值进行扩容,根据下标位置的节点类型保存数据,决定插入数组还是链节点或树节点,如果链表深度超过建树阈值(TREEIFY_THRESHOLD-1),即7,则调用treeifyBin把整个链表重构成树。

c)      Resize即重新规划table长度和阈值,如果数量超过阈值,扩容两倍(最小16,最大1<<30),阈值为容量*加载因子0.75。重新排列数据节点,遍历所以节点普通节点重新计算哈希值,树节点调用split方法,决定是否需要将其退化为链表。

引申:

       New HashMap时指定容量可以提高效率,合适的initCapacity=(需要存储的元素个数/加载因子)+1

6,TreeMap和HashMap有什么区别?

答案:TreeMap是有序Map,需要指定比较器或使用默认比较器。与HashMap组合了数组、链表、红黑树不同,TreeMap时完全有红黑树实现的。

引申:

       TreeMap的Put方法:

a)      如果TreeMap是空的,那么指定数据作为根节点。

b)      如果comparator不为空,使用comparator决定插入位置,否则认为key实现了Comparable,调用compareTo决定插入位置。

c)      插入完成,修复红黑树。

7,LinkedHashMap和HashMap有什么区别?

答案:LinkedHashMap的存储中包含了一个额外的双向链表结构,private transient Entry<K,V>header既是头又是尾(JDK1.8后有单独的tail),可以看成是一个环状链表,Hash桶中的每个节点都被这个环状链表的某个节点引用,从而达到有序访问目的。

8,HashTable与HashMap有什么区别?

答案:HashTable的实现方式被synchronized修饰,因此HashTable是线程安全的,而HashMap不是。此外HashTable不能存放null作为key,HashMap会把null key存在下标为0的位置。

引申:

       虽然Hashtable是线程安全的,但在多线程环境下并不推荐使用,因为采用synchronized方式实现的多线程安全容器在大量并发的情况下效率比较低,Java还引入了专门在大并发量情况下使用的并发容器,这种容器由于在实现时使用了更加细粒度的锁,由此在大并发量的情况下有着更好的性能。

9,什么是Java中Collectors的fast-fail特性?

答案:Java集合框架Iterator具有fast-fail的特性,在创建Iterator后,其它线程对Collection做了修改或当前线程调用非Iterator接口对集合做了修改,导致类似modCount!=expectedModCount,那么会抛出ConcurrentModificationException异常,产生fast-fail事件。

引申:

       针对ArrayList,fast-fail的实现是在其父类AbastractList中,当执行add、remove、clear等方法时,modCount会发生改变,expectedodCount不变,调用checkForModification检测是否modCount==expectedModCount。而JUC中的CopyOnWriteArrayList没有继承AbstractList,仅仅实现了List接口,进行增删改时没有所谓checkForModification。

Java面试题-Collection框架

原文:https://www.cnblogs.com/guanghe/p/13440571.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!