|
10 | 10 | - [1.1.3.3. Map](#1133-map)
|
11 | 11 | - [1.1.4. 如何选用集合?](#114-如何选用集合)
|
12 | 12 | - [1.1.5. 为什么要使用集合?](#115-为什么要使用集合)
|
13 |
| - - [1.1.6. Iterator 迭代器](#116-iterator-迭代器) |
14 |
| - - [1.1.6.1. 迭代器 Iterator 是什么?](#1161-迭代器-iterator-是什么) |
15 |
| - - [1.1.6.2. 迭代器 Iterator 有啥用?](#1162-迭代器-iterator-有啥用) |
16 |
| - - [1.1.6.3. 如何使用?](#1163-如何使用) |
17 |
| - - [1.1.7. 有哪些集合是线程不安全的?怎么解决呢?](#117-有哪些集合是线程不安全的怎么解决呢) |
18 | 13 | - [1.2. Collection 子接口之 List](#12-collection-子接口之-list)
|
19 | 14 | - [1.2.1. Arraylist 和 Vector 的区别?](#121-arraylist-和-vector-的区别)
|
20 | 15 | - [1.2.2. Arraylist 与 LinkedList 区别?](#122-arraylist-与-linkedlist-区别)
|
|
46 | 41 | - [1.5.1. 排序操作](#151-排序操作)
|
47 | 42 | - [1.5.2. 查找,替换操作](#152-查找替换操作)
|
48 | 43 | - [1.5.3. 同步控制](#153-同步控制)
|
49 |
| - - [1.6. 其他重要问题](#16-其他重要问题) |
50 |
| - - [1.6.1. 什么是快速失败(fail-fast)?](#161-什么是快速失败fail-fast) |
51 |
| - - [1.6.2. 什么是安全失败(fail-safe)呢?](#162-什么是安全失败fail-safe呢) |
52 |
| - - [1.6.3. Arrays.asList()避坑指南](#163-arraysaslist避坑指南) |
53 |
| - - [1.6.3.1. 简介](#1631-简介) |
54 |
| - - [1.6.3.2. 《阿里巴巴 Java 开发手册》对其的描述](#1632-阿里巴巴-java-开发手册对其的描述) |
55 |
| - - [1.6.3.3. 使用时的注意事项总结](#1633-使用时的注意事项总结) |
56 | 44 |
|
57 | 45 | <!-- /TOC -->
|
58 | 46 |
|
|
112 | 100 | 因为我们在实际开发中,存储的数据的类型是多种多样的,于是,就出现了“集合”,集合同样也是用来存储多个数据的。
|
113 | 101 |
|
114 | 102 | 数组的缺点是一旦声明之后,长度就不可变了;同时,声明数组时的数据类型也决定了该数组存储的数据的类型;而且,数组存储的数据是有序的、可重复的,特点单一。
|
115 |
| -但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据 |
116 |
| - |
117 |
| -### 1.1.6. Iterator 迭代器 |
118 |
| - |
119 |
| -#### 1.1.6.1. 迭代器 Iterator 是什么? |
120 |
| - |
121 |
| -```java |
122 |
| -public interface Iterator<E> { |
123 |
| - //集合中是否还有元素 |
124 |
| - boolean hasNext(); |
125 |
| - //获得集合中的下一个元素 |
126 |
| - E next(); |
127 |
| - ...... |
128 |
| -} |
129 |
| -``` |
130 |
| - |
131 |
| -`Iterator` 对象称为迭代器(设计模式的一种),迭代器可以对集合进行遍历,但每一个集合内部的数据结构可能是不尽相同的,所以每一个集合存和取都很可能是不一样的,虽然我们可以人为地在每一个类中定义 `hasNext()` 和 `next()` 方法,但这样做会让整个集合体系过于臃肿。于是就有了迭代器。 |
132 |
| - |
133 |
| -迭代器是将这样的方法抽取出接口,然后在每个类的内部,定义自己迭代方式,这样做就规定了整个集合体系的遍历方式都是 `hasNext()`和`next()`方法,使用者不用管怎么实现的,会用即可。迭代器的定义为:提供一种方法访问一个容器对象中各个元素,而又不需要暴露该对象的内部细节。 |
134 |
| - |
135 |
| -#### 1.1.6.2. 迭代器 Iterator 有啥用? |
136 |
| - |
137 |
| -`Iterator` 主要是用来遍历集合用的,它的特点是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 `ConcurrentModificationException` 异常。 |
138 |
| - |
139 |
| -#### 1.1.6.3. 如何使用? |
140 |
| - |
141 |
| -我们通过使用迭代器来遍历 `HashMap`,演示一下 迭代器 Iterator 的使用。 |
142 |
| - |
143 |
| -```java |
144 |
| - |
145 |
| -Map<Integer, String> map = new HashMap(); |
146 |
| -map.put(1, "Java"); |
147 |
| -map.put(2, "C++"); |
148 |
| -map.put(3, "PHP"); |
149 |
| -Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator(); |
150 |
| -while (iterator.hasNext()) { |
151 |
| - Map.Entry<Integer, String> entry = iterator.next(); |
152 |
| - System.out.println(entry.getKey() + entry.getValue()); |
153 |
| -} |
154 |
| -``` |
155 |
| - |
156 |
| -### 1.1.7. 有哪些集合是线程不安全的?怎么解决呢? |
157 |
| - |
158 |
| -我们常用的 `Arraylist` ,`LinkedList`,`Hashmap`,`HashSet`,`TreeSet`,`TreeMap`,`PriorityQueue` 都不是线程安全的。解决办法很简单,可以使用线程安全的集合来代替。 |
159 |
| - |
160 |
| -如果你要使用线程安全的集合的话, `java.util.concurrent` 包中提供了很多并发容器供你使用: |
161 |
| - |
162 |
| -1. `ConcurrentHashMap`: 可以看作是线程安全的 `HashMap` |
163 |
| -2. `CopyOnWriteArrayList`:可以看作是线程安全的 `ArrayList`,在读多写少的场合性能非常好,远远好于 `Vector`. |
164 |
| -3. `ConcurrentLinkedQueue`:高效的并发队列,使用链表实现。可以看做一个线程安全的 `LinkedList`,这是一个非阻塞队列。 |
165 |
| -4. `BlockingQueue`: 这是一个接口,JDK 内部通过链表、数组等方式实现了这个接口。表示阻塞队列,非常适合用于作为数据共享的通道。 |
166 |
| -5. `ConcurrentSkipListMap` :跳表的实现。这是一个`Map`,使用跳表的数据结构进行快速查找。 |
| 103 | +但是集合提高了数据存储的灵活性,Java 集合不仅可以用来存储不同类型不同数量的对象,还可以保存具有映射关系的数据。 |
167 | 104 |
|
168 | 105 | ## 1.2. Collection 子接口之 List
|
169 | 106 |
|
@@ -675,170 +612,6 @@ synchronizedMap(Map<K,V> m) //返回由指定映射支持的同步(线程安
|
675 | 612 | synchronizedSet(Set<T> s) //返回指定 set 支持的同步(线程安全的)set。
|
676 | 613 | ```
|
677 | 614 |
|
678 |
| -## 1.6. 其他重要问题 |
679 |
| - |
680 |
| -### 1.6.1. 什么是快速失败(fail-fast)? |
681 |
| - |
682 |
| -**快速失败(fail-fast)** 是 Java 集合的一种错误检测机制。**在使用迭代器对集合进行遍历的时候,我们在多线程下操作非安全失败(fail-safe)的集合类可能就会触发 fail-fast 机制,导致抛出 `ConcurrentModificationException` 异常。 另外,在单线程下,如果在遍历过程中对集合对象的内容进行了修改的话也会触发 fail-fast 机制。** |
683 |
| - |
684 |
| -> 注:增强 for 循环也是借助迭代器进行遍历。 |
685 |
| -
|
686 |
| -举个例子:多线程下,如果线程 1 正在对集合进行遍历,此时线程 2 对集合进行修改(增加、删除、修改),或者线程 1 在遍历过程中对集合进行修改,都会导致线程 1 抛出 `ConcurrentModificationException` 异常。 |
687 |
| - |
688 |
| -**为什么呢?** |
689 |
| - |
690 |
| -每当迭代器使用 `hashNext()`/`next()`遍历下一个元素之前,都会检测 `modCount` 变量是否为 `expectedModCount` 值,是的话就返回遍历;否则抛出异常,终止遍历。 |
691 |
| - |
692 |
| -如果我们在集合被遍历期间对其进行修改的话,就会改变 `modCount` 的值,进而导致 `modCount != expectedModCount` ,进而抛出 `ConcurrentModificationException` 异常。 |
693 |
| - |
694 |
| -> 注:通过 `Iterator` 的方法修改集合的话会修改到 `expectedModCount` 的值,所以不会抛出异常。 |
695 |
| -
|
696 |
| -```java |
697 |
| -final void checkForComodification() { |
698 |
| - if (modCount != expectedModCount) |
699 |
| - throw new ConcurrentModificationException(); |
700 |
| -} |
701 |
| -``` |
702 |
| - |
703 |
| -好吧!相信大家已经搞懂了快速失败(fail-fast)机制以及它的原理。 |
704 |
| - |
705 |
| -我们再来趁热打铁,看一个阿里巴巴手册相关的规定: |
706 |
| - |
707 |
| - |
708 |
| - |
709 |
| -有了前面讲的基础,我们应该知道:使用 `Iterator` 提供的 `remove` 方法,可以修改到 `expectedModCount` 的值。所以,才不会再抛出`ConcurrentModificationException` 异常。 |
710 |
| - |
711 |
| -### 1.6.2. 什么是安全失败(fail-safe)呢? |
712 |
| - |
713 |
| -明白了快速失败(fail-fast)之后,安全失败(fail-safe)我们就很好理解了。 |
714 |
| - |
715 |
| -采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。所以,在遍历过程中对原集合所作的修改并不能被迭代器检测到,故不会抛 `ConcurrentModificationException` 异常。 |
716 |
| - |
717 |
| -### 1.6.3. Arrays.asList()避坑指南 |
718 |
| - |
719 |
| -最近使用`Arrays.asList()`遇到了一些坑,然后在网上看到这篇文章:[Java Array to List Examples](http://javadevnotes.com/java-array-to-list-examples) 感觉挺不错的,但是还不是特别全面。所以,自己对于这块小知识点进行了简单的总结。 |
720 |
| - |
721 |
| -#### 1.6.3.1. 简介 |
722 |
| - |
723 |
| -`Arrays.asList()`在平时开发中还是比较常见的,我们可以使用它将一个数组转换为一个 List 集合。 |
724 |
| - |
725 |
| -```java |
726 |
| -String[] myArray = { "Apple", "Banana", "Orange" }; |
727 |
| -List<String> myList = Arrays.asList(myArray); |
728 |
| -//上面两个语句等价于下面一条语句 |
729 |
| -List<String> myList = Arrays.asList("Apple","Banana", "Orange"); |
730 |
| -``` |
731 |
| - |
732 |
| -JDK 源码对于这个方法的说明: |
733 |
| - |
734 |
| -```java |
735 |
| -/** |
736 |
| - *返回由指定数组支持的固定大小的列表。此方法作为基于数组和基于集合的API之间的桥梁,与 Collection.toArray()结合使用。返回的List是可序列化并实现RandomAccess接口。 |
737 |
| - */ |
738 |
| -public static <T> List<T> asList(T... a) { |
739 |
| - return new ArrayList<>(a); |
740 |
| -} |
741 |
| -``` |
742 |
| - |
743 |
| -#### 1.6.3.2. 《阿里巴巴 Java 开发手册》对其的描述 |
744 |
| - |
745 |
| -`Arrays.asList()`将数组转换为集合后,底层其实还是数组,《阿里巴巴 Java 开发手册》对于这个方法有如下描述: |
746 |
| - |
747 |
| -方法.png>) |
748 |
| - |
749 |
| -#### 1.6.3.3. 使用时的注意事项总结 |
750 |
| - |
751 |
| -**传递的数组必须是对象数组,而不是基本类型。** |
752 |
| - |
753 |
| -`Arrays.asList()`是泛型方法,传入的对象必须是对象数组。 |
754 |
| - |
755 |
| -```java |
756 |
| -int[] myArray = { 1, 2, 3 }; |
757 |
| -List myList = Arrays.asList(myArray); |
758 |
| -System.out.println(myList.size());//1 |
759 |
| -System.out.println(myList.get(0));//数组地址值 |
760 |
| -System.out.println(myList.get(1));//报错:ArrayIndexOutOfBoundsException |
761 |
| -int [] array=(int[]) myList.get(0); |
762 |
| -System.out.println(array[0]);//1 |
763 |
| -``` |
764 |
| - |
765 |
| -当传入一个原生数据类型数组时,`Arrays.asList()` 的真正得到的参数就不是数组中的元素,而是数组对象本身!此时 List 的唯一元素就是这个数组,这也就解释了上面的代码。 |
766 |
| - |
767 |
| -我们使用包装类型数组就可以解决这个问题。 |
768 |
| - |
769 |
| -```java |
770 |
| -Integer[] myArray = { 1, 2, 3 }; |
771 |
| -``` |
772 |
| - |
773 |
| -**使用集合的修改方法:`add()`、`remove()`、`clear()`会抛出异常。** |
774 |
| - |
775 |
| -```java |
776 |
| -List myList = Arrays.asList(1, 2, 3); |
777 |
| -myList.add(4);//运行时报错:UnsupportedOperationException |
778 |
| -myList.remove(1);//运行时报错:UnsupportedOperationException |
779 |
| -myList.clear();//运行时报错:UnsupportedOperationException |
780 |
| -``` |
781 |
| - |
782 |
| -`Arrays.asList()` 方法返回的并不是 `java.util.ArrayList` ,而是 `java.util.Arrays` 的一个内部类,这个内部类并没有实现集合的修改方法或者说并没有重写这些方法。 |
783 |
| - |
784 |
| -```java |
785 |
| -List myList = Arrays.asList(1, 2, 3); |
786 |
| -System.out.println(myList.getClass());//class java.util.Arrays$ArrayList |
787 |
| -``` |
788 |
| - |
789 |
| -下图是`java.util.Arrays$ArrayList`的简易源码,我们可以看到这个类重写的方法有哪些。 |
790 |
| - |
791 |
| -```java |
792 |
| - private static class ArrayList<E> extends AbstractList<E> |
793 |
| - implements RandomAccess, java.io.Serializable |
794 |
| - { |
795 |
| - ... |
796 |
| - |
797 |
| - @Override |
798 |
| - public E get(int index) { |
799 |
| - ... |
800 |
| - } |
801 |
| - |
802 |
| - @Override |
803 |
| - public E set(int index, E element) { |
804 |
| - ... |
805 |
| - } |
806 |
| - |
807 |
| - @Override |
808 |
| - public int indexOf(Object o) { |
809 |
| - ... |
810 |
| - } |
811 |
| - |
812 |
| - @Override |
813 |
| - public boolean contains(Object o) { |
814 |
| - ... |
815 |
| - } |
816 |
| - |
817 |
| - @Override |
818 |
| - public void forEach(Consumer<? super E> action) { |
819 |
| - ... |
820 |
| - } |
821 |
| - |
822 |
| - @Override |
823 |
| - public void replaceAll(UnaryOperator<E> operator) { |
824 |
| - ... |
825 |
| - } |
826 |
| - |
827 |
| - @Override |
828 |
| - public void sort(Comparator<? super E> c) { |
829 |
| - ... |
830 |
| - } |
831 |
| - } |
832 |
| -``` |
833 |
| - |
834 |
| -我们再看一下`java.util.AbstractList`的`remove()`方法,这样我们就明白为啥会抛出`UnsupportedOperationException`。 |
835 |
| - |
836 |
| -```java |
837 |
| -public E remove(int index) { |
838 |
| - throw new UnsupportedOperationException(); |
839 |
| -} |
840 |
| -``` |
841 |
| - |
842 | 615 |
|
843 | 616 |
|
844 | 617 | **《Java面试突击》:** Java 程序员面试必备的《Java面试突击》V3.0 PDF 版本扫码关注下面的公众号,在后台回复 **"面试突击"** 即可免费领取!
|
|
0 commit comments