Java面试之容器-不古出品
- 1.java 容器都有哪些?
- 2.Collection 和 Collections 有什么区别?
- 3.集合List 、Set、Map的区别与联系
- 4.HashMap 和 Hashtable 有什么区别?
- 5.如何决定使用 HashMap 还是 TreeMap?
- 6.说一下 HashMap 的实现原理?
- 7.说一下 HashSet 的实现原理?
- 8.ArrayList 和 LinkedList 的区别是什么?
- 9.如何实现数组和 List 之间的转换?
- 10.ArrayList 和 Vector 的区别是什么?
- 11.Array 和 ArrayList 有何区别?
- 12.在 Queue 中 poll()和 remove()有什么区别?
- 13.哪些集合类是线程安全的?
- 14.迭代器 Iterator 是什么?
- 15.Iterator 怎么使用?有什么特点?
- 16.Iterator 和 ListIterator 有什么区别?
- 17.怎么确保一个集合不能被修改?
- 18.HashMap 的扩容过程
- 19.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
- 20.JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计?
- 21.阻塞队列的实现,ArrayBlockingQueue的底层实现?
- 22.Java 中的 LinkedList是单向链表还是双向链表?
- 23.说一说ArrayList 的扩容机制吧
- 24.谈谈线程池阻塞队列吧
- 25.HashMap在JDK1.7和JDK1.8中有哪些不同?
- 26.HashMap是怎么解决哈希冲突的?
1.java 容器都有哪些?
2.Collection 和 Collections 有什么区别?
-
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有List与Set。
-
Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。
3.集合List 、Set、Map的区别与联系
1、结构特点
- List、Set是存储单列的数据集合,都继承与Collection接口。
- Map是存储键值对这样的双列数据的集合,是个独立接口。
- List中存储的数据是有序的,可以是重复的。
- Set中存储的数据是无序的,且不允许重复。
- Map中存储的数据是无序的,他的键是不允许重复的,值是可以重复的。
2、相关的实现类
-
1.List的接口有三个实现类。
-
ArrayList
- 优点: 底层数据结构是数组,查询快,增删慢。
- 缺点: 线程不安全(一般不考虑到线程的安全因素,用Arraylist效率比较高)
-
LinkedList
- 优点: 底层数据结构是链表,增删快,查询慢。
- 缺点: 线程不安全。
-
Vector
- 优点: 底层数据结构是数组,查询快,增删慢。线程安全。
- 缺点: 效率低。
-
-
2.Set接口有三个实现类
-
HashSet
- 为快速查找而设计的Set,依赖hashCode()和equals()方法保证元素的唯一性。如果没有其他限制,这就是我们的默认选择,因为他对速度进行了优化。
-
TreeSet
- 保证元素处于排序状态,底层为树结构,元素必须实现Comparable接口。
-
LinkedHashSet
- 具有HsahSet的查询速度,内部使用链表维护元素的顺序(插入的次序)在使用迭代器遍历Set时,结果会按元素插入的次序显示。元素也必须定义hashCode()方法。
-
3.Map接口有6个实现类。
- HashMap
+ Map基于散列表的实现(取代了Hashtable)。插入和查询"键值对"的开销是固定的。可以通过构造器设置容量和负载因子,以调整容器性能。-
LinkedHashMap
- 类似于HashMap,但是迭代遍历他时,取得的"键值对"的顺序是其插入次序,或是最近最少使用的(LRU)的次序。只比HashMap慢一点;而在迭代访问时反而更快,因为他使用链表维护内部次序。
-
TreeMap
- 是SortedMap现阶段的唯一实现。基于红黑树实现。查看"键"或者"键值对"时,他们会被排序(次序由Comparable或Comparator j决定)。TreeMap的特点在于,所得到的结果是经过排序的。
-
WeakHashMap
- 弱键(weak key)映射,允许释放映射所指的对象;这是为解决某类特殊问题而设计的。如果映射之外没有引用指向某个"键",则此"键"可以被垃圾回收器回收。
-
ConcurrentHashMap
- 一种线程安全的Map。
-
IdentityHashMap
- 使用"=="代替"equals()"对键进行比较的散列映射,专门解决特殊问题而设计出的。
-
三者区别
4.HashMap 和 Hashtable 有什么区别?
-
相同点:
- hashmap和Hashtable都实现了map、Cloneable(可克隆)、Serializable(可序列化)这三个接口
-
不同点:
- 1.底层数据结构不同:jdk1.7底层都是数组+链表,但jdk1.8 HashMap加入了红黑树
- 2.Hashtable 是不允许键或值为 null 的,HashMap 的键值则都可以为 null。
- 3.添加key-value的hash值算法不同:HashMap添加元素时,是使用自定义的哈希算法,而HashTable是直接采用key的hashCode()
- 4.实现方式不同:Hashtable 继承的是 Dictionary类,而 HashMap 继承的是 AbstractMap 类。
- 5.初始化容量不同:HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75。
- 6.扩容机制不同:当已用容量>总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 +1。
- 7.支持的遍历种类不同:HashMap只支持Iterator遍历,而HashTable支持Iterator和Enumeration两种方式遍历
- 8.迭代器不同:HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。而Hashtable 则不会。
- 9.部分API不同:HashMap不支持contains(Object value)方法,没有重写toString()方法,而HashTable支持contains(Object value)方法,而且重写了toString()方法
- 10.同步性不同: Hashtable是同步(synchronized)的,适用于多线程环境,
而hashmap不是同步的,适用于单线程环境。多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。
-
由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
-
什么时候使用Hashtable,什么时候使用HashMap?
-
基本的不同点是Hashtable同步HashMap不是的,所以无论什么时候有多个线程访问相同实例的可能时,就应该使用Hashtable,反之使用HashMap。非线程安全的数据结构能带来更好的性能。
-
如果在将来有一种可能—你需要按顺序获得键值对的方案时,HashMap是一个很好的选择,因为有HashMap的一个子类 LinkedHashMap。所以如果你想可预测的按顺序迭代(默认按插入的顺序),你可以很方便用LinkedHashMap替换HashMap。反观要是使用的Hashtable就没那么简单了。同时如果有多个线程访问HashMap,Collections.synchronizedMap()可以代替,总的来说HashMap更灵活。
-
5.如何决定使用 HashMap 还是 TreeMap?
- 对于在Map中插入、删除和定位元素这类操作,HashMap是最好的选择。然而,假如你需要对一个有序的key集合进行遍历,TreeMap是更好的选择。基于你的collection的大小,也许向HashMap中添加元素会更快,将map换为TreeMap进行有序key的遍历。
6.说一下 HashMap 的实现原理?
-
HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
-
HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
-
当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。
-
需要注意Jdk 1.8中对HashMap的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的O(n)到O(logn)
7.说一下 HashSet 的实现原理?
- HashSet底层由HashMap实现;
- HashSet的值存放于HashMap的key上;
- HashMap的value统一为PRESENT.
8.ArrayList 和 LinkedList 的区别是什么?
- 最明显的区别是 ArrrayList底层的数据结构是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。
9.如何实现数组和 List 之间的转换?
- List转换成为数组:调用ArrayList的toArray方法。
- 数组转换成为List:调用Arrays的asList方法。
10.ArrayList 和 Vector 的区别是什么?
- ArrayList和Vector都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引来取出某个元素,并且其中的数据是允许重复的。
区别
-
1、同步性:
Vector 是线程安全的,也就是说它的方法之间是线程同步(加了synchronized 关键字)的,而 ArrayList 是线程不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线程安全的问题,所以效率会高一些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。 -
2、数据增长:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个人超过了容量时,就需要增加 ArrayList 和 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要去的一定的平衡。Vector 在数据满时(加载因子1)增长为原来的两倍(扩容增量:原容量的 2 倍),而 ArrayList 在数据量达到容量的一半时(加载因子 0.5)增长为原容量的 (0.5 倍 + 1) 个空间。
11.Array 和 ArrayList 有何区别?
- Array可以容纳基本类型和对象,而ArrayList只能容纳对象。
- Array是指定大小后不可变的,而ArrayList大小是可变的。
- Array没有提供ArrayList那么多功能,比如addAll、removeAll和iterator等。
12.在 Queue 中 poll()和 remove()有什么区别?
- poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。
13.哪些集合类是线程安全的?
- vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
- statck:堆栈类,先进后出。
- hashtable:就比hashmap多了个线程安全。
- enumeration:枚举,相当于迭代器。
14.迭代器 Iterator 是什么?
- 迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
15.Iterator 怎么使用?有什么特点?
-
Java中的Iterator功能比较简单,并且只能单向移动:
- (1) 使用方法iterator()要求容器返回一个Iterator。第一次调用Iterator的next()方法时,它返回序列的第一个元素。注意:iterator()方法是java.lang.Iterable接口,被Collection继承。
- (2) 使用next()获得序列中的下一个元素。
- (3) 使用hasNext()检查序列中是否还有元素。
- (4) 使用remove()将迭代器新返回的元素删除。
-
Iterator是Java迭代器最简单的实现,为List设计的ListIterator具有更多的功能,它可以从两个方向遍历List,也可以从List中插入和删除元素。
16.Iterator 和 ListIterator 有什么区别?
- Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。
- Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。
- ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。
17.怎么确保一个集合不能被修改?
可以采用Collections包下的方法
- Collections.unmodifiableMap方法证map不被修改。
- Collections.unmodifiableList()方法证list和set集合不被修改。
- Collections.unmodifiableSet() 方法保证set集合不被修改
18.HashMap 的扩容过程
Hashmap的扩容:
- 第一步把数组长度变为原来的两倍
- 第二步把旧数组的元素重新计算hash插入到新数组中。
- jdk8时,不用重新计算hash,只用看看原来的hash值新增的一位是零还是1,如果是1这个元素在新数组中的位置,是原数组的位置加原数组长度,如果是零就插入到原数组中。扩容过程第二步一个非常重要的方法是transfer方法,采用头插法,把旧数组的元素插入到新数组中。
19.快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
- 快速失败
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。 - 安全失败
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。 - 其实,在java.util.concurrent 并发包的集合,如 ConcurrentHashMap, CopyOnWriteArrayList等,默认为都是安全失败的。
20.JAVA8的ConcurrentHashMap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计?
- jdk8 放弃了分段锁而是用了Node锁,减低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。
21.阻塞队列的实现,ArrayBlockingQueue的底层实现?
ArrayBlockingQueue是数组实现的线程安全的有界的阻塞队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。常用的操作包括 add ,offer,put,remove,poll,take,peek。
22.Java 中的 LinkedList是单向链表还是双向链表?
是双向链表,看源码
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
23.说一说ArrayList 的扩容机制吧
- ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。
public boolean add(E e) {
//扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//如果传入的是个空数组则最小容量取默认容量与minCapacity之间的最大值
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 如果最小需要空间比elementData的内存空间要大,则需要扩容
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
// 获取elementData数组的内存空间长度
int oldCapacity = elementData.length;
// 扩容至原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//校验容量是否够
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//若预设值大于默认的最大值,检查是否溢出
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 调用Arrays.copyOf方法将elementData数组指向新的内存空间
//并将elementData的数据复制到新的内存空间
elementData = Arrays.copyOf(elementData, newCapacity);
}
24.谈谈线程池阻塞队列吧
-
ArrayBlockingQueue
-
LinkedBlockingQueue
-
DelayQueue
-
PriorityBlockingQueue
-
SynchronousQueue
-
ArrayBlockingQueue: (有界队列)是一个用数组实现的有界阻塞队列,按FIFO排序量。
-
LinkedBlockingQueue: (可设置容量队列)基于链表结构的阻塞队列,按FIFO排序任务,容量可以选择进行设置,不设置的话,将是一个无边界的阻塞队列,最大长度为Integer.MAX_VALUE,吞吐量通常要高于ArrayBlockingQuene;newFixedThreadPool线程池使用了这个队列
-
DelayQueue:(延迟队列)是一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序。newScheduledThreadPool线程池使用了这个队列。
-
PriorityBlockingQueue:(优先级队列)是具有优先级的无界阻塞队列;
-
SynchronousQueue:(同步队列)一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQuene,newCachedThreadPool线程池使用了这个队列。针对面试题:线程池都有哪几种工作队列?
25.HashMap在JDK1.7和JDK1.8中有哪些不同?
26.HashMap是怎么解决哈希冲突的?
- Hashmap解决hash冲突,使用的是链地址法,即数组+链表的形式来解决。put执行首先判断table[i]位置,如果为空就直接插入,不为空判断和当前值是否相等,相等就覆盖,如果不相等的话,判断是否是红黑树节点,如果不是,就从table[i]位置开始遍历链表,相等覆盖,不相等插入。