|
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