集合类面经
请说明Java集合类框架的基本接口有哪些?
Collection,List,Set, Queue,Deque,Map
请你讲讲wait方法的底层原理
请说明List、Map、Set三个接口存取元素时,各有什么特点?
List:有序可重复
Map:键值对的方式存储数据,key唯一,value值可以重复,相同的key不同valuse会被最后一个覆盖
Set:无序不可重复
阐述ArrayList、Vector、LinkedList的存储性能和特性
-
ArrayList和Vector都是使用数组方式存储数据,插入,删除数据速度慢,但是Vector是线程安全的。但性能上比ArrayList差
-
ArrayList:插入,删除速度慢,但是定位快;ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
-
LinkedList:双向链表实现存储,插入,删除速度快,但是定位慢,按序号索引数据需要进行前向或后向遍历。
ArrayList与LinkedList的区别?
-
ArrayList:是以数组的方式存储数据的,直接根据序号索引,定位快,但是在插入数据的时候需要移动数组元素,因此插入,删除数据慢;
-
LinkedList是以双向链表的结构存储数据,按照序号索引时,需要前序和后序遍历,因此定位慢,但是插入数据的时候只要知道前后项即可,所以插入,删除数据快。
ArrayList与Vector的区别?
- 同步性:
Vector是线程安全的,也就是说是它的方法之间是线程同步的,而ArrayList是线程序不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用ArrayList,因为它不考虑线程安全,效率会高些;如果有多个线程会访问到集合,那最好是使用Vector,因为不需要我们自己再去考虑和编写线程安全的代码。 - 数据增长:
- ArrayList与Vector都有一个初始的容量大小,当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡。
- Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍)。ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法。
Array 和 ArrayList 有何区别?
-
Array 可以包含基本数据类型和引用类型,ArrayList只能包含引用类型。
-
ArrayList是基于数组实现的,Array大小不可以调整大小,但ArrayList可以通过内部方法自动调整容量。
-
ArrayList是List接口的实现类,相比Array支持更多的方法和特性。
请判断List、Set、Map是否继承自Collection接口?
List,Set继承自Collection
Map没有继承Collection
List、Set、Map 之间的区别是什么?
-
List(列表)
List的元素以线性方式存储,可以存放重复对象,List主要有以下两个实现类:-
1.ArrayList: 长度可变的数组,可以对元素进行随机的访问,向ArrayList中插入与删除元素的速度慢。JDK8中ArrayList扩容的实现是通过grow()方法里使用语句newCapacity = oldCapacity + (oldCapacity >> 1)(即1.5倍扩容)计算容量,然后调用Arrays.copyof()方法进行对原数组进行复制。
-
2.LinkedList: 采用链表数据结构,插入和删除速度快,但访问速度慢。
-
-
Set(集合)
Set中的对象不按特定(HashCode)的方式排序,并且没有重复对象,Set主要有以下两个实现类:- 1.HashSet:HashSet按照哈希算法来存取集合中的对象,存取速度比较快。当HashSet中的元素个数超过数组大小*loadFactor(默认值为0.75)时,就会进行近似两倍扩容(newCapacity = (oldCapacity << 1) + 1)。
- 2.TreeSet:TreeSet实现了SortedSet接口,能够对集合中的对象进行排序。
-
Map(映射)
Map是一种把键对象和值对象映射的集合,它的每一个元素都包含一个键对象和值对象。Map主要有以下实现类:- HashMap:HashMap基于散列表实现,其插入和查询<K,V>的开销是固定的,可以通过构造器设置容量和负载因子来调整容器的性能。
- LinkedHashMap:类似于HashMap,但是迭代遍历它时,取得<K,V>的顺序是其插入次序,或者是最近最少使用(LRU)的次序。
- TreeMap:TreeMap基于红黑树实现。查看<K,V>时,它们会被排序。 TreeMap是唯一的带有subMap()方法的Map,subMap()可以返回一个子树。
请讲讲你所知道的常用集合类以及主要方法?
-
ArrayList:add,addAll,remove,clear,get,indexOf,contains,set,toArray,size
-
Deque:getFirst,getLast,removeFirst,removeLast,addFirst
-
Queue:offer,poll,peek
-
Stacke:push,pull
-
HashMap:get(key,value),put(key,value)
请说明Collection 和 Collections的区别。
-
Collection是集合对象的上级接口,提供了集合对象通用的接口方法,直接继承该接口的有List和Set
-
Collections是针对集合类的一个帮助类,提供一系列静态方法实现对各种集合的搜索,排序,线程安全化等操作。
请你说明HashMap和Hashtable的区别?
-
继承不同: Hashtable是继承自Dictionary类的,而HashMap则是Java 1.2引进的Map接口的一个实现。;两者都实现Map接口
-
安全性不同:HashTable是默认线程安全的,多个线程可以共享HashTable,HashMap是非线程安全的,如果没有正确的同步的话,多个线程是不能共享HashMap的。Collections.synchronizeMap(hashMap);可以使map同步。
-
是否可为空值的异同:HashMap允许key和value为null,但是key只能一个为null,可以有多个value为null,HashTable中的key和value都不能为null。
-
二者的遍历方式的内部实现上不同:两者都使用了迭代器,HashMap是fail-fast迭代器,HashTable是enumerator迭代器。
-
哈希值的使用不同:HashTable是直接使用对象的HashCode,而HashMap是重新计算hash的值。
-
二者内部实现方式的数组的初始大小和扩容的方式不同: HashTable中hash数组默认大小是11,增加的方式是 old*2+1;HashMap中hash数组的默认大小是16,而且一定是2的指数。
请解释为什么集合类没有实现Cloneable和Serializable接口?
-
克隆(cloning)或者是序列化(serialization)的语义和含义是跟具体的实现相关的。因此,应该由集合类的具体实现来决定如何被克隆或者是序列化。
-
实现Serializable序列化的作用:将对象的状态保存在存储媒体中以便可以在以后重写创建出完全相同的副本;按值将对象从一个从一个应用程序域发向另一个应用程序域。
-
实现 Serializable接口的作用就是可以把对象存到字节流,然后可以恢复。所以你想如果你的对象没有序列化,怎么才能进行网络传输呢?要网络传输就得转为字节流,所以在分布式应用中,你就得实现序列化。如果你不需要分布式应用,那就没必要实现实现序列化。
-
什么时候需要序列化:想把内存中的对象状态保存到一个文件中或者数据库中时候;想把对象通过网络进行传播的时候
-
如何序列化:类实现接口Serializable
请你说说Iterator和ListIterator的区别?
-
1.ListIterator 只能对 List 迭代,而 Iterator 不仅可以对 List 和他子类型迭代,还可以迭代 Set,Map和这些集合的子类型。
-
2.ListIterator 可以双向迭代,可以向前和向后遍历,而 Iterator 只能单向迭代,只能向后遍历。
-
3.ListIterator 继承 Iterator 接口增加了更多的方法,如下图。
请简单说明一下什么是迭代器?
-
遍历集合
-
Iterator的一个很强的用处:能够将遍历序列的操作与序列底层的结构分离。
请说说快速失败(fail-fast)和安全失败(fail-safe)的区别?
- 快速失败: 在使用迭代器对集合对象进行遍历的时候,如果A线程对集合进行遍历,正好B线程对集合进行修改(增加、删除、修改)则A线程会抛出ConcurrentModificationException异常。
- 快速失败原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
- 安全失败:采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
- 安全失败原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception,例如CopyOnWriteArrayList。
请你说明一下Map和ConcurrentHashMap的区别?
HashMap和HashTable相同点:HashMap和HasheTable都可以使用来存储key–value的数据。
HashMap和HashTable区别:
- HashMap是可以把null作为key或者value的,而HashTable是不可以的。
- HashMap是线程不安全的,效率较高。而HashTalbe是线程安全的,效率较低。
ConcurrentHashMap,通过把整个Map分为N个Segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。所以ConcurrentHashMap实现线程安全并且效率高。
请你解释一下hashMap具体如何实现的?
- put(key,value)的原理:
HashMap mapDemo=new HashMap()
mapDemo.put("name","Aiden")时,
Node node=new Node("name","Aiden");
int index=hash("name")&length-1,假如算出index=2
tab[2]=node;
调用map的put方法的时候,方法内部会调用hash(key)方法,计算出key的hash值,根据hash值将对象直接放到hash值为下标的位置。
- get(key)的原理:
一般情况下,调用hash内部的hash(key)计算hash值,根据hash值找到对象地址,比较在该位置的内容是否一样;但是如果是链表结构或者红黑树结构,查询到的该位置的内容跟要找的不一样,那么可以通过对象向下遍历,然后比较内容,直到找到为止。
如果hashMap的key是一个自定义的类,怎么办?
-
使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()。
-
1.hashcode()和equals()是在哪里被用到的?什么用的? HashMap是基于散列函数,以数组和链表的方式实现的。
-
而对于每一个对象,通过其hashCode()方法可为其生成一个整形值(散列码),该整型值被处理后,将会作为数组下标,存放该对象所对应的Entry(存放该对象及其对应值)。
equals()方法则是在HashMap中插入值或查询时会使用到。当HashMap中插入 值或查询值对应的散列码与数组中的散列码相等时,则会通过equals方法比较key值是否相等,所以想以自建对象作为HashMap的key,必须重写 该对象继承object的hashCode和equals方法。 -
2.本来不就有hashcode()和equals()了么?干嘛要重写,直接用原来的不行么? HashMap中,如果要比较key是否相等,要同时使用这两个函数!因为自定义的类的hashcode()方法继承于Object类,其hashcode码为默认的内存地 址,这样即便有相同含义的两个对象,比较也是不相等的,例如,生成了两个“羊”对象,正常理解这两个对象应该是相等的,但如果你不重写 hashcode()方法的话,比较是不相等的! HashMap中的比较key是这样的,先求出key的hashcode(),比较其值是否相等,若相等再比较equals(),若相等则认为他们是相等 的。若equals()不相等则认为他们不相等。如果只重写hashcode()不重写equals()方法,当比较equals()时只是看他们是否为 同一对象(即进行内存地址的比较),所以必定要两个方法一起重写。HashMap用来判断key是否相等的方法,其实是调用了HashSet判断加入元素 是否相等。
请你简单介绍一下ArrayList和LinkedList的区别,并说明如果一直在list的尾部添加元素,用哪种方式的效率高?
-
ArrayList:是以数组的方式存储数据的,直接根据序号索引,定位快,但是在插入数据的时候需要移动数组元素,因此插入,删除数据慢;
-
LinkedList是以双向链表的结构存储数据,按照序号索引时,需要前序和后序遍历,因此定位慢,但是插入数据的时候只要知道前后项即可,所以插入,删除数据快。
-
当输入的数据小于千万级别的时候,大部分是LinkedList效率高,但是数据大于千万的时候,ArrayList效率高。原因是,在LinkedList添加数据的时候,会new一个Node对象来存数据,ArrayList需要扩容,所以当数据小的时候,这个new对象的时间不明显,比扩容时间少,那么LinkedList的效率高,但是数据大的时候,new对象的时间大于扩容时间,那么ArrayList的效率就高。
请你解释HashMap的容量为什么是2的n次幂?
请你说明ConcurrentHashMap锁加在了哪些地方?
- 加在每个Segment 上面。
请你说明一下TreeMap的底层实现?
请你说明concurrenthashmap有什么优势以及1.7和1.8区别?
HashMap:
JDK1.8之前 JDK1.8 之前 HashMap 底层是 数组和链表 结合在一起使用也就是 链表散列。
JDK1.8之后 相比于之前的版本, JDK1.8之后在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转 化为红黑树,以减少搜索时间。
ConcurrentHashMap:
JDK1.7的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,
JDK1.8 采用的数据结构跟 HashMap1.8的结构一样,数组+链表/红黑二叉树。
HashTable:
Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类 似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
请说明ArrayList是否会越界?
ArrayList并发add()可能出现数组下标越界异常。