集合

List

ArrayList

1、ArrayList用过吗,是什么东西,可以用来干嘛

用过,ArrayList就是数组列表,主要用来装在数据,当我们装在的是基本数据类型如int、long、boolean、short、byte时,只可以存储他们的包装类,他的底层实现是一个Object类型的数组,特点就是查询效率高,增删的效率低,线程不安全,但是使用的频率高

与他类似的是LinkedList、和LinkedList相比,ArrayList查找和访问元素的速度较快,但是新增和删除的速度较慢

2、为啥线程不安全还要使用它呢

因为在我们正常的使用场景里,都是用来查询的,不会涉及太频繁的增删,如果涉及台频繁的增删,那么使用LinkedList,如果需要线程安全,就是用Vector

不存在一个集合工具查询效率高、增删效率也高,而且还线程安全,所以需要根据实际需求来取舍

3、ArrayList底层实现是数组,但是数组的大小是定长的,如果不断的往里面添加数据的话,不会有什么问题吗

ArrayList可以通过构造方法在初始化的时候指定底层数组的大小

通过无参构造方法实例化ArrayList的时候,底层为默认的空数组,只有在真正添加数据add的时候,才会分配初始容量10

4、数组长度是有限制的,而ArrayList可以存放任意数量的对象,是怎么实现的

通过数组扩容实现的,如果在新增元素的时候,发现数组已经满了,那么会重新定义一个长度为原来1.5倍的数组,并且将原数组的数据原封不动的复制的新数组里,然后再将指向原数组的地址换为新数组

5、具体说下1.7和1.8中初始化的区别

ArrayList在1.7中,初始化的时候就会调用this(10),创建一个容量为10的数组,在1.8中,默认创建的是一个空数组,只有在第一次add的时候,才会将容量变为10

6、ArrayList增删效率低,底层在增删的时候是怎么做的,为什么慢

ArrayList有指定index新增,也可以直接新增,在新增的时候,会先判断长度是否够,如果不够需要就需要扩容,这是导致增加效率低的一个原因,还有,在jdk8后,扩容采用的是为运算,相较于7已经快了许多了

如果在指定位置新增,在校验长度以后,实际上是数组的元素的移动,将指定位置后面的元素,统统向后移动一位,然后再讲新元素插入,删除元素的时候同理,在数据量大的时候,元素的移动,增加了很多系统开销,这是导致效率低的第二个原因

7、ArrayList是线程安全的吗

不是,线程安全的容器是Vector

但是也可以不使用Vector,使用Collections.synchronizedList方法可以将一个普通的ArrayList包装成一个线程安全版本的容器

8、ArrayList适合用来做队列吗

不适合,队列一般是先进先出的,如果用ArrayList做队列的话,那么需要在数组尾部追加数据,在数组的头部删除数据,反过来也可以,但是无论哪种方式,都会涉及到数组中数据的移动,非常耗费性能

9、那什么适合用来做队列

ArrayBlockingQueue,内部实现的就是一个环形队列,是一个定长的队列,内部使用的是定长数组实现

Library,也是通过环形数组实现高性能队列

10、ArrayList和LinkedList遍历性能比较

遍历的话,ArrayList快得多,因为ArrayList遍历最大的优势在于内存的连续性,CPU的内部缓存结构会缓存连续的内存片段,可以大幅度降低读取内存的性能开销

Map

HashMap

1、了解HashMap吗,聊聊它的结构和底层原理

了解,HashMap是非常常用的双列集合,底层有数据+链表或红黑树组成,默认长度16

数组里面每个地方都存在着以key-value形式存在的实例,jdk7中为entry,jdk8中为node

所以在没有插入的时候,它本身的所有位置都是null,put的时候根据key的hash去计算index值,也就是插入的位置

2、提到了链表,为什么需要用链表,链表是什么样子的

因为数组的长度优先,在有限的长度里面使用hash计算,因为hash本身具有概率性,也就是说,两次插入元素的key有概率一样,那么在同样的位置上就需要使用链表

3、新的Entry节点在插入链表的时候,是怎么插入的

jdk7之前是头插法,也就是新来的值,会取代原有的值,那么原来的值就会被顺推到链表中

jdk8之后,使用的尾部插入

4、什么时候resize

导致resize扩容的因素是两个,一个是Capacity,HashMap当前的长度,一个是LoadFactor,负载因子,默认是0.75,比如当前的容量为100,当存入第76个元素的时候,判断就需要进行resize了,那么就会进行扩容

5、怎么扩容的

第一步、创建一个新的Entry空数组,长度为原数组的2倍,遍历原来的Entry数组,并且将原来的Entry重新通过Hash计算添加到新数组中

6、为什么需要重新Hash计算而不是直接复制

因为计算index的Hash计算规则和数组长度有关

7、HashMap中链表大小超过8时自动转为红黑树,小于6时重新变为链表,为什么

根据泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7不转换,大于等于8时候转换为红黑树,小于等于6转换为链表

HashTable

1、说说HashTable

跟HashMap相比,HashTable是线程安全的,适合在多线程的情况下使用,因为底层在所有对数据操作的方法上都会添加synchronized,所以效率不高

2、处理线程方面,说说和HashMap的区别

存储元素:HashTable的key和value都不允许为null,但是HashMap的key和value都可以为null

实现方式:HashTable继承了Dictionary类,而HashMap继承的是AbstractMap类

初始化容量:HashMap的初始化容量16,HashTable初始容量为11

扩容机制:二者都是在到达总容量乘负载因子时扩容,但是HashMap是对当前容量乘2,而HashTable是当前容量乘2再加1

迭代器不同:HashMap中Iterator迭代器是fail-fast的,而HashTable的Enumerator不是fail-fast的

3、什么是fail-fast

fail-fast快速失败是java集合中的一种机制,在用迭代器遍历一个集合对时,如果在过程中,对集合对象内容进行了增、删、改,那么会抛出异常

原理是,在遍历时,直接访问集合中的内容,并且在遍历过程中使用一个变量,如果过程中集合内容改变,那么会改变这个变量值,每次在使用hashNext或next方法的时候,会检测这个变量是否为最开始的值,如果是就正常遍历,不是就会抛出异常

4、为什么HashTable的key-value不可以为null,HashMap可以

从底层原码上,HashMap对传入的key进行的了非空判断,而HashTable存入null会抛出空指针异常

由于HashTable使用的安全失败机制,这种机制会是此次读到的数据不一定是最新的数据,如果使用了null,那么无法判断对应的key是不存在还是为空