1. Collection集合接口
Java中大量的集合类和接口(Map除外)都是派生自Collection接口!Collection接口中定义了一个集合的基本能力!
public interface Collection<E> extends Iterable<E>
{
/*获取集合长度*/
int size();
/*判断集合是否为空*/
boolean isEmpty();
/*判断集合中是否存在o元素*/
boolean contains(Object o);
/*返回集合迭代器*/
Iterator<E> iterator();
/*将集合转换为数组*/
Object[] toArray();
/*将集合转换为数组,如果a数组可以存放下集合中元素,使用a空间;如果空间不够,重新开辟;并返回*/
<T> T[] toArray(T[] a);
/*添加一个元素*/
boolean add(E e);
/*移除一个元素*/
boolean remove(Object o);
/*判断一个集合是否被当前集合包含*/
boolean containsAll(Collection<?> c);
/*将一个集合加入当前集合*/
boolean addAll(Collection<? extends E> c);
/*将集合C中存在的元素,全部异常*/
boolean removeAll(Collection<?> c);
/*集合中元素满足filter.test方法后,移除该元素*/
default boolean removeIf(Predicate<? super E> filter) {
Objects.requireNonNull(filter);
boolean removed = false;
final Iterator<E> each = iterator();
while (each.hasNext()) {
if (filter.test(each.next())) {
each.remove();
removed = true;
}
}
return removed;
}
/*当前集合和C集合取交集*/
boolean retainAll(Collection<?> c);
/*清空集合*/
void clear();
/*判断两个集合是否相等*/
boolean equals(Object o);
/*返回集合对象的hashCode值*/
int hashCode();
/*集合接口为并行Stream提供的,元素分隔迭代器;用于为Stream的并行执行,分隔集合中的元素*/
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
/*集合串行Stream获取方法*/
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
/*集合并行Stream获取方法*/
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
}
2. 集合的遍历及Iterator接口
2.1 常用的集合遍历方式(for循环下表、foreach循环、迭代器迭代、迭代器forEachRemaining、集合forEach):
public class CollectionTest
{
public static void main(String arg[])
{
Collection<String> collection = new ArrayList<>();
collection.add("1");
collection.add("12");
collection.add("123");
collection.add("1234");
/*for循环方式遍历*/
System.out.println("***************1***************");
for (int i = 0; i < collection.size(); i++)
{
/*Collection接口没有提供通过索引获取元素的方法*/
}
/*foreach循环方式遍历*/
System.out.println("***************2***************");
for (String temp : collection)
{
System.out.println(temp);
}
/*迭代器方式遍历*/
System.out.println("***************3***************");
Iterator<String> iterator1 = collection.iterator();
while(iterator1.hasNext())
{
System.out.println(iterator1.next());
}
/*集合foreach方式遍历*/
System.out.println("***************4***************");
collection.forEach(obj->System.out.println(obj));
/*迭代器foreach方式遍历*/
System.out.println("***************5***************");
Iterator<String> iterator2 = collection.iterator();
iterator2.forEachRemaining(obj->System.out.println(obj));
}
}
Ps:迭代器和集合的foreach方式很相似,forEach和forEachRemaining两个方法的入参都是一个Consumer函数式接口,都可以传入lamda表达式;内部实现都是遍历集合元素,并将元素作为入参传入函数式接口的默认方法中!区别就是forEach方法是Collection接口继承自Iterable接口的默认方法(使用foreach方式遍历);forEachRemaining方法是Iterator接口的默认方法(使用迭代器迭代的方式遍历)!
源码如下:
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
2.2 常用的数组和String遍历方式:
public class CollectionTest
{
public static void main(String arg[])
{
String[] a = new String[4];
a[0] = "1";
a[1] = "12";
a[2] = "123";
a[3] = "1234";
/*数组for循环遍历*/
System.out.println("***************1***************");
for (int i = 0; i < a.length; i++)
{
System.out.println(a[i]);
}
/*数组foreach循环遍历*/
System.out.println("***************2***************");
for (String s : a)
{
System.out.println(s);
}
String s = "1234";
/*字符串for循环遍历*/
System.out.println("***************3***************");
for (int i = 0; i < s.length(); i++)
{
System.out.println(s.charAt(i));
}
System.out.println("***************4***************");
/*字符串转换为char数组,再进行foreach循环遍历*/
for (char c : s.toCharArray())
{
System.out.println(c);
}
}
}
2.3 Iterator接口介绍:
iterator接口提供四个方法:
- hasNext():判断是否还存在下一个元素
- next():获取下一个元素
- remove():删除集合里上一次next()的元素
- forEachRemaining(Consumer<? super E> action):通过迭代器遍历集合元素
Ps:
- Collection接口中提供了一个获取iterator的方法,在实现Collection接口的同时也需要同步实现一个iterator的接口,来满足集合迭代器的功能!
- 通过迭代器遍历集合的时候,获得的集合元素只是一个副本,并不集合元素本身,所以通过迭代器是不能改变元素内容的,这一点与foreach遍历一致(不管是数组遍历的foreach还是集合遍历的foreach都不能改变元素内容)!
- 迭代器很像指针,当迭代器指向集合末尾的时候,如果需要再次遍历,只能通过集合重新获取迭代器,指向集合尾部的迭代器不能重新遍历集合!
- 集合的foreach遍历和迭代器遍历中,都不能改变集合的数目(包括增加、删除),除非在同线程中迭代器遍历时调用迭代器的remove()方法,但是可以修改集合元素的内容;一旦改变集合数目,则会抛出java.util.ConcurrentModificationException;所以如果多线程并发修改集合数目的时候,遍历集合只能使用for循环索引方式遍历;值得注意的是,数组的foreach遍历不涉及这个问题,因为数组长度是固定的!
2.4 Predicate函数式接口:
Collection接口在Java8后引入了一个removeIf(Predicate<? super E> filter)方法,该方法作用是,遍历集合中所有元素,并将元素传递给Predicate函数式接口,如果返回true,则删除该元素!原理与Iterable的forEach类似!
2.5 Stream方式操作集合:
Java8引用了IntStream、LongStream、DoubleStream、Stream四个流式API接口;用来补充数组、集合在函数式编程上面的能力,同时提供了串行和并行两种流的操作!
代码示例:
public class StreamTest
{
public static void main(String[] args)
{
IntStream istream = IntStream.builder()
.add(1)
.add(100)
.add(200)
.build();
System.out.println("Begin IntStream Test!!!");
/*通过流式Api的函数式编程,计算IntStream流中的小于100的个数*/
System.out.println("IntStream Test Num " + istream.filter(e->{
if (e > 100)
return true;
else
return false;
}).count());
/*count方法为终止方法,执行后,IntStream流会关闭,不能再次使用该流对象,否则会报错*/
//System.out.println("IntStream Test Max " + istream.max());
/*通过流式Api的函数式编程,计算Stream串行流中的小于100的个数*/
System.out.println("Begin Stream Test!!!");
HashSet<Integer> hs = new HashSet<>();
hs.add(1);
hs.add(100);
hs.add(200);
Stream<Integer> istream1 = hs.stream();
System.out.println("Stream Test Num " + istream1.filter(e->{
if (e > 100)
return true;
else
return false;
}).count());
/*通过流式Api的函数式编程,计算Stream并行流中的小于100的个数*/
System.out.println("Begin ParallelStream Test!!!");
Stream<Integer> istream2 = hs.parallelStream();
System.out.println("ParallelStream Test Num " + istream2.filter(e->{
if (e > 100)
return true;
else
return false;
}).count());
}
}
执行结果:
Ps:
- 可见Collection接口提供了两个获取Stream流的方法(stream()、parallelStream());分别表示串行执行流、并行执行流;同时还为并行执行流提供了一个默认的元素分隔迭代器(spliterator()),用于并行执行元素的分隔!
- IntStream、LongStream、DoubleStream默认都是串行执行流!
- 当使用了Stream流式Api中的末端方法(例如:count()、max()、min()等方法)后,该流对象会被关闭,无法再次使用!
Stream接口中提供的能力:
主要方法说明:
3. Set集合
Set是一个元素不重复的集合概念,Set接口继承自Collection接口,接口提供方法与Collection完全一致!
3.1 HashSet集合:
3.1.1 HashSet集合简述:
- HashSet是继承自AbstractSet抽象类,AbstractSet抽象类继承自Set接口,Set接口继承自Collection接口;
- HashSet是线程不安全集合;
- HashSet可以接收null元素;
- HashSet是一个无序的集合;
- HashSet基于HashMap实现的,HashSet是一个value固定为PRESENT的HashMap,元素就是HashMap的key值对象;
/**
* Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
* default initial capacity (16) and load factor (0.75).
*/
public HashSet() {
map = new HashMap<>();
}
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
/**
* Adds the specified element to this set if it is not already present.
* More formally, adds the specified element <tt>e</tt> to this set if
* this set contains no element <tt>e2</tt> such that
* <tt>(e==null ? e2==null : e.equals(e2))</tt>.
* If this set already contains the element, the call leaves the set
* unchanged and returns <tt>false</tt>.
*
* @param e element to be added to this set
* @return <tt>true</tt> if this set did not already contain the specified
* element
*/
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
3.1.2 HashSet集合的使用:
HashSet中主要方法与Collection接口中一直,这里就不再仔细展开了!
3.2 LinkedHashSet集合:
3.2.1 LinkedHashSet集合简述:
LinkedHashSet是HashSet的子类,继承了HashSet的全部能力,唯一区别就是LinkedHashSet是通过HashSet(int initialCapacity, float loadFactor, boolean dummy)构造方法,构造的LinkedHashMap(HashMap的子类、且记录了元素插入顺序),用来保证集合的有序性!
3.3 TreeSet集合、EnumSet集合:
与HashSet原理相同,TreeSet和EnumSet本质分别就是value固定为PRESENT的TreeMap和EnumMap,再进一步通过TreeMap和EnumMap中的key元素集合实现TreeSet和EnumSet的能力!
Ps:通过对上面Set集合的总结,可见Set集合是基于固定value的Map集合中key元素集合的进一步的能力封装!所以详细的说明见<JavaSE学习笔记(13.Java之集合Map)>
4. List集合
List是一个可通过下标访问的有序线性表数据结构接口,继承自Collection接口!
List接口提供能力:
Ps:
1. List接口除了实现继承了Collection接口的能力以外,还提供了一些通过下标进行数据操作的方法;
2. List接口中通过集合元素数据对象进行的数据查询及操作的都满足一个原则,仅仅通过equals()方法来作为数据判断的唯一依据,并且当List集合中存在多份数据的时候,通常都是查找及操作最靠前的元素!
3. List接口中还提供了两个函数式编程的默认方法:sort(Comparator<? super E>)和replaceAll(UnaryOperator<E>)
4. List中除了支持Collection接口继承的迭代器iterator,还提供了一个独立的迭代器listIteration(listIteration迭代器支持双向迭代)
4.1 ArrayList集合与Vector集合:
4.1.1 ArrayList集合简述:
- ArrayList是继承自AbstractList抽象类,AbstractList抽象类继承自List接口,List接口继承自Collection接口;
- ArrayList是线程不安全集合;
- ArrayList可以接收null元素;
- ArrayList是一个有序的集合(以下标为序);
- 存储结构:数组(顺序存储)
- 集合容量:无限制
4.1.2 Vector集合简述:
- Vector是继承自AbstractList抽象类,AbstractList抽象类继承自List接口,List接口继承自Collection接口;
- Vector是线安全集合;
- Vector可以接收null元素;
- Vector是一个有序的集合(以下标为序);
- 存储结构:数组(顺序存储)
- 集合容量:无限制
4.1.2 ArrayList集合与Vector集合区别:
- ArrayList和Vector实质上都是可变长数组,在元素不断增加的过程中不断扩充数组大小
- ArrayList为线程不安全容器,但是可以使用Collections.synchronizedList()方法进行包装!
- Vector为线程安全容器,但是由于Vector和hashtable一样都是java1.0的集合,处于半废弃状态,不建议使用!
4.1.3 Stack集合:
Stack集合是Vector的子类(与Vector特性相同),实质上就是通过对Vector的封装实现压栈出栈等操作(pop、peek)!同样不建议使用,推荐使用ArrayDeque类代替!
4.2 Arrays.ArrayList集合:
Arrays数组类中提供了一个静态内部类Arrays.ArrayList集合;该集合的特点就是线性表长度固定,当通过Arrays.asList静态方法构造Arrays.ArrayList集合的时候,集合长度已经固定为入参数组长度,该List集合不可再使用add、remove等方法改变集合长度,否则会抛出UnsupportedOperationException异常!
5. Queue集合
Queue是一个有序的队列接口,继承自Collection接口!
Ps:除了Collection提供的能力,Queue接口还提供了一些队列相关操作的能力!
- add(E):在队列尾插入元素,当队列满的时候,会抛出IIIegaISlabEepeplian异常!
- offer(E):在队列尾插入元素,当队列满的时候,返回false!
- remove():删除队列头元素并返回,当队列为空的时候,会抛出NoSuchElementException异常!
- poll():删除队列头元素并返回,当队列为空的时候,返回null!
- element():返回队列头元素,当队列为空的时候,会抛出NoSuchElementException异常!
- peek():返回队列头元素,当队列为空的时候,返回null!
5.1 PriorityQueue集合:
5.1.1 PriorityQueue集合简述:
- PriorityQueue是继承自AbstractQueue抽象类,AbstractQueue抽象类继承自Queue接口,Queue接口继承自Collection接口;
- PriorityQueue是线程不安全集合;
- PriorityQueue不可以接收null元素;
- PriorityQueue是一个有序的集合(与TreeMap类似,根据自定义比较方式,进行队列顺序约束);
- 存储结构:数组(顺序存储);存储逻辑是堆结构
- 集合容量:无限制
示例代码:
public class PriorityQueueTest
{
public static void main(String[] args)
{
PriorityQueue<Integer> queue = new PriorityQueue<>();
queue.offer(100);
queue.offer(10);
queue.offer(20);
queue.offer(300);
System.out.println("**************1***************");
/*通过循环遍历输出的时候,无顺序*/
for (Integer i : queue)
{
System.out.println(i);
}
System.out.println("**************2***************");
/*按照队列元素获取的方式遍历,存在顺序*/
while (null != queue.peek())
{
System.out.println(queue.poll());
}
}
}
输出结果:
Ps:
- PriorityQueue虽然继承Queue接口,具备入队列和出队列的能力;但是队列顺序并不是根据队列进出顺序进行判断的,而是根据元素的排序规则进行判断的(方式与TreeMap的自然排序和定制排序一致),任务队列顺序是从小到大顺序!
- 通过for循环遍历PriorityQueue集合,可见PriorityQueue并不是在入队列的时候进行的顺序管理,而是在poll和remove出队列的时候进行处理的!
- JDK中Queue接口的实现类并不多,原因在于Queue接口仅仅提供了一个队列的基本能力;Queue接口的子类Deque接口(双向队列)的使用范围更广!
5.2 Deque接口:
Deque接口是Queue接口的子类,提供了一个双向队列的能力,同时也提供了一个栈的能力!
5.3 ArrayDeque集合:
5.3.1 ArrayDeque集合简述:
- ArrayDeque是继承自Deque接口,Deque接口继承自Queue接口,Queue接口继承自Collection接口;
- ArrayDeque是线程不安全集合;
- ArrayDeque不可以接收null元素;
- ArrayDeque是一个有序的集合(队列顺序);
- 存储结构:数组(顺序存储)
- 集合容量:无限制
示例代码:
public class ArrayDequeTest
{
public static void main(String args[])
{
/*Queue Test*/
ArrayDeque<String> queue = new ArrayDeque<>();
System.out.println("Queue Test Begin!");
queue.offerLast("a1");
queue.offerLast("a2");
queue.offerLast("a3");
/*for循环遍历输出*/
for (String s : queue)
{
System.out.println(s);
}
System.out.println("*****************************");
/*通过队列元素获取的方式遍历*/
while (null != queue.peekFirst())
{
System.out.println(queue.pollFirst());
}
/*Stack Test*/
ArrayDeque<String> stack = new ArrayDeque<>();
System.out.println("Stack Test Begin!");
queue.push("a1");
queue.push("a2");
queue.push("a3");
while (null != queue.peekFirst())
{
System.out.println(queue.pop());
}
}
}
输出结果:
Ps:可见ArrayDeque集合的用途很广,既可以当做队列使用也可以当做栈来使用!
5.4 LinkedList集合:
5.4.1 LinkedList集合简述:
- LinkedList集合比较特殊,前面介绍的线性表都是通过数组实现的顺序存储线性表;LinkedList是个链式存储的线性表!
- LinkedList即继承自List接口,又继承自Deque接口;可见LinkedList是链式存储的双向队列、栈、列表!
- LinkedList是线程不安全集合;
- LinkedList可以接收null元素;
- LinkedList是一个有序的集合(以下标为序);
- 存储结构:链表(链式存储)
- 集合容量:无限制
示例代码:
public class TestWorkSpace
{
public static void main(String args[])
{
/*ListTest*/
System.out.println("ListTest Begin!");
LinkedList<String> list = new LinkedList<>();
list.add(0,"a1");
list.add(1,null);
list.add(2,"a3");
for (String s : list)
{
System.out.println(s);
}
/*QueueTest*/
System.out.println("QueueTest Begin!");
LinkedList<String> queue = new LinkedList<>();
queue.offerLast("a1");
queue.offerLast(null);
queue.offerLast("a3");
while (!queue.isEmpty())
{
System.out.println(queue.pollFirst());
}
/*StackTest*/
System.out.println("StackTest Begin!");
LinkedList<String> stack = new LinkedList<>();
stack.push("a1");
stack.push(null);
stack.push("a3");
while (!stack.isEmpty())
{
System.out.println(stack.pop());
}
}
}
输出结果:
Ps:
- LinkedList由于是链式结构,所以不存在容器初始化大小,当然也不存在扩容的概念!
- 链式存储(LinkedList)与顺序存储(ArrayXXX)的选择:
- 当需要大量使用下标访问元素的时候,建议选择顺序存储;所以LinkedList使用时尽量多用迭代器,如果下标访问频繁建议使用ArrayXXX
- 当需要大量的插入、删除;或者容器大小不确定,频繁变化的时候,建议选择链式存储
5.5 BlockingQueue接口:
BlockingQueue阻塞队列接口,提供了一个队列满时,入队列阻塞等待(put方法);队列空时,出队列阻塞等待(take方法)的队列接口!
5.5.1 BlockingQueue实现类:
ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue、DelayQueue这四个实现类都是线程安全的集合!都不能允许添加null元素!
ArrayBlockingQueue集合:
基于数组的阻塞队列,必须强制在构造方法中指定队列大小!
LinkedBlockingQueue集合:
基于链表的阻塞队列,不需强制在构造方法中指定队列大小,默认大小为Integer.MAX_VALUE!
PriorityBlockingQueue集合:
数组作为物理存储,堆结构作为逻辑存储的优先级阻塞队列,是对PriorityQueue集合的封装!PriorityBlockingQueue集合不存在队列满的场景,会不断的扩容,所以不存在put()阻塞的场景!
DelayQueue集合:
DelayQueue集合是一个用于存放Delayed类型及其子类的阻塞队列,同样DelayQueue也是PriorityQueue集合的封装,也不存在队列满的场景,会不断的扩容,所以也不存在put()阻塞的场景!只有元素的延迟期满了,才可以出队列,而且队列顺序是按照超过延迟期长短排序的,超时最长的放在队列头;如果队列中所有元素都未超过延迟期,则队列为空,take()方法会阻塞!
Ps :虽然BlockingQueue接口的实现类,实现了入队列和出队列的阻塞功能,但是当使用迭代器遍历队列的时候,依然不能改变队列长度,否则依然会抛出ConcurrentModificationException异常!
6. 集合线程安全分析:
上面描述的集合中,除Vector、Stack、Hashtable这三个比较老的类以外,其他的集合均为线程不安全集合!集合所谓的线程不安全是指对集合的修改(增加元素,修改元素,删除元素)行为不是原子行为!导致这些行为的并发会造成集合内部数据的破坏!针对这个问题,Java提供了两种解决方案,一种是ConcurrentXXX集合,另一种是Collections.SynchronizedXXX方法:
6.1 ConcurrentXXX集合:
ConcurrentXXX集合包括:ConcurrentHashMap、ConcurrentLinkedDeque、ConcurrentLinkedQueue、ConcurrentSkipListMap、ConcurrentSkipListSet、CopyOnWriteArrayList、CopyOnWriteArraySet,这7个集合类!
- ConcurrentXXX集合是Java1.5后,在java.util.concurrent包下提供支持高效并发访问的集合!
- ConcurrentXXX集合由于比Collections.SynchronizedXXX方法同步锁的颗粒更细,所以效率更高!(Collections.SynchronizedXXX方法是锁集合,导致集合所有行为都存在锁的竞争)
- ConcurrentLinkedDeque集合、ConcurrentLinkedDeque集合并没有设置并发数量;ConcurrentHashMap集合在构造函数中可以设置concurrencyLevel来设置并发数量!
- ConcurrentHashMap中从Java1.8后,增加了很多函数式编程方法,可以结合Lamda表达式进行聚集操作!
- 普通集合在使用迭代器遍历集合的时候,改变集合数量会导致抛出异常!ConcurrentXXX集合修改了这个限制,支持可以在迭代器中修改集合数量,不会抛出异常!但是Collections.SynchronizedXXX方法仅仅是对非线程安全的集合进行了一层包装,所以如果在同线程中可以拿到同步锁的情况下,在迭代器遍历中修改结合数量,依然会导致抛出异常!
- 针对ConcurrentXXX集合的迭代器遍历,如果想实时感知其他线程对集合的修改,需要不断的获取新的迭代器;如果一致使用老的迭代器会导致感知不到获取迭代器后面的集合修改!
- 除了ConcurrentXXX集合外,java.util.concurrent包中还提供了两个CopyOnWriteXXX集合,底层通过使用数组复制的形式来实现线程安全!
- ConcurrentSkipListMap集合、ConcurrentSkipListSet集合是通过跳表数据结构实现的!
6.2 Collections.SynchronizedXXX方法:
Collections类中提供了,SynchronizedCollection、SynchronizedSet、SynchronizedSortedSet、SynchronizedNavigableSet、SynchronizedList、SynchronizedRandomAccessList、SynchronizedMap、SynchronizedSortedMap、SynchronizedNavigableMap这几个静态内部类,分别对上述非线程安全集合,通过synchronized同步代码块进行同步封装,将线程不安全集合包装为线程安全集合!
7.操作集合工具类:Collections
7.1 排序操作:
7.2 查找、替换操作:
7.3 设置不可变集合:
Collections类中提供三类静态方法,提供三类不可变集合:
- unmodifiableXXX方法:将一个集合包装为不可变集合,修改集合会抛出异常!
- singletonXXX方法:提供一个只有一个元素的集合,修改集合会抛出异常!
- emptyXXX方法:提供一个不可变的空集合,修改集合会抛出异常!
public class CollectionsTest
{
public static void main(String args[])
{
List<String> list = Collections.emptyList();
/*抛出UnsupportedOperationException异常*/
//list.add("emptyList");
List<String> list1 = Collections.singletonList("singletonList");
/*抛出UnsupportedOperationException异常*/
//list1.add("singletonList1");
Map<String,String> mapTest = new HashMap<>();
mapTest.put("key1","value1");
mapTest.put("key2","value2");
Map<String,String> map = Collections.unmodifiableMap(mapTest);
/*抛出UnsupportedOperationException异常*/
//map.put("key1","value1");
}
}
Ps:可见Collections类中提供了很多对集合操作的静态方法,而且很多功能是与Stream流式Api重复的;是由于Collections类是Java1.2版本的时候,为集合类提供补充能力的,Stream流式Api是Java1.8后,为后续函数式编程演进提供的能力!