总体概括
-Java容器分为Collection和Map两个大类
-Collection接口的子接口有:List、Set、Queue接口,Map不是Collection子接口
-Map/Collection/Queue接口是所有集合框架的父接口
-List接口实现类:ArrayList/LinkedList/Vector/CopyOnWriteArrayList/Stack
-Set接口实现类:HashSet/TreeSet/LinkedHashSet
-Map接口实现类:HashMap/LinkedHashMap/TreeMap/ConcurrentHashMap/HashTable/Properties
List
ArrayList
-ArrayList底层数据结构是Object数组。
-ArrayList线程不安全。
-ArrayList有序可重复。
-ArrayList随机访问效率高,ArrayList 实现了 RandomAccess 接口,因此查 找的时候非常快。
-ArrayList尾部添加删除时间复杂度O(1),指定位置添加和删除需要移动数组,时间复杂度O(n-1),适合顺序添加。
-ArrayList默认大小是10个元素,扩容后新容量为旧容量的1.5倍。
-ArrayList支持序列化。
LinkedList
-LinkedList底层使用的是双向链表数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环)
-LinkedList线程不安全
-LinkedList有序可重复
-LinkedList适合插入和删除时间复杂度O(1),指定位置插入和删除需要移动位置时间复杂度O((n))
-LinkedList不支持高效的随机元素访问
-LinkedList采用的是尾插法
-空间消耗别 ArrayList 多
Vector
-Vector底层数据结构是Object数组
-Vector线程安全
-有序可重复
-随机访问效率高
-尾部添加删除时间复杂度O(1),指定位置添加和删除需要移动数组,时间复杂度O(n-1)
-Vector默认初始化容量是10,扩容为原来的2倍
-Vector不支持序列化
CopyOnWriteArrayList
-是一个线程安全的ArrayList,内部使用了重入锁(ReetrantLock)使用System.arraycopy()方法实现写时复制,写删改操作加锁,而读无锁
-底层数据结构是volatile修饰的Object数组
-适合读多写少的场景使用
-初始化容量是1
-有序可重复
-随机访问效率高
-尾部添加删除时间复杂度O(1),指定位置添加和删除需要移动数组,时间复杂度O(n-1)
Stack
-底层数据结构是Object数组,继承自Vector
-线程安全
-先进后出
Set
HashSet
-底层是HashMap,key是值,value是Object静态常量
-无序不可重复
-非线程安全
-无参的构造方法,默认初始容量是16,加载因子是0.75
-通过HashCode和equals判断相等
-迭代器,增强for循环遍历
-集合元素可以是null,但只能放入一个null
TreeSet(有序唯一)
-底层通过treeMap(红黑树)实现
-有序默认自然升须排序
-不可重复
-线程不安全
-集合元素不可以为null
LinkedHashSet
-LinkedHashSet底层实际上是一个LinkedHashMap(数组+双向链表)
-有序不可重复
-非线程安全
-集合元素不可以为null
-初始化容量16,负载因子是0.75
CopyOnWriteArraySet
-底层是通过CopyOnWriteArrayList实现的,也是动态数组结构
-线程安全
-无序不可重复
-适合读多写少
Map
HashMap
-底层由数组+链表+红黑树(jdk1.8增加)实现
-初始长度是16,加载因子是0.75
-HashMap链表转红黑树的条件是:链表长度大于8,数组容量大于64,如果数组不大于64会先扩容
-HashMap允许空的key-value键值对
-每次扩容为原来的2倍,将原集合的元素重新映射到新集合中
ConcurrentHashMap
-线程安全
-jdk1.7中的实现是引入了段的概念,把集合中的数据分成若干个段,每个段其实就是一个小的HashTable,给段进行加锁
-jdk1.8利用CAS+Synchronized来实现
LinkedHashMap
-LinkedHashMap是HashMap+LinkedList(数组+双向链表)的实现。
-key,value允许为空、key重复会覆盖,value允许重复、有序、非线程安全。
TreeMap(有序)
-TreeMap底层是红黑树实现的,红黑树结构天然支持排序,默认情况下通过key值的自然顺序进行排序
-key不能为null,单允许多个value为null
-线程不安全
HashTable
-Hashtable也是一个哈希散列表,Hashtable继承于Dictionary
-HashTable不允许空的key和value值
-线程安全
-HashTable的put和get增加了synchronized锁性能较差
-初始容量是11,扩容为原来的2n+1
ThreadLocal
1.每个Thread都有一个属于自己的ThreadLocalMap,这个ThreadLocalMap是ThreadLocal的一个内部类,类似于一个
HashMap集合
2.每次存取值都是通过ThreadLocalMap存取,key值为ThreadLocal实例,value是需要存放的值
ThreadLocal中的嵌套内部类ThreadLocalMap,这个类本质上是一个map,和HashMap之类的实现相似,依然是key-
value的形式,其中有一个内部类Entry,其中key可以看做是ThreadLocal实例,但是其本质是持有ThreadLocal实例的
弱引用。而线程本身Tread中会有threadLocals这个局部变量,这个局部变量就是ThreadLocalMap的实例存放数据是需
要先初始化这个变量每次保存时都是以一个ThreadLocal实例作为key值,存放的数据作为value值进行存放。一个
ThreadLocal只能存放一种指定的类型的数据。其所有功能都是通过ThreadLocalMap这个内部类实现的。
哪些集合类是线程安全的?
vector:就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已 经不太建议使用。
在web应用中,特别是前台页面,往往效率(页面响应速度)是优 先考虑的。
statck:堆栈类,先进后出。
hashtable:就比hashmap多了个线程安全。
enumeration:枚举,相当于迭代器。
Java集合的快速失败机制 “fail-fast”?
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作 时,有可能会产生 fail-fast 机制。
例如: 假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中 的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简 单的修改集合元素的内容),那么这个时候程序就会抛出ConcurrentModificationException 异常,从而产生fail-fast机制。
原因: 迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount 的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测 modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
解决办法:
1.在遍历过程中,所有涉及到改变modCount值得地方全部加上 synchronized。
2.使用CopyOnWriteArrayList来替换ArrayList
怎么确保一个集合不能被修改?
可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个 只读集合,这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。 示例代码如下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size());
迭代器 Iterator 是什么?
Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
Iterator 怎么使用?有什么特点?
Iterator 使用代码如下:
List<String> list = new ArrayList
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
Iterator 的特点是只能单向遍历,但是更加安全,因为它可以确保,在当前遍历的集合元素被更改的时候,就会抛出 ConcurrentModificationException 异常。
如何边遍历边移除Collection中的元素?
边遍历边修改 Collection 的唯一正确方式是使用 Iterator.remove() 方法,如下:
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
*// do something*
it.remove();
}
一种 常见的错误代码如下:
for(Integer i : list){
list.remove(i)
}
运行以上错误代码会报 ConcurrentModificationException 异常。这是因为当使用 foreach(for(Integeri : list)) 语句时,会自动生成一个iterator 来遍历该 list,但同时该 list 正在被 Iterator.remove() 修改。
Java 一般不允许一个线程在遍历 Collection 时另一个线程修改它。
Iterator 和 ListIterator 有什么区别?
- Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
- Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
- ListIterator 实现 Iterator 接口,然后添加了一些额外的功能,比如添加一个元 素、替换一个元素、获取前面或后面元素的索引位置。
遍历一个 List 有哪些不同的方式?每种方法的实现原理是什 么?Java 中 List 遍历的最佳实践是什么?
遍历方式有以下几种:
- for 循环遍历,基于计数器。在集合外部维护一个计数器,然后依次读 取每一个位置的元素,当读取到后一个元素后停止。
- 迭代器遍历,Iterator。Iterator 是面向对象的一个设计模式,目的是屏 蔽不同数据集合的特点,统一遍历集合的接口。Java 在 Collections 中支 持了 Iterator 模式。
- foreach 循环遍历。foreach 内部也是采用了 Iterator 的方式实现,使 用时不需要显式声明 Iterator 或计数器。优点是代码简洁,不易出错;缺 点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替 换。
最佳实践: Java Collections 框架中提供了一个 RandomAccess 接口,用来标 记 List 实现是否支持Random Access。
- 如果一个数据集合实现了该接口,就意味着它支持 Random Access,按位置读 取元素的平均时间复杂度为 O(1),如ArrayList。
- 如果没有实现该接口,表示不支持 Random Access,如LinkedList。 推荐的做法就是,支持Random Access 的列表可用 for 循环遍历,否则建议 用 Iterator 或 foreach 遍历。
ArrayList 和 LinkedList 的区别是什么?
- 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的数据结构实现。
- 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
- 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为ArrayList 增删操作要影响数组内的其他数据的下标。
- 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
- 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除操作较多时,更推荐使用 LinkedList。
补充:数据结构基础之双向链表
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
ArrayList 和 Vector 的区别是什么?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合。
线程安全:Vector 使用了 Synchronized 来实现线程同步,是线程安全的,而ArrayList 是非线程安全的。
性能: ArrayList 在性能方面要优于 Vector。
扩容: ArrayList 和 Vector 都会根据实际的需要动态的调整容量,只不过在
Vector 扩容每次会增加 1 倍,而 ArrayList 只会增加 50%。
Vector类的所有方法都是同步的。可以由两个线程安全地访问一个Vector对
象、但是一个线程访问Vector的话代码要在同步操作上耗费大量的时间。
Arraylist不是同步的,所以在不需要保证线程安全时时建议使用Arraylist。
插入数据时,ArrayList、LinkedList、Vector谁速度较快?阐述 ArrayList、Vector、LinkedList 的存储性能和特性?
ArrayList、LinkedList、Vector 底层的实现都是使用数组方式存储数据。数组
元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,但是插入元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。
Vector 中的方法由于加了 synchronized 修饰,因此 Vector是线程安全容器,但性能上较ArrayList差。
LinkedList 使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但插入数据时只需要记录当前项的前后项即可,所以 LinkedList插入速度较快。
多线程场景下如何使用 ArrayList?
ArrayList 不是线程安全的,如果遇到多线程场景,可以通过 Collections 的synchronizedList 方法将其转换成线程安全的容器后再使用。例如像下面这样:
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("aaa");
synchronizedList.add("bbb");
for (int i = 0; i < synchronizedList.size(); i++) {
System.out.println(synchronizedList.get(i));
}
为什么 ArrayList 的 elementData 加上 transient 修饰? ArrayList 中的数组定义如下:
private transient Object[] elementData;
再看一下 ArrayList 的定义:
public class ArrayList extends AbstractList implements List,
RandomAccess, Cloneable, java.io.Serializable
可以看到 ArrayList 实现了 Serializable 接口,这意味着 ArrayList 支持序列化。transient 的作用是说不希望 elementData 数组被序列化,重写了 writeObject 实现:
每次序列化时,先调用 defaultWriteObject() 方法序列化 ArrayList 中的非transient 元素,然后遍历elementData,只序列化已存入的元素,这样既加快了序列化的速度,又减小了序列化之后的文件大小。