提交 5fdb3f86 编写于 作者: G guide

[fix]图片地址出错

上级 f2c3a98c
......@@ -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-使用时的注意事项总结)
<!-- /TOC -->
......@@ -112,58 +100,7 @@
因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。
数组的缺点是一旦声明之后,长度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。
但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据
### 1.1.6. Iterator 迭代器
#### 1.1.6.1. 迭代器 Iterator 是什么?
```java
public interface Iterator<E> {
//集合中是否还有元素
boolean hasNext();
//获得集合中的下一个元素
E next();
......
}
```
`Iterator` 对象称为迭代器(设计模式的一种),迭代器可以对集合进行遍历,但每一个集合内部的数据结构可能是不尽相同的,所以每一个集合存和取都很可能是不一样的,虽然我们可以人为地在每一个类中定义 `hasNext()``next()` 方法,但这样做会让整个集合体系过于臃肿。于是就有了迭代器。
迭代器是将这样的方法抽取出接口,然后在每个类的内部,定义自己迭代方式,这样做就规定了整个集合体系的遍历方式都是 `hasNext()``next()`方法,使用者不用管怎么实现的,会用即可。迭代器的定义为:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。
#### 1.1.6.2. 迭代器 Iterator 有啥用?
`Iterator` 主要是用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 `ConcurrentModificationException` 异常。
#### 1.1.6.3. 如何使用?
我们通过使用迭代器来遍历 `HashMap`,演示一下 迭代器 Iterator 的使用。
```java
Map<Integer, String> map = new HashMap();
map.put(1, "Java");
map.put(2, "C++");
map.put(3, "PHP");
Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> 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<K,V> m) //返回由指定映射支持的同步(线程安
synchronizedSet(Set<T> 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<String> myList = Arrays.asList(myArray);
//上面两个语句等价于下面一条语句
List<String> myList = Arrays.asList("Apple","Banana", "Orange");
```
JDK 源码对于这个方法的说明:
```java
/**
*返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。
*/
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
```
#### 1.6.3.2. 《阿里巴巴 Java 开发手册》对其的描述
`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴 Java 开发手册》对于这个方法有如下描述:
![阿里巴巴Java开发手-Arrays.asList()方法](<https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/阿里巴巴Java开发手-Arrays.asList()方法.png>)
#### 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<E> extends AbstractList<E>
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<? super E> action) {
...
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
...
}
@Override
public void sort(Comparator<? super E> c) {
...
}
}
```
我们再看一下`java.util.AbstractList``remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`
```java
public E remove(int index) {
throw new UnsupportedOperationException();
}
```
**《Java面试突击》:** Java 程序员面试必备的《Java面试突击》V3.0 PDF 版本扫码关注下面的公众号,在后台回复 **"面试突击"** 即可免费领取!
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册