1、java集合全景图

从图中可以看出,Java集合主要由两大接口(Collection和Map)及其实现类组成。
2、List、Set、Map各自特点
- List:存储的元素是有序的、可重复的。
- Set:存储的元素是无序的、不可重复的。
- Map:使用键值对存储,Key 是无序的、不可重复的,value 是可重复的,每个键最多映射到一个值。
总结:List一般用在用于顺序存储,Set一般用于唯一存储(每个元素只能在set中出现一次),Map一般用于键值对存储和查找。
3、如何选用集合?
选用原则:根据各集合的特点来选用。
- 如果有根据键值来获取元素值的需求,则使用Map接口下的实现类,需要排序时,用TreeMap,不需要排序时就选择HashMap,需要保证线程安全就选用ConcurrentHashMap。
- 如果只是存放元素,则使用Collection接口下的实现类,需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSet 或 HashSet,不需要就选择实现 List 接口的比如 ArrayList 或 LinkedList,然后再根据实现这些接口的集合的特点来选用。
4、为什么要使用集合?
当我们需要保存一组类型相同的数据的时候,我们下意识的选择数组来存储,但是数组有他固有的缺点:
- 长度不可变
- 数组存储有序,可重复
当我们需要一种集合,它可以自适应的扩容,且元素存储无序且唯一,这时候数组显然不适用,这时候就需要像集合这样强用力的工具来辅助我们。
5、Iterator 迭代器是什么,怎么使用
Iterator是一个接口,提供方法访问一个容器对象中各个元素。在各集合类中有它的具体实现。
Iterator 主要是用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
在HashMap源码分析篇,已经写过HashMap遍历方式,与此处类同,不再赘述。
6、集合框架底层数据结构总结
- List
- ArrayList: Object[ ]
- vector: Object[ ]
- LinkdedList: 双向链表
- Set
-
HashSet: 基于HashMap实现,底层使用HashMap来保存元素
-
LinkedHashSet : HashSet 的子类,并且其内部是通过 LinkedHashMap 来实现的
-
TreeSet: 红黑树(自平衡的排序二叉树,有序、唯一)
- Map
- HashMap: 数组+链表+红黑树, 见源码分析
- LinkedHashMap: LinkedHashMap 继承自 HashMap,在上面结构的基础上,增加了一条双向链表,使得上面的结构可以保持键值对的插入顺序。
- Hashtable: 数组+链表
- TreeMap:红黑树
7、有哪些集合是线程不安全的?怎么解决?
不安全的集合有:ArrayList、LinkedList、HashMap、HashSet、TreeSet、TreeMap、PriorityQueue等。
解决:使用juc包下的并发容器,如:ConcurrentHashMap、CopyOnWriteArrayList等。
8、无序性和不可重复性的含义是什么
- 什么是无序性?
无序性不等于随机性 ,无序性是指存储的数据在底层数组中并非按照数组索引的顺序添加 ,而是根据数据的哈希值决定的。
- 什么是不可重复性?
不可重复性是指添加的元素按照 equals()判断时 ,需要同时重写 equals()方法和 HashCode()方法。
9、ArrayList和vector区别?ArrayList和LinkedList区别?
-
ArrayList、Vector都是Object[ ]数组存储,Vector是线程安全的,ArrayList不是。
-
ArrayList和LinkedList都不是线程安全的。
-
ArrayList底层是Object[ ]数组,LinkedList底层是双向链表。
-
LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。
-
ArrayList 的空间浪费主要体现在在 list 列表的结尾会预留一定的容量空间,而 LinkedList 的空间花费则体现在它的每一个元素都需要消耗比 ArrayList 更多的空间(因为要存放直接后继和直接前驱以及数据)。
10、HashMap 和 Hashtable 的区别
-
HashMap 是非线程安全的,HashTable 是线程安全的,HashTable 内部的方法基本都经过synchronized 修饰。
-
HashMap 要比 HashTable 效率高
-
HashMap 可以存储 null 的 key 和 value,但 null 作为键只能有一个;HashTable 不允许有 null 键和 null 值,否则会抛出 NullPointerException。
-
创建时如果不指定容量初始值,Hashtable 默认的初始大小为 11,之后每次扩充,容量变为原来的 2n+1。HashMap 默认的初始化大小为 16。之后每次扩充,容量变为原来的 2 倍。
-
创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为 2 的幂次方大小(HashMap 中的tableSizeFor()方法保证)。也就是说 HashMap 总是使用 2 的幂作为哈希表的大小
-
JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为 8)(将链表转换成红黑树前会判断,如果当前数组的长度小于 64,那么会选择先进行数组扩容,而不是转换为红黑树)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
11、TreeMap和HashMap
TreeMap 和HashMap 都继承自AbstractMap ,但是需要注意的是TreeMap它还实现了NavigableMap接口和SortedMap 接口。
-
实现 NavigableMap 接口让 TreeMap 有了对集合内元素的搜索的能力。
-
实现SortMap接口让 TreeMap 有了对集合中的元素根据键排序的能力。默认是按 key 的升序排序
12、ConcurrentHashMap 和 Hashtable 的区别
- 底层数据结构
JDK1.7 的 ConcurrentHashMap 底层采用分段的数组+链表 实现,JDK1.8采用的数据结构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的 HashMap 的底层数据结构类似都是采用 数组+链表 的形式,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突而存在的;
- 实现线程安全的方式
在 JDK1.7 的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。
13、Collections工具类
常用方法:
void reverse(List list)//反转 常用!!!
void shuffle(List list)//随机排序
void sort(List list)//按自然排序的升序排序
void sort(List list, Comparator c)//定制排序,由Comparator控制排序逻辑
void swap(List list, int i , int j)//交换两个索引位置的元素
void swap(Object[] arr, int i, int j)
void rotate(List list, int distance)//旋转。当distance为正数时,将list后distance个元素整体移到前面。当distance为负数时,将 list的前distance个元素整体移到后面
14、什么是快速失败(fail-fast)和安全失败(fail-safe)

快速失败(fail-fast) 是 Java 集合的一种错误检测机制。在使用迭代器对集合进行遍历的时候,我们在多线程下操作非安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出 ConcurrentModificationException 异常。 另外,在单线程下,如果在遍历过程中对集合对象的内容进行了修改的话也会触发 fail-fast 机制。
什么是Fail Fast Iterator(简称FFI吧)呢?其实上面的图我是摘别人的,不是很准确,符合FFI的必须是继承了一个抽象的类,比如说ArrayList继承了AbstractList,LinkedList继承了AbstractSequentialList,他们两个的iterator都是有fail-fast的机制的。
对于Fail Safe Iterator,属于这种安全失败机制的类一般都是juc包下的类。
注:增强 for 循环也是借助迭代器进行遍历。
错误检测机制:每当迭代器使用 hashNext()/next()遍历下一个元素之前,都会检测 modCount 变量是否为 expectedModCount 值,是的话就返回遍历;否则抛出异常,终止遍历。
但是如果不是通过集合内的方法修改集合,而是通过Iterator 的方法修改集合的话,会修改到 expectedModCount 的值,所以不会抛出异常。
List<String> list = new ArrayList<>();
...省略若干操作
Iterator<String> iter = list.iterator();
while(iter.hasNext()){
String str = iterator.next();
if(删除元素的条件){
iter.remove();
}
}
安全失败机制:在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 ConcurrentModificationException 异常。
15、Arrays.asList()注意事项
Arrays.asList将一个数组转换为一个 List 集合。
List<Integer> list1 = Arrays.asList(1, 2, 3);
List<String> list2 = Arrays.asList("1", "2", "3");
String[] arr = {"1","2", "3"};
List<String> list3 = Arrays.asList(arr);
int[] arr1 = {1,2,3};
List<String> list4 = Arrays.asList(arr1); //不允许,编译出错,只能是对象数组,不能是基本数据类型数组
使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,比如List.add/remove/clear方法会抛出UnsupportedOperationException异常。
注:asList的返回对象是一个Arrays内部类,并没有实现集合的修改方法,Arrays.asList()体现的是适配器模式,只是转换接口,后台的数据仍是数组
List<String> list = Arrays.asList("1", "2", "3");
System.out.println(list.getClass()); //class java.util.Arrays$ArrayList
抛出UnsupportedOperationException异常的原因解析:
在Arrays的内部类ArrayList中,没有add、remove和clear等方法,而Arrays继承自AbstractList,AbstractList内有这些方法,看看他们里面的操作是什么,就不言而喻了。
add方法:
public boolean add(E e) {
add(size(), e);
return true;
}
public void add(int index, E element) {
throw new UnsupportedOperationException();
}
remove方法:
public E remove(int index) {
throw new UnsupportedOperationException();
}
clear方法:
public void clear() {
removeRange(0, size());
}
removeRange调用如下:
protected void removeRange(int fromIndex, int toIndex) {
ListIterator<E> it = listIterator(fromIndex);
for (int i=0, n=toIndex-fromIndex; i<n; i++) {
it.next();
it.remove();
}
}
//it.remove()的方法调用如下
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
//AbstractList.this.remove(lastRet)调用如下
public E remove(int index) {
throw new UnsupportedOperationException();
}