集合专题
脑图
知识点
1:set表示无序,不可重复的集合,List代表有序、重复的集合,map表示有映射关系的集合。
2: 迭代器
迭代器是一个对象,它的工作是遍历并选择序列中的对象,它提供了一种访问一个容器对象中的各个元素,而又不必暴露该对象内部细节的方法。
使用Iterator方法返回一个Iterator,通过next和hasNext遍历,可以通过remove()方法删除迭代器返回的元素。
当Iterator这个迭代器被创建后,除了迭代器本身的方法(remove)可以改变集合的结构外,其他的因素如若改变了集合的结构,都被抛出ConcurrentModificationException异常
场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
场景:java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
3:java8 为Iterable接口新增一个foreach(Comsumer action)默认方法,该方法所需的参数类型是一个函数式接口,而Iterable是Collection接口的父接口,因此Collection集合也可以直接调用该方法,可以使用forEachRemaining(ConSumer action),该方法可以使用Lambda表达式来遍历集合元素。
4:使用Predicate操作集合
使用Predicate可以充分简化集合的运算,假设依然有上面程序所示的books集合,如果程序有如下三个统计需求:
➢ 统计书名中出现“疯狂”字符串的图书数量。
➢ 统计书名中出现“Java”字符串的图书数量。
➢ 统计书名长度大于10的图书数量。
此处只是一个假设,实际上还可能有更多的统计需求。如果采用传统的编程方式来完成这些需求,则需要执行三次循环,但采用Predicate只需要一个方法即可。如下程示范了这种用法。

上面程序先定义了一个calAll()方法,该方法将会使用Predicate判断每个集合元素是否符合特定条件——该条件将通过Predicate参数动态传入。从上面程序中三行粗体字代码可以看到,程序传入了三个Lambda表达式(其目标类型都是Predicate),这样calAll()方法就只会统计满足Predicate条件的图
5:使用Stream操作集合
上面程序先创建了一个IntStream,接下来分别多次调用IntStream的聚集方法执行操作,这样即可获取该流的相关信息。注意:上面粗体字代码每次只能执行一行,因此需要把其他粗体字代码注释掉。
Stream提供了大量的方法进行聚集操作,这些方法既可以是“中间的”(intermediate),也可以是“末端的”(terminal)。
➢ 中间方法:中间操作允许流保持打开状态,并允许直接调用后续方法。上面程序中的map()方法就是中间方法。中间方法的返回值是另外一个流。
➢ 末端方法:末端方法是对流的最终操作。当对某个Stream执行末端方法后,该流将会被“消耗”且不再可用。上面程序中的sum()、count()、average()等方法都是末端方法
有状态的方法:这种方***给流增加一些新的属性,比如元素的唯一性、元素的最大数量、保证元素以排序的方式被处理等。有状态的方法往往需要更大的性能开销。
➢ 短路方法:短路方法可以尽早结束对流的操作,不必检查所有的元素。
下面简单介绍一下Stream常用的中间方法。
➢ filter(Predicate predicate):过滤Stream中所有不符合predicate的元素。
➢ mapToXxx(ToXxxFunction mapper):使用ToXxxFunction对流中的元素执行一对一的转换,该方法返回的新流中包含了ToXxxFunction转换生成的所有元素。
➢ peek(Consumer action):依次对每个元素执行一些操作,该方法返回的流与原有流包含相同的元素。该方法主要用于调试。
➢ distinct():该方法用于排序流中所有重复的元素(判断元素重复的标准是使用equals()比较返回true)。这是一个有状态的方法。
➢ sorted():该方法用于保证流中的元素在后续的访问中处于有序状态。这是一个有状态的方法。
➢ limit(long maxSize):该方法用于保证对该流的后续访问中最大允许访问的元素个数。这是一个有状态的、短路方法。
下面简单介绍一下Stream常用的末端方法。
➢ forEach(Consumer action):遍历流中所有元素,对每个元素执行action。
➢ toArray():将流中所有元素转换为一个数组。
➢ reduce():该方法有三个重载的版本,都用于通过某种操作来合并流中的元素。
➢ min():返回流中所有元素的最小值。
➢ max():返回流中所有元素的最大值。
➢ count():返回流中所有元素的数量。
➢ anyMatch(Predicate predicate):判断流中是否至少包含一个元素符合Predicate条件。
➢ allMatch(Predicate predicate):判断流中是否每个元素都符合Predicate条件。
➢ noneMatch(Predicate predicate):判断流中是否所有元素都不符合Predicate条件。
➢ findFirst():返回流中的第一个元素。
➢ findAny():返回流中的任意一个元素。
6:set集合
HashSet具有以下特点。
➢ 不能保证元素的排列顺序,顺序可能与添加顺序不同,顺序也有可能发生变化。
➢ HashSet不是同步的,如果多个线程同时访问一个HashSet,假设有两个或者两个以上线程同时修改了HashSet集合时,则必须通过代码来保证其同步。
➢ 集合元素值可以是null。
当向HashSet集合中存入一个元素时,HashSet会调用该对象的hashCode()方法来得到该对象的hashCode值,然后根据该hashCode值决定该对象在HashSet中的存储位置。如果有两个元素通过equals()方法比较返回true,但它们的hashCode()方法返回值不相等,HashSet将会把它们存储在不同的位置,依然可以添加成功。
EnumSet
- 不允许插入空值
- 只能添加枚举类型
HashSet和TreeSet是Set的两个典型实现,到底如何选择HashSet和TreeSet呢?HashSet的性能总是比TreeSet好(特别是最常用的添加、查询元素等操作),因为TreeSet需要额外的红黑树算法来维护集合元素的次序。只有当需要一个保持排序的Set时,才应该使用TreeSet,否则都应该使用HashSet。
HashSet还有一个子类:LinkedHashSet,对于普通的插入、删除操作,LinkedHashSet比HashSet要略微慢一点,这是由维护链表所带来的额外开销造成的,但由于有了链表,遍历LinkedHashSet会更快。
EnumSet是所有Set实现类中性能最好的,但它只能保存同一个枚举类的枚举值作为集合元素。
7: list集合
Vector还提供了一个Stack子类,它用于模拟“栈”这种数据结构,“栈”通常是指“后进先出”(LIFO)的容器。最后“push”进栈的元素,将最先被“pop”出栈。与Java中的其他集合一样,进栈出栈的都是Object,因此从栈中取出元素后必须进行类型转换,除非你只是使用Object具有的操作。所以Stack类里提供了如下几个方法。
➢ Object peek():返回“栈”的第一个元素,但并不将该元素“pop”出栈。
➢ Object pop():返回“栈”的第一个元素,并将该元素“pop”出栈。
➢ void push(Object item):将一个元素“push”进栈,最后一个进“栈”的元素总是位于“栈”顶。
8:Queue集合
➢ void add(Object e):将指定元素加入此队列的尾部。
➢ Object element():获取队列头部的元素,但是不删除该元素。
➢ boolean offer(Object e):将指定元素加入此队列的尾部。当使用有容量限制的队列时,此方法通常比add(Object e)方法更好。
➢ Object peek():获取队列头部的元素,但是不删除该元素。如果此队列为空,则返回null。
➢ Object poll():获取队列头部的元素,并删除该元素。如果此队列为空,则返回null。
➢ Object remove():获取队列头部的元素,并删除该元素。
PriorityQueue是一个比较标准的队列实现类。之所以说它是比较标准的队列实现,而不是绝对标准的队列实现,是因为PriorityQueue保存队列元素的顺序并不是按加入队列的顺序,而是按队列元素的大小进行重新排序。因此当调用peek()方法或者poll()方法取出队列中的元素时,并不是取出最先进入队列的元素,而是取出队列中最小的元素。(不允许为空值)
推荐使用ArrayDeque 作为栈的行为。
9
LinkedList与ArrayList、ArrayDeque的实现机制完全不同,ArrayList、ArrayDeque内部以数组的形式来保存集合中的元素,因此随机访问集合元素时有较好的性能;而LinkedList内部以链表的形式来保存集合中的元素,因此随机访问集合元素时性能较差,但在插入、删除元素时性能比较出色(只需改变指针所指的地址即可)。需要指出的是,虽然Vector也是以数组的形式来存储集合元素的,但因为它实现了线程同步功能(而且实现机制也不好),所以各方面性能都比较差。
注意:
对于所有的内部基于数组的集合实现,例如ArrayList、ArrayDeque等,使用随机访问的性能比使用Iterator迭代访问的性能要好,因为随机访问会被映射成对数组元素的访问。
10 关于list使用建议
如果需要遍历List集合元素,对于ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好;对于LinkedList集合,则应该采用迭代器(Iterator)来遍历集合元素。
➢ 如果需要经常执行插入、删除操作来改变包含大量数据的List集合的大小,可考虑使用LinkedList集合。使用ArrayList、Vector集合可能需要经常重新分配内部数组的大小,效果可能较差。
➢ 如果有多个线程需要同时访问List集合中的元素,开发者可考虑使用Collections将集合包装成线程安全的集合。
10:map集合
笔试/面试题
1:以下各类中哪几个是线程安全的?( )
ArrayList
Vector
Hashtable
Stack
vector stack hashtable Enumeration 线程安全