diff --git a/docs/java/collection/Java集合框架常见面试题.md b/docs/java/collection/Java集合框架常见面试题.md index fe58927f..0f490ace 100644 --- a/docs/java/collection/Java集合框架常见面试题.md +++ b/docs/java/collection/Java集合框架常见面试题.md @@ -10,11 +10,6 @@ - [1.1.3.3. Map](#1133-map) - [1.1.4. 如何选用集合?](#114-如何选用集合) - [1.1.5. 为什么要使用集合?](#115-为什么要使用集合) - - [1.1.6. Iterator 迭代器](#116-iterator-迭代器) - - [1.1.6.1. 迭代器 Iterator 是什么?](#1161-迭代器-iterator-是什么) - - [1.1.6.2. 迭代器 Iterator 有啥用?](#1162-迭代器-iterator-有啥用) - - [1.1.6.3. 如何使用?](#1163-如何使用) - - [1.1.7. 有哪些集合是线程不安全的?怎么解决呢?](#117-有哪些集合是线程不安全的怎么解决呢) - [1.2. Collection 子接口之 List](#12-collection-子接口之-list) - [1.2.1. Arraylist 和 Vector 的区别?](#121-arraylist-和-vector-的区别) - [1.2.2. Arraylist 与 LinkedList 区别?](#122-arraylist-与-linkedlist-区别) @@ -46,13 +41,6 @@ - [1.5.1. 排序操作](#151-排序操作) - [1.5.2. 查找,替换操作](#152-查找替换操作) - [1.5.3. 同步控制](#153-同步控制) - - [1.6. 其他重要问题](#16-其他重要问题) - - [1.6.1. 什么是快速失败(fail-fast)?](#161-什么是快速失败fail-fast) - - [1.6.2. 什么是安全失败(fail-safe)呢?](#162-什么是安全失败fail-safe呢) - - [1.6.3. Arrays.asList()避坑指南](#163-arraysaslist避坑指南) - - [1.6.3.1. 简介](#1631-简介) - - [1.6.3.2. 《阿里巴巴 Java 开发手册》对其的描述](#1632-阿里巴巴-java-开发手册对其的描述) - - [1.6.3.3. 使用时的注意事项总结](#1633-使用时的注意事项总结) @@ -112,58 +100,7 @@ 因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。 数组的缺点是一旦声明之后,长度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。 -但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据 - -### 1.1.6. Iterator 迭代器 - -#### 1.1.6.1. 迭代器 Iterator 是什么? - -```java -public interface Iterator { - //集合中是否还有元素 - boolean hasNext(); - //获得集合中的下一个元素 - E next(); - ...... -} -``` - -`Iterator` 对象称为迭代器(设计模式的一种),迭代器可以对集合进行遍历,但每一个集合内部的数据结构可能是不尽相同的,所以每一个集合存和取都很可能是不一样的,虽然我们可以人为地在每一个类中定义 `hasNext()` 和 `next()` 方法,但这样做会让整个集合体系过于臃肿。于是就有了迭代器。 - -迭代器是将这样的方法抽取出接口,然后在每个类的内部,定义自己迭代方式,这样做就规定了整个集合体系的遍历方式都是 `hasNext()`和`next()`方法,使用者不用管怎么实现的,会用即可。迭代器的定义为:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。 - -#### 1.1.6.2. 迭代器 Iterator 有啥用? - -`Iterator` 主要是用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 `ConcurrentModificationException` 异常。 - -#### 1.1.6.3. 如何使用? - -我们通过使用迭代器来遍历 `HashMap`,演示一下 迭代器 Iterator 的使用。 - -```java - -Map map = new HashMap(); -map.put(1, "Java"); -map.put(2, "C++"); -map.put(3, "PHP"); -Iterator> iterator = map.entrySet().iterator(); -while (iterator.hasNext()) { - Map.Entry entry = iterator.next(); - System.out.println(entry.getKey() + entry.getValue()); -} -``` - -### 1.1.7. 有哪些集合是线程不安全的?怎么解决呢? - -我们常用的 `Arraylist` ,`LinkedList`,`Hashmap`,`HashSet`,`TreeSet`,`TreeMap`,`PriorityQueue` 都不是线程安全的。解决办法很简单,可以使用线程安全的集合来代替。 - -如果你要使用线程安全的集合的话, `java.util.concurrent` 包中提供了很多并发容器供你使用: - -1. `ConcurrentHashMap`: 可以看作是线程安全的 `HashMap` -2. `CopyOnWriteArrayList`:可以看作是线程安全的 `ArrayList`,在读多写少的场合性能非常好,远远好于 `Vector`. -3. `ConcurrentLinkedQueue`:高效的并发队列,使用链表实现。可以看做一个线程安全的 `LinkedList`,这是一个非阻塞队列。 -4. `BlockingQueue`: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。 -5. `ConcurrentSkipListMap` :跳表的实现。这是一个`Map`,使用跳表的数据结构进行快速查找。 +但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。 ## 1.2. Collection 子接口之 List @@ -675,170 +612,6 @@ synchronizedMap(Map m) //返回由指定映射支持的同步(线程安 synchronizedSet(Set s) //返回指定 set 支持的同步(线程安全的)set。 ``` -## 1.6. 其他重要问题 - -### 1.6.1. 什么是快速失败(fail-fast)? - -**快速失败(fail-fast)** 是 Java 集合的一种错误检测机制。**在使用迭代器对集合进行遍历的时候,我们在多线程下操作非安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出 `ConcurrentModificationException` 异常。 另外,在单线程下,如果在遍历过程中对集合对象的内容进行了修改的话也会触发 fail-fast 机制。** - -> 注:增强 for 循环也是借助迭代器进行遍历。 - -举个例子:多线程下,如果线程 1 正在对集合进行遍历,此时线程 2 对集合进行修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进行修改,都会导致线程 1 抛出 `ConcurrentModificationException` 异常。 - -**为什么呢?** - -每当迭代器使用 `hashNext()`/`next()`遍历下一个元素之前,都会检测 `modCount` 变量是否为 `expectedModCount` 值,是的话就返回遍历;否则抛出异常,终止遍历。 - -如果我们在集合被遍历期间对其进行修改的话,就会改变 `modCount` 的值,进而导致 `modCount != expectedModCount` ,进而抛出 `ConcurrentModificationException` 异常。 - -> 注:通过 `Iterator` 的方法修改集合的话会修改到 `expectedModCount` 的值,所以不会抛出异常。 - -```java -final void checkForComodification() { - if (modCount != expectedModCount) - throw new ConcurrentModificationException(); -} -``` - -好吧!相信大家已经搞懂了快速失败(fail-fast)机制以及它的原理。 - -我们再来趁热打铁,看一个阿里巴巴手册相关的规定: - -![](images/ad28e3ba-e419-4724-869c-73879e604da1.png) - -有了前面讲的基础,我们应该知道:使用 `Iterator` 提供的 `remove` 方法,可以修改到 `expectedModCount` 的值。所以,才不会再抛出`ConcurrentModificationException` 异常。 - -### 1.6.2. 什么是安全失败(fail-safe)呢? - -明白了快速失败(fail-fast)之后,安全失败(fail-safe)我们就很好理解了。 - -采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 `ConcurrentModificationException` 异常。 - -### 1.6.3. Arrays.asList()避坑指南 - -最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。 - -#### 1.6.3.1. 简介 - -`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个 List 集合。 - -```java -String[] myArray = { "Apple", "Banana", "Orange" }; -List myList = Arrays.asList(myArray); -//上面两个语句等价于下面一条语句 -List myList = Arrays.asList("Apple","Banana", "Orange"); -``` - -JDK 源码对于这个方法的说明: - -```java -/** - *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 - */ -public static List asList(T... a) { - return new ArrayList<>(a); -} -``` - -#### 1.6.3.2. 《阿里巴巴 Java 开发手册》对其的描述 - -`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴 Java 开发手册》对于这个方法有如下描述: - -![阿里巴巴Java开发手-Arrays.asList()方法]() - -#### 1.6.3.3. 使用时的注意事项总结 - -**传递的数组必须是对象数组,而不是基本类型。** - -`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。 - -```java -int[] myArray = { 1, 2, 3 }; -List myList = Arrays.asList(myArray); -System.out.println(myList.size());//1 -System.out.println(myList.get(0));//数组地址值 -System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException -int [] array=(int[]) myList.get(0); -System.out.println(array[0]);//1 -``` - -当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时 List 的唯一元素就是这个数组,这也就解释了上面的代码。 - -我们使用包装类型数组就可以解决这个问题。 - -```java -Integer[] myArray = { 1, 2, 3 }; -``` - -**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。** - -```java -List myList = Arrays.asList(1, 2, 3); -myList.add(4);//运行时报错:UnsupportedOperationException -myList.remove(1);//运行时报错:UnsupportedOperationException -myList.clear();//运行时报错:UnsupportedOperationException -``` - -`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。 - -```java -List myList = Arrays.asList(1, 2, 3); -System.out.println(myList.getClass());//class java.util.Arrays$ArrayList -``` - -下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。 - -```java - private static class ArrayList extends AbstractList - implements RandomAccess, java.io.Serializable - { - ... - - @Override - public E get(int index) { - ... - } - - @Override - public E set(int index, E element) { - ... - } - - @Override - public int indexOf(Object o) { - ... - } - - @Override - public boolean contains(Object o) { - ... - } - - @Override - public void forEach(Consumer action) { - ... - } - - @Override - public void replaceAll(UnaryOperator operator) { - ... - } - - @Override - public void sort(Comparator c) { - ... - } - } -``` - -我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。 - -```java -public E remove(int index) { - throw new UnsupportedOperationException(); -} -``` - **《Java面试突击》:** Java 程序员面试必备的《Java面试突击》V3.0 PDF 版本扫码关注下面的公众号,在后台回复 **"面试突击"** 即可免费领取!