48. Java中有哪些常见的集合类?
Java中的容器集合类主要包括 Collection 和 Map 两大类,而 Collection 接口又包括 Set 和 List 两个子接口;
List接口的实现类:ArrayList LinkList Stack Vector等;
Set接口实现类:HashSet SortedSet TreeSet LinkHashSet等;
Map接口实现类:HashMap HashTable TreeMap ConcurrentHashMap等;
49. 数组和集合的区别
数组长度固定不变;集合长度可变;
数组可以存放基本数据类型;集合只能存放引用数据类型;
数据存储的元素必须时同一类型的;集合存储的元素是任意类型的;
50.集合是否可以存储null
List接口:ArrayList、LinkedList以及Vector等可以存储多个null;
Set接口:HashSet、LinkedSet可以存储一个null,Treeset不可以存储null;
Map接口:HashMap、LinkedHashMa 的 key 与 value 均可以为 null。Treemap 的 key 不可以为 null,value 可以为 null。HashTable、ConcurrentHashMap 的 key 与 value 均不能为 null。
51. ArrayList 和 LinkList 区别
ArrayList:底层数组,查找快,增删慢;
LinkList:底层是双向链表,增删快,查找慢;
52. ArrayList 和 Vertor 的区别
ArrayList:线程不安全,每次增长为原来的1.5倍;
Vertor :线程安全,每次增长为原来的1倍;
53. ListIterator和Iterator的区别
ListIterator是继承于Iterator的一个接口,功能更加强大的迭代器,但只能应用各种List类型的访问;
主要区别:
Iterator支持list和set,而ListIterator只支持list;
Iterator只支持单向访问,List Iterator支持双向前后遍历;
54.什么是 RandomAccess 接口
为了标记List集合是否支持随机访问设计;
支持RandomAccess接口的集合使用for循环比Iterator和foreach快,反之Iterator和foreach更快一些;
55.什么是Fail-Fast和Fail-Safe
在Collection集合类中,有线程安全和线程不安全的两大类型,对于线程不安全的类在并发情况下有可能出现Fail-Fast,线程安全的可能会出现Fail-Safe;
56. transient有什么作用?
Serilizable可以序列化一个对象,被transient修饰的变量不会被序列化到指定的地方;只能修饰变量,不能修饰类和方法;
57. ArrayList中elementData为什么被transient修饰?
为了节省空间和时间;因为element Data是一个缓存数组,通常会预留一些容量,等容量不足时在扩充容量那么有些空间可能就没有存储实际元素,使用transient这个关键字就可以实现节省空间和时间;
58. HashMap底层数据结构
JDK1.7版本:底层使用数组+链表的方式存储数据;
JDK1.8版本:底层使用数组+链表+红黑树形式存储数据;
59.链表和红黑树之间的转化
链表长度大于8并且数组长度大于等于64,则将链表转为红黑树;
链表长度小于6时会将红黑树转为链表;
60.阈值为何是8和6
为何阈值为8:hashCode算法下所有桶中节点分布频率会遵循泊松分布,即当链表长度达到八个元素的概率为0.00000006,几乎不可能;
为何阈值为6:主要还是为了避免红黑树和链表之间频繁转换带来的性能损耗;
61.为何使用红黑树而非二叉树或平衡树?
平衡二叉树是比红黑树更加严格的平衡树,为了达到平衡需要进行更多的旋转次数,所以红黑树插入删除操作效率更高。
62.put 操作的执行流程?
首先通过hash()方法计算出key的hash值;
判断桶数组长度是否为零或者null,是就调用resize()方法进行初始化;
根据数组长度-1对hash值进行&运算,计算出key在数组中对应的下标位置,如果该位置没有数据则直接插入新数据,有数据则有三中处理情况:
①:对应数组当前node节点的key和新元素的hash值相等,且key也相等,则使用新元素的值覆盖旧值;
②:对应数组当前node节点如果是树形节点,则将新元素直接插入红黑树中;
③:对应数组当前node节点是链表节点,则遍历此链表到尾部,并将新元素插入到链表尾部,插入成功之后在根据链表长度和树华阈值的比较判断是否需要进行树化;
最后在所有元素处理完毕后,还需判断当前元素大小是否超过扩容阈值,超过则到调用resize方法进行扩容;
63. HashMap 容量为何是 2 的倍数?
为了方便哈希取余,因为当除数是2的倍数时,可以将取模运算替换成&位运算,达到了更快的运算效率,和节省系统资源;
扩容后容量也是2的倍数,则每个桶中的节点要么保留在当前位置,要么移动到2的n次方位置,这要可以快速简洁的完成数据迁移;
64.为什么负载因子时0.75
主要出于对时间和空间成本的综合考量;
如果设置的负载因子过大,元素比较多时,扩容时桶数组发生碰撞的位置较少,查找的时间的成本就增加了;
如果设置的负载因子过小,元素比较少时,数组还有足够空闲的桶位置时就发生了扩容,虽然哈希碰撞的几率降低了,查找时间成本降低,但是就需要更多的空间去存储元素,空间成本就增加了;
65. HashMap是如何进行扩容的
在map新元素插入之后会进行容量检查,当超过阈值时,就调用resize方法进行扩容;
66. JDK1.8中有哪些优化
数据结构:把数组+链表改成了数组+链表/红黑树的结构;
插入方式:put操作链表的方式从头插入该成了尾插入,因为头插入扩容时会使链表发生反转,多线程环境下会发生反转,并发情况下hashmap头插入可能会造成死循环;
扩容时机:1.7是在插入前进行判断,1.8是在插入后进行判断;
67.HashMap 是否线程安全以及如何解决?
不安全:
可能会引发的问题:
多线程的put时元素的丢失;(如果两个线程计算出来的索引位置是相同的,那会造成前一个 key 被后一个 key 覆盖,从而导致前一个元素的丢失)
如何解决:
使用线程安全的map,如Hashtable,Collections.SynchronizedMap以及ConcurrentHashMap等;
hashtable是直接在操作方法上增加synchronized关键字,锁住整个table数组,粒度比较大;
Collections.SynchronizedMap是使用Collections集合工具的内部类,通过传入Map封装出一个SynchronizedMap对象,内部定义了一个对象锁,方法内通过对象锁实现;
ConcurrentHashMap在JDK1.7中使用分段锁,在JDK1.8中使用CAS+Synchronized;
68. ConcurrentHashMap 如何实现的
JDK1.7中是基于分段锁实现的;
JDK1.8中是基于CAS+Synchronized实现的;
69. ConcurrentHashMap 为什么value和值不能为null
value不能为null的原因:
因为ConcurrentHashMap是用于多线程的,如果value为null了,get(key)得到一个null,就无法判断究竟是值为null,还是根本没有这个key,所以返回null,因为就有了二义性;
key不能为null的原因:
同上;
70.LinkedHashMap如何实现有序?
LinkedHashMap在实现hashMap的基础上,还将每个key-value对应的node维护了一个额外的双向链表中;
71. TreeMap 如何实现有序?
TreeMap是按照key的自然顺序或者comparator的顺序进行排序,内部是通过红黑树实现,总之,要么key所属的类实现了comparable接口,要么自定义了一个comparator接口的比较器;