Java|【Java笔记】一网打尽Java中的集合知识

一网打尽Java中的集合知识 前段时间我们总结过数组的相关知识,这次我们就来总结一下Java中的集合类。这里是总结集合的第一篇,所以这里不做深入的代码分析,只在概念上搭起集合类的大框架,了解各自的特点和相互之间的关系。

  • 数组与集合
    • 为什么引入容器?
    • 数组与集合之间的关系
    • 输出数组和集合
  • 纵观全局的集合关系
    • 全局集合关系图
    • Collection接口、Map接口和Iterator接口
  • 主要的集合接口介绍
    • Collection接口
      • List
      • Set
      • Queue
    • Map接口
  • 主要的集合实现类介绍
    • 有序与无序的理解
    • ArrayList的特性
    • LinkedList的特性
    • HashSet的特性
    • TreeSet的特性
    • LinkedHashSet的特性
    • HashMap的特性
    • TreeMap的特性
    • LinkedHashMap的特性
  • 集合实现类之间的区别比较
    • ArrayList与LinkedList的异同
    • ArrayList和Ventor的异同
    • HashSet和TreeSet的异同
    • HashMap和TreeMap的异同
    • HashMap和HashTable的异同
  • 集合知识的十万个为什么?
    • 为什么ArrayList继承了AbstractList抽象类却还要实现List接口?
    • Map接口没有继承Iterable接口,为什么可以使用迭代器遍历?
    • Iterator与ListIterator有什么区别?
    • 在HashTable上下文中同步是什么意思?
    • 快速失败特性(fail—fast)和安全失败特性(fail—safe)?
    • 怎样使HashMap同步?
    • 什么时候使用Hashtable,什么时候使用HashMap
    • 为什么官方不推荐使用Vector类并视其为过时技术?
  • 集合的扩展知识
数组与集合
在学习之前,我们来了解一下。Java为什么要引用容器这个概念。
为什么要引进容器? 通常我们知道数组是可以用来存放多个对象的好东西。但是我们写程序的时候并不知道到底需要多少个对象或是否需要更复杂的方式来存储对象。而数组是编译期间就已经确定了大小的,所以数组的这个性质就显得非常局限了。所以此时Java引进容器的概念,相对数组的一个好处就是可以动态的扩容,没有固定的大小,可以根据需求进行动态的扩增。
数组与集合之间的关系
  • 数组(可以存储基本类型和对象,必须是同一类型)长度固定,不适合在元素数量未知的情况下使用。
  • 集合(只能存储对象,对象类型可以不一样)的长度可变,可在多数情况下使用。
输出数组和集合 输出数组,我们需要通过Arrays.toString()方法的帮助,才能把具体内存输出出来,因为数组对象没有重写toString()方法
而容器不需要任何东西的帮助,他可以直接输出,因为集合类的实现类通常都重写了toString()方法


纵观全局的集合关系
全局集合关系图 图中,实线边框的是实现类(如TreeSet),虚线边框的是抽象类(如AbstractSet),而点线边框的是接口(如Set)
(首先声明该图不是完整版,来源于网上,是基于Java5的,原资料出自《Java编程思想》)
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
Collection接口、Map接口和Iterator接口 我们可以看到图中最上层的有三个接口(Iterator接口,Collection接口,Map接口)
  • Collection接口:
    Collection是三种集合类的根接口(List,Set,Queue),List必须按插入顺序保存元素,而Set不能插入重复元素。Queue则只允许从容器的一端插入,另一端移除。
  • Map接口:
    Map是另一种集合类的根接口(不过网上一般不称其为集合的根接口),它和Collection接口没有关系,是相互独立的。Map类集合是以key-value键值对的形式来存储对象的。Map不能包含重复的key,但是可以包含相同的value。
  • Iterator接口:
    所有的集合类都直接或间接的实现了Iterable接口,这是一个用于遍历集合元素的接口。Iterable接口有一个iterator()方法,调用iterator方法,可以返回一个iterator实例,用于遍历结合元素。这代表着实现了Iterator接口的类是可以通过迭代器迭代遍历的
从上我们可以了解到Collection接口和Map接口都是java.util包下的接口,都是容器类的接口,所有的容器类接口都直接或间接的实现了Iterable接口,从而都可以使用迭代器去遍历容器中的元素。要注意区分Iterable接口和Iterator接口
注意:
我注意到网上有一个观点,是说只有从Collection集合中继承下来的才是集合类,Map不算集合类。但是这只是其中的一个观点。博主更偏向于把Collection和Map都称为集合类,此集合非仅局限Collection。所以在下面的描述中,将不再阐述。
有疑问的可以看下面这个链接:
Map是不是集合? - 作者:贩剑的紫眸

主要的集合接口介绍
Collection接口 Java|【Java笔记】一网打尽Java中的集合知识
文章图片
从图中我们可以看到,Collection接口主要被三个子接口所继承(List,Set,Queue),所以我们这里分别讨论一下List集合、Set集合和Queue集合
List集合: List承诺可以将元素维护在特定的序列中。List接口在Collection的基础上添加了大量的方法,使得可以在List中间插入和移除元素。
特性:
  • 一个大小可变,可实现动态扩容的线性表集合接口,也可以简单的理解是数组可动态扩展的替代品
实现类:
List接口中目前主要有两个重要的实现类:
  • ArrayList类
  • LinkedList类
Set集合 Set集合与List集合在特性上有些许不同,Set集合不会去保存重复的元素,因为Set集合每次插入都检查所插入元素是否已经存在。所以查找算是Set集合中最重要的操作之一。
特性:
  • Set集合不允许含有重复元素,一定会判断元素是否重复再决定是否插入该元素。
实现类:
Set接口中目前主要有三个重要的实现类:
  • HashSet类
  • TreeSet类
  • LinkedHashSet类
Queue集合 Queue用于模拟队列这种数据结构。队列通常是指“先进先出(FIFO)”的容器。队列的头部保存在队列中存放时间最长的元素,尾部保存存放时间最短的元素。新元素插入到队列的尾部,取出元素会返回队列头部的元素。通常,队列不允许随机访问队列中的元素。
特性:
  • 先进先出(FIFO)
实现类:
Queue接口常用实现类:
  • PriorityQueue
  • LinkedList(可向上转型为Deque)

Map接口 Map集合其实就是一个映射表(或称关联数组),通过维护Key-Value键值对的关系来实现时间复杂度为O(1)的集合查询。
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
特性:
  • Map集合是一个种以Key-Value键值对的形式存放数据的。
实现类:
Map接口有三种重要的实现类:
  • HashMap
  • TreeMap
  • LinkedHashMap

常见集合实现类介绍
有序与无序的理解 这里有一个前提概念,我们为了避免混淆,我这里有必要说明一下有序和无序的概念,在不同角度上,有序和无序的理解是不同的。
插入顺序的有序与无序:
有序:集合会根据插入顺序记录数据元素在逻辑上的先后顺序
无序:集合不会根据插入顺序记录数据,而是随机分布的
【Java|【Java笔记】一网打尽Java中的集合知识】大小排序上的有序与无序:
有序:集合中的数据元素根据特定的排序,升序或降序或其他方式排序
无序:集合中的元素随机分布,或按插入顺序记录。但没有根据特定的方式进行排列,既没有经过排序。
ArrayList的特性 ArrayList继承于AbstractList抽象类,分别实现了List, RandomAccess, Cloneable, java.io.Serializable四个接口.
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
  • ArrayList底层数据结构是数组,是以数组为基础,具有动态扩容特性的集合。
  • ArrayList是有序的,这里的有序是指根据插入顺序记录数据之间的逻辑顺序
  • ArrayList擅长于随机访问元素,访问和修改数据(get\set)速度快效率高,因为是直接根据索引来访问,不需要遍历。但在添加和删除数据(add\remove)方法速度则很慢,因为每次插入或删除都需要移动插入(删除)点及其后面的数据。
LinkedList的特性 LinkedList继承于AbstractSequentialList抽象类,分别实现了List, Deque, Cloneable, java.io.Serializable四个接口
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
  • LinkedList的底层数据结构是一个双向链表。
  • LinkedList是有序的,这里的有序是指根据插入顺序记录数据之间的逻辑顺序
  • LinkedList在随机访问数据方面具有劣势,因为每次访问(get\set)都需要遍历全部数据,所以效率低速度慢。但在添加和删除数据方面就具有很大优势,因为插入(删除)数据时只需要记录本项的前后项即可。
  • LinkedList还实现了Queue队列的子接口Deque接口,可以向上转型类Deque类型(双端队列)。
HashSet的特性 HashSet继承于AbstractSet抽象类,分别实现了Set, Cloneable,java.io.Serializable接口。
HashSet底层是基于哈希表的实现来存放元素,所以它的插入删除速度很快。原理是通过调用插入元素的hashCode()方法来得到该元素的hashCode值,然后根据 hashCode值来决定该元素在HashSet中存储位置。
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
  • HashSet数据结构底层是哈希表,以散列结构存储数据
  • 插入删除速度很快
  • HashSet是无序的,这里的无序是既不会根据插入顺序记录,也不会排序
  • HashSet没有实现线程同步的,既非线程安全
  • HashSet集合中的元素可以存放null,但仅能有一个null值,这一点跟HashMap的key特性一样
  • HashSet所存放的元素必须实现equals方法和hashCode方法
  • HashSet集合判断两个元素相等的标准是两个元素的HashCode一致,且equals也一致
注意:
  • Hash形式的数据结构要存储对象,那么该对象的类必须重写equals(),hashCode()方法,不能使用Object类默认实现的equals(),hashCode()方法。
  • 底层数据结构是哈希表,本质就是根据哈希值进行数据存储,通过判断元素的hashCode方法和equals方法来保证元素的唯一性,当hashCode值不相同,就直接存储了,不用再判断equals了。当hashCode值相同时,则有概况是哈希冲突的情况,所以需要再通过equals方法判断一次实际的值是否相同,如果为true则视为用一个元素,不用存储,如果为false,则代表是哈希冲突。JDK底层通过拉链法(位桶法)和红黑树来解决哈希冲突。
TreeSet的特性 TreeSet 继承于AbstractSet抽象类,分别实现NavigableSet, Cloneable, java.io.Serializable接口。
TreeSet不同于HashSet,TreeSet不是无序的,它可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然(升序)排序和定制排序,其中自然排序为默认的排序方式。向TreeSet中加入的应该是同一个类的对象。
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
  • TreeSet的底层数据结构是二叉树,以树形结构存储数据。
  • TreeSet是有序的。这里的有序是指数据是排了序的,可有两种形式,自然排序或定制排序(按照compareTo的实现方式),默认是自然排序(升序)
  • TreeSet不允许存放null值,这与HashSet不同
  • TreeSet没有实现线程同步,既非线程安全
  • TreeSet存放的元素必须实现equals方法和Comparable接口(既实现CompareTo方法)
  • TreeSet判断两个对象相等的方式是两个对象通过equals方法比较返回true,或者通过CompareTo方法比较返回0
注意:
  • 底层的数据结构是二叉树,可以对Set集合中的元素进行排序。这种结构,可以提高排序性能, 根据比较方法的返回值确定的,只要返回的是0.就代表元素重复
  • TreeSet的本质就是通过实现Comparable接口,从而实现CompareTo方法,来达到排序的目的
  • 这里实现equals的目的是为了在CompareTo中判断元素是否相同
LinkedHashSet的特性 LinkedHashSet继承于HashSet类,分别实现了Set, Cloneable, java.io.Serializable接口。
LinkedHashSet是HashSet的子类。为什么还有LinkedHashSet呢?这是为了维护不同于TreeSet的另一种有序状态,这种有序状态指的是插入的顺序。所以LinkedHashSet具有HashSet的查询速度,同时其内部还使用了链表来维护元素的顺序(既根据数据插入的顺序来维护数据)。
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
  • LinkedHashSet的数据结构底层依然是哈希表,但是又使用了链表来记录数据的顺序。
  • LinkedHashSet相对HashSet而言是有序的,能根据插入顺序记录数据之间的先后顺序。
  • HashSet具有的特性,LinkedHashSet都具有,因为LinkedHashSet是子类,所以还具有HashSet所不具有的特性。
HashMap的特性 HashMap继承于AbstractMap抽象类,分别实现了Map, Cloneable,Serializable接口
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
  • HashMap的底层数据结构是哈希表
  • HashMap是无序的,这里的无序是指既不会根据插入顺序记录,也不会根据大小排序
  • HashMap的键的对象的类必须重写equal()和HashCode()方法
  • HashMap没有实现线程同步,所以是非线程安全的
TreeMap的特性 TreeMap继承于AbstractMap,实现了NavigableMapCloneableSerializable接口
TreeMap相对HashMap来说是有序的,这个有序是排序,情况跟TreeSet一样,只不过TreeMap是Map的版本
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
  • TreeMap的底层数据结构是二叉树,以树形结构存储数据。
  • TreeMap是有序的。这里的有序是指数据是会排序的,可以是自然排序或自定义排序,默认是自然排序(升序)
  • TreeMap 的键不允许是null值,这与TreeSet相同。不过值可以是null
  • TreeMap没有实现线程同步,既非线程安全
  • TreeMap存放的元素必须实现equals方法和Comparable接口
LinkedHashMap的特性 LinkedHashMap继承了HashMap,分别实现了Map, Cloneable,Serializable接口
Java|【Java笔记】一网打尽Java中的集合知识
文章图片
这里也不多解释了,其实就是LinkedHashSet的HashMap版本
  • LinkedHashMap的数据结构底层依然是哈希表,但又使用了链表来记录数据的顺序。
  • LinkedHashMap相对HashMap而言是有序的,能根据插入顺序记录数据之间的先后顺序。
PriorityQueue的特性 PriorityQueue继承于AbstractQueue,实现了java.io.Serializable接口
PriorityQueue是一个比较标准的队列实现类。之所以是比较标准的原因是PriorityQueue保存队列元素的顺序并不是按照加入队列的顺序,而是按照队列元素的大小进行重新排序。因此当调用peek()或者是poll()的方法取出队列中的元素通常都是最小的元素。
Java|【Java笔记】一网打尽Java中的集合知识
文章图片

Emmm,PriorityQueue不了解,就不来害人了,有时间再补充

主要的集合实现类特性
这里是Java集合类中常用的六种实现类和他们的过往版本
接口 集合实现类 过往集合实现类
List ArrayList Vector
LinkedList Stack
Set HashSet
TreeSet
Map HashMap Hashtable
TreeMap Properties
一、Arraylist和Linkedlist的异同 相同点:
  • 都实现了List、Cloneable、java.io.Serializable接口,可以实现clone()和对象序列化
不同点:
  • ArrayList的底层数据结构是数组,LinkedList底层数据结构是链表。
  • 对于随机访问get和set,ArrayList优于LinkedList。因为ArrayList可以直接根据索引访问,而LinkedList则需要遍历整个数据进行查找。
  • 对于新增和删除操作add和remove,LinkedList更具优势。不过这一点要看实际情况:
    1. 若只对单条数据插入或删除,ArrayList的速度反而优于LinkedList。但若是批量随机的插入删除数据,LinkedList的速度大大优于 ArrayList。因为ArrayList每插入一条数据,要移动插入点及之后的所有数据。而LinkedList使用双向链表实现存储,插入数据时只需要记录本项的前后项即可。
    2. ArrayList的尾插入效率也很高,因为不需要移动
    3. 在大量数据的前提下,在集合中部插入数据,ArrayList的性能比LinkedList高,因为ArrayList虽然是说要移动元素,但在代码层次中,实际是使用native的System.arraycopy方法实现的,性能消耗挺低的。虽然LinkedList只需要记住前后项,但是当数据量大时,你需要遍历到中部,做插入操作,这个遍历也是很耗时的
二、Vector和ArrayList的异同 相同点:
  • Vector集合和ArrayList集合的底层数据结构都是数组。
  • Vector和ArrayList都实现了List, RandomAccess, Cloneable, java.io.Serializable接口,继承于AbstractList抽象类。可以实现clone(享元模式)和对象序列化
  • 查找一个指定位置的数据或是频繁的访问数据,Vector和Arraylist使用的时间是相同的,都是通过索引访问元素。所以都具有随机访问快速的优点,但都具有插入(删除)数据慢的缺点。
不同点:
  • Vector是线程同步的,所以它是线程安全的,而Arraylist是线程异步的,所以是不安全的。如果不考虑到线程的安全因素,一般用ArrayList效率比较高。
  • 如果集合中的元素的数目大于目前集合数组的长度时,Vector增长率为目前数组长度的100%,而Arraylist增长率为目前数组长度的50%。如果在集合中使用数据量比较大的数据,用vector有一定的优势。
  • Vector由于使用了synchronized方法(线程安全)所以在系统开销上要比使用ArrayList更高,所以相对性能上比ArrayList要差
三、HashSet和TreeSet 相同点:
  • HashSet和TreeSet都是间接的实现了Set接口,都是Set接口的实现类。所以都具有Set集合的特点,就是去重,不允许有重复元素在集合中。
  • HashSet和TreeSet都没有实现线程同步,都是非线程安全的
不同点:
  • HashSet的底层数据结构是哈希表,TreeSet底层数据结构是二叉树。
  • HashSet是无序的,而Treeset 是有序的(但这种有序与根据插入顺序来记录的又不同),数据是排了序的
  • HashSet允许有null值,但只能有一个。而TreeSet不允许放入null值。
  • HashSet要求放入的对象必须实现HashCode()和equal()方法,判断重复对象是以HashCode码作为标识的。TreeSet则是必须实现equal()方法和Compareable接口。
四、ArrayList、LinkedList、HashSet、TreeSet在效率上的比较 五、HashMap与TreeMap 相同点:
  • 最终都间接的实现Map接口,是Map的实现类 ,都是Key-Value键值对的形式存储数据
  • HashMap和TreeMap都没有实现线程同步,所以都是非线程安全的
  • HashMap和TreeMap的内部元素,不管顺序是否相同,通过equals比较,只要元素都相同,即返回True
不同点:
  • HashMap的底层数据结构是哈希表,是散列存储结构。TreeMap的底层数据结构是二叉树,是树形存储结构
  • HashMap是无序的,而TreeMap是有序的(这种有序与根据插入顺序来记录的有序不同),数据是排了序的
  • HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
  • 在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。
六、HashMap与HashTable的异同 相同点:
  • 两者都是用key-value方式存储数据。Hashtable是原始集合类(也称作遗留类)。HashMap是设计用来替换Hashtable的
  • HashMap和Hashtable都实现了Map接口,但继承的类不同,HashMap继承于AbstractMap抽象类,而Hashtable继承于Dictionary抽象类(该抽象类已经是一个顶级超类)。
不同点:
  • HashMap允许存在一个为null的key,多个为null的value。Hashtable的key和value都不允许为null。
  • HashMap的子类LinkedHashMap可以实现按照插入顺序存储元素,且LinkedHashMap很容易转换为HashMap,而Hashtable就没有这类实现。
  • HashMap扩容时是当前容量翻倍即:capacity x 2。Hashtable扩容时是容量翻倍+1即:capacity x 2+1
  • 迭代HashMap采用快速失败机制,而Hashtable不是。
  • HashMap是非线程安全的,HashTable是线程安全的。在不要求安全的情况下,HashMap的性能效率更高。即使有安全要求,我们也可以通过Collections.synchronizedMap(hashMap)的方式实现HashMap同步。当然要求安全且保证一定性能的情况下我们可以直接使用Java并发包提供的ConcurrentHashMap

集合知识的十万个为什么?
这里我们来总结一下关于集合知识的一些为什么?
为什么ArrayList继承了AbstractList抽象类却还要实现List接口? 为什么?
我们都知道,ArrayList继承AbstractList是为了精简代码,共享重复代码。是模板设计模式的体现。那么既然ArrayList已经继承了AbstractList,为什么还要实现List呢?这样不是多此一举吗?当然我也不理解,所以我只能在网上寻找答案。(类似的情况在Java集合中很常见,不仅仅是List)
结论一:
这样做的方式可能仅仅是为了让大家清晰的看出ArrayList的层次关系结构,因为ArrayList的方法实现本质上是实现了List接口。其实我们不实现List接口也是没有任何变化的。
Why does ArrayList have “implements List”?
结论二:
I’ve asked Josh Bloch, and he informs me that it was a mistake. He used to think, long ago, that there was some value in it, but he since “saw the light”. Clearly JDK maintainers haven’t considered this to be worth backing out later.
高票答友说他向写集合类的作者 Josh Bloch表达过疑惑,Josh Bloch回答说在做这个设计的时候,他觉得有些价值,但后来发现,这并没有什么卵用,是一个错误。但目前Oracle并没有把这个设计剔除
Why does LinkedHashSet extend HashSet and implement Set
Map接口没有继承Iterable接口,为什么可以使用迭代器遍历? Map及其子类虽然没有实现Iterable接口,但是为什么还是能用Iterator迭代器去遍历呢?这个问题有点意思,但是当我们理清楚HashMap遍历的本质时,我们便会一目了然。
  • 首先HashMap并不能直接被foreach循环,会被提示foreach not applicable to type 'java.util.Map,也就是说HashMap其实并不能通过Iterator迭代器机制去遍历
  • 虽然HashMap不能直接遍历,但是HashMap真正存储数据的是内部的KeySet和EntrySet内部类,这两个实现了Set接口,相当于间接实现了Iterable接口
  • 因为KeySet和EntrySet间接实现了Iterable接口,所以KeySet和EntrySet可以运行迭代器机制进行迭代遍历,它的作用就相当于HashMap可以被迭代器迭代遍历一般。
所以,认真点,HashMap并不能被迭代器所遍历,但是HashMap中存储数据的内部类可以通过迭代器遍历数据
Iterator与ListIterator有什么区别? Iterator:
  • 只能正向遍历集合
  • 在遍历的时候不同修改,增加元素。可以删除元素
  • 适用范围广,List,Set,Map等…
ListIerator:
  • 继承Iterator,可以双向列表的遍历
  • 可以遍历的同时修改,增加,删掉元素
  • 适用范围窄,只适用于List集合
在HashTable上下文中同步是什么意思? 同步意味着在一个时间点只能有一个线程可以修改哈希表,任何线程在执行hashtable的更新操作前需要获取对象锁,其他线程等待锁的释放。
快速失败特性(fail—fast)和安全失败特性(fail—safe)? 快速失败和安全失败特性主要体现在集合中
快速失败特性(fail—fast) 快速失败原则一般用于List,Set,Map集合中,我们在这些集合里面的crud操作中,可以看到一个属性modCount,如下:
/** * ArrayList的Remove操作 */ public E remove(int index) { rangeCheck(index); modCount++; //modCount E oldValue = https://www.it610.com/article/elementData(index); int numMoved = size - index - 1; if (numMoved> 0) System.arraycopy(elementData, index+1, elementData, index, numMoved); elementData[--size] = null; // clear to let GC do its workreturn oldValue; }

什么是modCount呢? 这是为了为了防止集合数据不一致的情况,我们可以理解成这个集合的版本,每当该集合进行一次crud操作,版本号就会+1。
所以当我们通过迭代器机制进行遍历的同时,还对集合的内容进行修改crud操作,比如删除,当遍历到下一个元素前,迭代器都会检查集合的modCount变量是否为期待的expectedmodCount值,如果是则返回遍历结果,如果不是则会抛出异常,终止遍历。
这就是快速失败机制,在遍历过程中发现集合的版本号与期望的版本号不一致时,说明遍历过程中,数据发生过修改,与原集合内容不一致,所以为了保证遍历的结果正确,我们需要抛出异常。
快速失败机制可以防止什么情况呢? 如果没有快速失败机制,在多个线程对同一个集合进行操作时就有可能无法获取到正确的数据。
比如线程A正在遍历集合中,此时线程B在对该集合进行数据删除操作。因为线程B的删除操作,而导致线程A获得的遍历结果是线程B删除后的,线程A并没有获取到期望的数据。如果有快速失败机制,则线程A在遍历时就会因为线程B的修改而抛出Concurrent Modification Exception异常
安全失败特性(fail—safe) 采用安全失败机制的集合容器有CopyOnWriteArrayList等,一般都是并发包下的集合。安全失败机制使得这些集合在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。
好处:
由于迭代时是对集合的拷贝进行遍历,所以在遍历过程中,所有对原集合所作的修改,迭代器都是无法感知到的,因为本质上迭代的是原集合的拷贝,与原集合无关,所以不会触发Concurrent Modification Exception异常。
坏处:
安全失败机制的优点是避免了Concurrent Modification Exception异常,但同样地,迭代器并不能访问到原集合修改后的内容,即迭代器遍历那一刻,拿到的是原集合的拷贝,在遍历期间原集合发生的修改迭代器是无法感知的。
怎样使HashMap同步?
  • HashMap可以通过Map m = Collections.synchronizedMap(hashMap)来达到同步的效果。
  • 或者直接使用并发包下的ConcurrentHashMap
什么时候使用Hashtable,什么时候使用HashMap
  • 他们最主要的区别就是Hashtable是线程同步,而HashMap不是线程同步,所以当需要多个线程访问相同共享实例的时候,就可以考虑使用Hashtable,如果不需要线程同步则考虑使用HashMap。非线程安全可以降低系统开销,所以HashMap的性能更好。但HashMap也可以使用Collections.synchronizedMap来实现线程安全。
  • 如果有一种情况是需要按顺序获数据时,HashMap则是一个更好的选择,因为有HashMap的一个子类 LinkedHashMap,且很容易转换为HashMap。反观HashTable就没有相对于的实现了。
所以总的来说HashMap更灵活易容用,效率更高,所以能使用HashMap去实现就不选用HashTable。
为什么官方不推荐使用Vector类并视其为过时技术? 通常情况下,很多场景都不需要线程同步,所以我们更多的使用ArrayList而不是Vector。Vector类同步了每个方法,你几乎从不要那样做,通常有想要同步的是整个操作序列。同步单个的操作也不安全(如果你迭代一个Vector,你还是要加锁,以避免其它线程在同一时刻改变集合).而且效率更慢。当然同样有锁的开销即使你不需要,这是个很糟糕的方法在默认情况下同步访问。你可以一直使用Collections.sychronizedList来装饰一个集合。
事实上Vector结合了“可变数组”的集合和同步每个操作的实现。这是另外一个设计上的缺陷。Vector还有些遗留的方法在枚举和元素获取的方法,这些方法不同于List接口,如果这些方法在代码中程序员更趋向于想用它。尽管枚举速度更快,但是他们不能检查如果集合在迭代的时候修改了,这样将导致问题。

扩展知识
  • 集合类的多种遍历方式
  • Collections工具类的应用
  • Java8的ArrayList源码分析
  • LinkedList的源码分析
  • HashSet的源码分析
  • TreeSet的源码分析
  • Java8的HashMap源码分析
  • TreeMap的源码分析

参考资料
  • 《Java编程思想》
  • JAVA集合类汇总 - 作者:@lipper_
  • Java集合类详解 - 作者:@也非野人
  • 史上最全的Java集合类解析 - 作者:@HHcoco
  • Java 集合框架 - 作者:@菜鸟教程
  • Java——HashSet和TreeSet的区别 - 作者:@夏之晨风

    推荐阅读