本文主要从集合整体说明Collection,对集合中LIst、Set及常遇到的点进行说明,其中单独对ArrayList、LinkedList、Vector具体实现细节,可参考其它文章。
文章目录
List集合的体系结构
集合底部实现依赖于数组,又和传统的数组有区别。但是,集合和数组又可以相互转化。
数组:用于存放具有相同类型的元素,且大小容量固定。能通过下表快速访问元素,不易扩容。
相较于数组,集合的具备的特点。
集合:大小可动态扩展,可以存储各种类型的数据。
相比于数组,集合具备很多优势,但是具体是使用数组还是集合,还是需要根据实际场景选择,避免大材小用。
再上图,看集合Colleciton下的接口,类继承关系,一目了然。
从图中看出,对于集合List和Map的接口和类的继承关系,这里不讨论Map。对于集合,其超级接口为Collection,然后Collection接口又依赖与Iterator接口。Collection接口继承Iterator接口表明自己支持迭代,能有使用foreach语句的特权。
继承了Collection接口的,又有队列Queue、List和Set。因此,理解集合,主要就是要关注Queue、List、Set,其中更常用的是List和Set。
Colleciton作为集合的超级接口,其中声明的方法,更具有代表性。JDK不提供此接口的任何直接实现,均通过实现更具体的子接口(如 Set 和 List)。Collection中代表性方法如下表格。
方法 | 解释 |
---|---|
boolean add(Object obj) | 添加元素,成功返回true |
boolean addAll(Collection v) | 向该Colleciton中添加一个Colleciton |
void clear() | 清空集合中所有元素 |
boolean contains(Object obj) | 集合中是否包含某一元素 |
boolean containsAll(Collection c) | 集合中是否包含另外一个集合中的所有元素 |
boolean isEmpty() | 集合是否为空 |
Iterator iterator() | 返回该集合的迭代器Iterator,可用于遍历元素 |
boolean remove(Object obj) | 移除集合中指定元素 |
boolean removeAll(Coolection c) | 移除集合中包含指定集合c中的所有元素 |
int size() | 返回集合中的元素个数 |
Object[] toArray() | 返回包含此collection中所有元素的数组 |
Java中List、Set、Queue的比较
再上图,Collection全家族。
从图中可以看出Collection下主要包括Set、Queue、List的接口实现,三者分别具备不同特点,应用不同场景。
List、Set与Queue都是实现了Collection的子接口,具备不同的特点,概括如下
- List中元素,有序,可重复,可为空;
- Set中元素,无序,不可重复,只可有一个元素为空;
- Queue表示队列,有序,可重复,可为空,先入先出。
Java中的List有几种实现,各有什么不同?
共有三种实现,ArrayList、LinkedList、Vector。
- ArrayList由数组实现,能够自动扩容,默认扩容0.5倍,随机存取速度快,添加,修改效率高,非线程安全
- LinkedList由双向链表实现,顺序访问高效,插入,删除效率高,非线程安全
- Vector底部维护一个数组,默认每次扩容1倍,线程安全的集合,采用同步方法实现。
详细了解可参考
LinkedList源码解析
ArrayList源码解析
Java中的Set有几种实现,各有什么不同?
三种实现,HashSet、TreeSet、LinkedHashSet
集合Set特点:Set中元素,无序,不可重复,只可有一个元素为空;
其实HashSet、TreeSet、LinkedHashSet,实现都是与map有关,熟悉了HashMap、LinkedHashMap,这些就不是问题了。
- HashSet,维护一个HashMap,将Map中的value值每次设置为Null,传入参数存入Map的Key中,利用HashCode计算,不能按照存入顺序遍历
- TreeSet,维护一个TreeMap,有序,可以通过实现Comparable接口,提取有序序列
- LinkedHashSet,维护一个LinkedHashMap,迭代器遍历时,能够按照插入顺序输出。
HashMap相关可参考
HashMap源码分析
什么是synchronizedList吗?他和Vector有何区别?
synchronizedList
synchronizedList是Collections下的一个静态内部类,返回一个线程安全的List,用来保证集合的线程安全。以ArrayList为例,synchronizedList返回的List,其内部增删改查依然调用的为ArrayList下的方法,但是在调用ArrayList方法时添加了同步代码块来保证线程同步。
Vector
Vector是实现Collection接口的一个类,
两者区别
- SynchronizedList中使用的同步代码块,Vector是同步方法,锁对象不同
- 扩容机制不同,Vector默认情况扩展为原来一倍,ArrayList为其50%。
SynchronizedList特点
- SynchronizedList可以将所有的List子类转换成线程安全的
- 遍历过程中,增删操作,SynchronizedList无法保证同步,需要手动同步
- SynchronizedList使用不同代码块,可以改变锁对象
通过Array.asList获得的List有何特点,使用时该注意什么?
前面提到过,数组与集合是可以相互转化的,其中方法之一是调用方法Arrays.asList()方法,
- Arrays.asList()方法,传入数组,返回ArrayList对象,但是该ArrayList对象与集合java.util.ArrayList不同,为Arrays类中的一个内部类。是原来数组视图的List,大小固定,因此不能进行增删操作。
- 可以使用ArrayList构造器,将其转变为java.util.ArrayList。
Integer[] array = {1, 2, 3}; Integer[] array = {1, 2, 3};
List<Integer> aList = Arrays.asList(array); // 不能增删
ArrayList<Integer> arrList = new ArrayList<>(aList); // 可以增删
什么是fail-fast?
使用迭代器遍历一个集合过程中,再去修改(添加,修改,删除)集合中内容,则会抛出ConcurrentModificationException异常。
因为迭代器在遍历过程中是直接访问内部数据的,因此内部的数据在遍历的过程中无法被修改。为了保证不被修改,迭代器内部维护了一个标记“modCount”,当集合结构改变(添加,修改,删除),标记“modCount”会被修改,而迭代器每次的hasNext()和next()方法都会检查该“modCount”是否被改变,当检测到被修改时,抛出ConcurrentModificationException。
如下代码
Iterator itr = list.iterator();
int i = 0;
while(itr.hasNext())
{
if(i == 1)
{
list.remove(1);
}
i++;
System.out.println(itr.next());
}
将会抛出异常ConcurrentModificationException。
-
注意:
该异常的抛出条件是 modCount != expectedModCount,若在修改集合内容时,又恰好修改了expectedModCount时,如并发条件下。
那么不会抛出该异常。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法,
迭代器的快速失败行为应该仅用于检测bug。 -
应用
java.util包下的集合均是快速失败,不能在多线程下发生并发修改(指迭代过程中修改)。
什么是fail-safe?
指为了避免fail-fast,使用fail-safe机制的集合,在遍历操作时,不是直接在原集合的内容上访问,先复制原有集合内容,在拷贝的集合上进行遍历。由于遍历的为原集合的拷贝,在遍历过程中,即使原集合内容进行了改变,也不会被迭代器检测到,因此也不会抛出异常ConcurrentModificationException。
-
缺点
由于这种设计,遍历开始时,拿到的是集合的拷贝,遍历过程也是对拷贝集合中的操作,遍历过程中,集合内容发生的改变不能被监测到,不具备实时性。 -
应用
java.util.concurrent包下的容器,均为安全失败,可以在多线程下并发修改。
如何在遍历的同时删除ArrayList中的元素?
方法1
使用迭代器Iterator中remove方法。代码如下:
Iterator itr = list.iterator();Iterator itr = list.iterator();
int i = 0;
while(itr.hasNext())
{
if(i == 1)
{
//list.remove(1);
itr.remove();
}
i++;
System.out.println(itr.next());
}
采用迭代器中的remove方法,删除元素,但是该方法只能移除当前遍历的元素,不能删除指定元素。
Java中remove()方法源码
public void remove() {public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException异常。();
}
}
remove()方法实现还是调用了ArrayList的remove方法,将修改后的modCount,再重新复制给了expectedModCount,
有效避免了修改集合元素时判断modCount != expectedModCount,导致报ConcurrentModificationException异常。
方法2
采用java.util.concurrent包中类替代ArrayList存储元素。例如:CopyOnWriteArrayList