JAVA集合类概览
带着问题来阅读
1、Java有哪些集合
2、不同集合的应用场景分别是哪些
3、哪些实现类是线程安全的
4、为什么Java集合不能存放基本类型
5、集合的fail-fast和fail-safe是什么
Java集合概览
Java通过Java Collections Framework(JCF)为开发者提供了一系列集合接口和实现,所谓集合,就是多个Java对象的聚集。
学习过数据结构的同学们对各类集合的定义肯定不陌生,Java通过提供一系列的内置数据结构实现,为开发者提高了开发的便利性,提升了程序的兼容性,降低了编程的复杂性。

图片出自https://www.pdai.tech/md/java/collection/java-collection-all.html
Java集合包含两个顶层接口:Collection和Map,Collection是主要保存对象的集合,Map是保存键值对的集合。
下面我们对两类集合的实现做简单介绍。
Collection
Collection是Jdk1.2版本引入的接口,用于描述保存对象的集合,它的扩展接口有List、Queue、Set。
List
List以列表形式顺序存取元素,保证元素的插入顺序和存储顺序一致。
| 实现 | 线程安全 | |
|---|---|---|
| ArrayList | 数组 | 否 |
| LinkedList | 双向链表 | 否 |
| CopyOnWriteArrayList | 数组 | 是,使用CopyOnWrite保证线程安全 |
| Vector | 数组 | 是,使用Synchronized保证线程安全 |
| Stack | 数组 | 是,继承Vector |
Vector是Jdk1.0就引入的线程安全的列表实现,早于Collection接口设计,采用直接在方法上添加Synchronized来保证线程安全,Stack是继承Vector实现的栈结构,由于其线程安全的低效性,目前在实际环境均不再推荐使用。
Queue
Queue是先进先出的结构,从队尾加入元素,队头弹出元素。其子接口Deque为双端队列,即两头都可以进出。
| 实现 | 线程安全 | |
|---|---|---|
| ArrayDeque | 循环数组 | 否 |
| LinkedList | 链表 | 否 |
| PriorityQueue | 堆 | 否 |
| BlockingQueue | BlockingQueue为阻塞队列的扩展接口 | 是 |
由于BlockingQueue略为复杂,更涉及到一些进阶应用场景,留待后续讲解。
Set
Set是不重复元素集合,元素中不包含相同元素,且通常情况不保持元素插入顺序
| 实现 | 线程安全 | |
|---|---|---|
| HashSet | 哈希表 | 否,基于HashMap实现 |
| TreeSet | 红黑树 | 否,基于TreeMap实现 |
| LinkedHashSet | 哈希表+链表 | 否,基于LinkedHashMap实现 |
| CopyOnWriteArraySet | 数组 | 是,CopyOnWrite保证 |
| ConcurrentSkipListSet | 跳表 | 是,基于ConcurrentSkipListMap实现 |
Map
Map用于存储<Key, Value>键值对。
| 实现 | 线程安全 | |
|---|---|---|
| HashMap | 哈希表+红黑树 | 否 |
| TreeMap | 红黑树 | 否 |
| LinkedHashMap | 哈希表+链表+红黑树 | 否 |
| WeakHashMap | 哈希表 | 否 |
| ConcurrentHashMap | 哈希表+红黑树 | 是,基于节点CAS和Synchronized实现 |
| ConcurrentSkipListMap | 跳表 | 是 |
为什么Java集合不能存放基本类型
Java在1.2版本引入JCF框架,Java范型是在1.5版本引入,因此在泛型引入之前集合默认以Object作为存储类型。
以List为例
List list = new ArrayList();
list.add(123); // 自动boxing
list.add("123");
int num = (int) list.get(0);
String str = (String) list.get(1);
显而易见该方式存在缺陷,集合内可以放入任何以Object为基类的元素,而元素的获取方无法确定元素的具体类型,容易出现类型转换错误。在1.5版本引入范型以后,对集合接口进行规范,添加了范型参数,Java的泛型机制本质上还是将具体类型擦除为Object,因此泛型集合在初始化时,无法将参数指定为非Object派生的基本类型。
什么是fail-fast和fail-safe
List<String> list = new ArrayList();
list.add("123");
list.add("456");
//(1) throw ConcurrentModificationException
for (String s : list) {
list.remove(s);
}
//(2) 正常移除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
it.next();
it.remove();
}
//(3) throw ConcurrentModificationException
new Thread(() -> {
for (String s : list) {
System.out.println(s);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException ignore)
{
}
}
}).start();
new Thread(() -> {list.add("789");}).start();
上面这段代码,(1) (3) 会抛出ConcurrentModificationException,(2)可以正常移除所有元素。首先了解一下ConcurrentModificationException,当对一个对象做出的并发修改不被允许时,将抛出这个异常。
This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.
那么(1)是单线程执行,(3)并发添加元素也并未对已有元素做修改,为什么也会触发该异常呢。
ArrayList var1 = new ArrayList();
var1.add("123");
var1.add("456");
Iterator var2 = var1.iterator();
while(var2.hasNext()) {
String var3 = (String)var2.next();
var1.remove(var3);
}
对代码(1)的class文件反编译查看,发现foreach实际上是通过Iterator做的迭代,迭代过程中删除是直接调用list.remove。我们再进入到list.iterator方法探个究竟。
/** Returns an iterator over the elements in this list in proper sequence.
* The returned iterator is fail-fast. */
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
....
int expectedModCount = modCount;
....
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
iterator方法会创建一个Itr对象,在其创时会复制modCount到expectedModCount,每进行迭代时都会判断两个值是否相同,如果不同则抛出ConcurrentModificationException。
再来看modCount是一个继承自AbstractList的成员变量,用于记录list被修改的次数,每当调用add/remove时,modCount都会加1。
// The number of times this list has been structurally modified.
protected transient int modCount = 0;
那么问题就很明显了,每当对list进行修改modCount都会改变,而foreach的iterator记录的是迭代对象创建时刻的modCount值,接下来的迭代过程中,由于调用了list的修改方法,改变了其中modCount的值,导致modCount != expectedModCount,于是就抛出了异常。(3)代码是相同的问题,不再进行赘述。
* <p><a name="fail-fast">
* The iterators returned by this class's {@link #iterator() iterator} and
* {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
* if the list is structurally modified at any time after the iterator is
* created, in any way except through the iterator's own
* {@link ListIterator#remove() remove} or
* {@link ListIterator#add(Object) add} methods, the iterator will throw a
* {@link ConcurrentModificationException}. Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.
在所有Java集合类中,直接位于java.util下除Vector、Stack、HashTable外,所有的集合都是fail-fast的,而在java.util.concurrent下的集合都是fail-safe的,即可以并发的遍历和修改集合,具体实现由各自的线程安全机制保证。
为什么需要fail-fast
fail-fast意为快速失败,在非线程安全的集合应用场景中,并发对集合做的添加/删除,可能导致另一个正在遍历集合的线程出现未知的错误如数组越界。因此非线程安全的集合实现引入fail-fast以此来快速中断线程,避免引发未知的连锁问题。
参考
JAVA集合类概览的更多相关文章
- Java 性能调优指南之 Java 集合概览
[编者按]本文作者为拥有十年金融软件开发经验的 Mikhail Vorontsov,文章主要概览了所有标准 Java 集合类型.文章系国内 ITOM 管理平台 OneAPM 编译呈现,以下为正文: 本 ...
- Java集合类--温习笔记
最近面试发现自己的知识框架有好多问题.明明脑子里知道这个知识点,流程原理也都明白,可就是说不好,不知道是自己表达技能没点,还是确实是自己基础有问题.不管了,再巩固下基础知识总是没错的,反正最近空闲时间 ...
- 做JavaWeb开发不知Java集合类不如归家种地
Java作为面向对象语言对事物的体现都是以对象的形式,为了方便对多个对象的操作,就要对对象进行存储.但是使用数组存储对象方面具有一些弊端,而Java 集合就像一种容器,可以动态地把多个对象的引用放入容 ...
- 【转载】Java集合类Array、List、Map区别和联系
Java集合类主要分为以下三类: 第一类:Array.Arrays第二类:Collection :List.Set第三类:Map :HashMap.HashTable 一.Array , Arrays ...
- 摘抄转载前辈们的Java集合类总结
本文摘自 Blue Sky:http://www.cnblogs.com/hubcarl JAVA 集合类介绍和使用 类关系示意图Iterable(接口) │ └--Collection (接口) ├ ...
- Java集合类简单总结(重学)
java集合类简介(重学) 一.Collection(集合).Map接口两者应该是平行关系吧. 1.Map介绍 Map是以键值(key-value)对来存放的,2个值.通过key来找到value(例: ...
- Java集合类中的哈希总结
JAVA集合类中的哈希总结 目 录 1.哈希表 2.Hashtable.HashMap.ConcurrentHashMap.LinkedHashMap.TreeMap区别 3.Hashtable.Ha ...
- Java集合类: Set、List、Map、Queue使用场景梳理
本文主要关注Java编程中涉及到的各种集合类,以及它们的使用场景 相关学习资料 http://files.cnblogs.com/LittleHann/java%E9%9B%86%E5%90%88%E ...
- Java 集合类详解(含类图)
0.参考文献 此图中蓝色为抽象类.深红色表示接口(Arrays除外).绿色表示具体容器类 1.java集合类图 1.1 1.2 上述类图中,实线边框的是实现类,比如ArrayList,LinkedLi ...
随机推荐
- Java操作SQL数据库(JDBC)
0.引入驱动jar包 使用jdbc进行具体操作前,需要引入相关数据库的jar包, 或者使用mave管理依赖 <!-- https://mvnrepository.com/artifact/mys ...
- Unity接入ShareSDK实现QQ登录和QQ分享、微信分享
原文链接:Unity接入ShareSDK实现QQ登录和QQ分享.微信分享 由于微信登录需要企业审核,我这里就不说明了,有需要的可以去官网看一下文档,和QQ登录比多了一个打包的步骤. 第一步:到官网申请 ...
- k8s1.20环境搭建部署(二进制版本)
1.前提知识 1.1 生产环境部署K8s集群的两种方式 kubeadm Kubeadm是一个K8s部署工具,提供kubeadm init和kubeadm join,用于快速部署Kubernetes集群 ...
- 关于Ubuntu的超级管理员Root的切换及初始密码设置
背景介绍 总有一些操作,可能需要更高的超级管理员权限才能进行,甚至才可见有些文件,所以在Linux中我们需要切换到Root用户,也就是对应的Windows的Administrator账户. 从当前用户 ...
- 从零开始学前端,React框架背后的核心机制和原理JSX
什么是React React是起源于Facebook的一个前端框架,用于构建用户界面的JavaScript库,Facebook用来探索一种更加高效优雅的Javascript MVC框架来架设Insta ...
- layui动态创建Tab、Tab右键功能
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 & ...
- 关于PHP导出数据超时的优化
一般情况下,导出超时可能都是以下三种情况: 一.sql语句复杂,查询时间过长: 二.处理查询后数据逻辑冗余: 三.数据量过大导致响应超时. 接下来分别给出这三种情况的优化建议. 一.sql语句复杂,查 ...
- LeSS 的诞生(一):大规模团队该何去何从
<敏捷宣言>发布后,"敏捷"被越来越多的小型开发团队认可.与此同时,另一个问题也逐渐暴露了出来:以 Scrum 为首的敏捷方法论对那些大规模的开发团队并不友好. 基于此 ...
- Nginx:Nginx配置url重定向
符号含义: 正则表达式匹配: ~ 为区分大小写匹配 ~* 为不区分大小写匹配 !~和!~*分别为区分大小写不匹配及不区分大小写不匹配 文件及目录匹配: -f和!-f用来判断是否存在文件 -d和!-d用 ...
- ArcnLinux安装基础配置(二)
本文为对此ArchLinux安装使用教程网站中部分内容的总结和扩展补充,想看更详细的内容可以去此网站. 添加一个用户 useradd -m -G wheel -s /bin/bash cirry 设置 ...