文章目录
首先,要知道Java中的集合不仅仅是指Set系列,而是(包含Set、List、Queue和Map四种体系),是用于存储数量不等的对象。
其中,
Set
代表无序、不可重复的集合;
List
代表有序、重复的集合;
Map
则代表具有映射关系的集合;
Queue
代表一种队列集合;
Java集合类主要是由两个接口派生而出:Collection
和Map
一、Collection和数组的区别
数组:
长度不可变;
无法保存具有映射关系的数据;
数组元素可以是基本类型(byte,short,int,long,float,double,boolean,char)的值,也可以是对象(实际上保存的是对象的引用变量)
集合类:
可以保存长度不确定的数据;
可以保存具有映射关系的数据;
集合里保存的是对象(实际上保存的是对象的引用变量)
二、遍历集合的方法
1.使用lambda表达式遍历集合
//programming=["Java","Python","C#","C++"]
programming.forEach(obj -> System.out.println("迭代集合元素:" + obj));
2.使用Iterator循环遍历Collection
//programming=["Java","Python","C#","C++"]
var it = programming.iterator(); // 获取programming集合对应的迭代器
while (it.hasNext()){
// it.next返回的是数据类型是Object类型,因此需要强制类型转换
var p = (String) it.next();
System.out.println(p);
}
3.使用foreach循环遍历Collection
//programming=["Java","Python","C#","C++"]
for (var program : programming){
var p = (String) program;
System.out.println(p);
}
三、使用Predicate操作集合
public class temp {
public static void main(String[] args) {
var programming = new HashSet<>();
programming.add("Java");
programming.add("Python");
programming.add("C#");
programming.add("C++");
programming.add("JavaScript");
programming.add("Vue");
System.out.println(calculate(programming, ele -> ((String) ele).length() == 3));
System.out.println(calculate(programming, ele -> ((String) ele).contains("C")));
}
public static int calculate(Collection objs, Predicate p) {
int total = 0;
for (var obj : objs) {
// 使用Predicate的test()方法判断该对象是否满足Predicate指定的条件
if (p.test(obj)) {
total++;
}
}
return total;
}
}
四、使用Stream操作集合
独立使用stream的步骤如下:
- 使用Stream或XxxStream的builder()类方法创建该Stream对应的Builder;
- 重复调用Builder的add()方法向流中添加多个元素;
- 调用Builder的build()方法获取对应的Stream;
- 调用Stream的聚集方法;
(对于大部分的聚集方法,每个Stream只能执行一次,所以下面执行了两个聚集方法allMatch和map,在两个方法之间又重新定义了一个变量is1,给第二个聚集方法使用)
public static void main(String[] args) {
var is = IntStream.builder().add(3).add(1).add(2).add(9).build();
System.out.println("is所有元素是否都是偶数:" + is.allMatch(ele -> ele % 2 == 0));
var is1 = IntStream.builder().add(3).add(1).add(2).add(9).build();
var newIs = is1.map(ele -> ele * 3 + 5);
newIs.forEach(System.out::println);
// is.forEach(ele -> System.out.println(ele % 2));
}
Stream提供了大量的方法进行聚集操作,这些方法既可以是“中间的”,也可以是“末端的”。上述的allMatch就是末端操作,map是中间操作。
中间操作:允许流保持打开状态,并允许直接调用后续方法,中间方法的返回值是另外一个流;
末端方法:对流的最终操作。当对某个Stream执行末端方法后,该流将会被“消耗”,切不可再用。
所以上述的is流进行了allMatch末端操作后,被消耗,不可再次使用;
常用的中间操作:filter
、mapToXxx
、peek
、distinct
、sorted
、limit
等
常用的末端方法:forEach
、toArray
、reduce
、min
、max
、count
、anyMatch
、allMatch
、noneMatch
、findFirst
、findAny
等
五、Set集合
1.HashSet
HashSet按Hash算法来存储集合中的元素,因此具有很好的存取和查找性能。
- 不能保证元素的排列顺序;
- HashSet不是同步的,是线程不安全的;
- 集合元素值可以是null;
2.LinkedHashSet
- LinkedHashSet是HashSet的子类;
- 也是根据元素的hashCode值来决定元素的存储位置,但它同时使用链表维护元素的次序。这样是的元素看起来是以插入的顺序保存的;
- 线程不安全;
- 集合元素值可以是null;
3.TreeSet
- TreeSet是SortedSet接口的实现类,可以确保集合元素处于排序状态;
- 采用红黑树的数据结构来存储集合元素;
- 线程不安全;
自然排序
自然排序的对象要实现Comparable接口,而且应该是同一个类的多个实例;定制排序
在创建TreeSet对象时,提供一个Comparator对象与该TreeSet集合关联,由Comparator对象负责集合元素的排序逻辑;
(0:相等;负整数:前者小于后者;正整数:前者大于后者)
4.EnumSet
- EnumSet中的所有元素都必须是指定枚举类型的枚举值
- 元素有序,以枚举值在Enum类内的定义顺序来决定集合元素的顺序
- 在内部以向量的形式存储,紧凑高效,占用内存小,运行效率高
- 不允许加入null元素
- 线程不安全
5.Set实现类的选择
性能:EnumSet > HashSet > TreeSet
需要一个保持排序的Set:TreeSet
遍历:LinkedHashSet > HashSet
即一般情况下选HashSet
,需要排序
选TreeSort
,强调遍历
选LinkedHashSet
,枚举
选EnumSet
六、List集合
1.ArrayList(基于数组的线性表)
- 封装了一个动态的,允许再分配的Object[]数组,initialCapacity参数默认值为10;
- 对于所有内部基于数组的集合实现,使用随机访问的性能要比使用Iterator迭代访问的性能要好;
- 线程不安全
- Arrays.asList:生成Arrays的内部类ArrayList的一个实例,Arrays.ArrayList是一个固定长度的List集合,只能遍历,不能增加删除;
2.Vector
- 与上述ArrayList类似,显著区别是:Vector线程安全,还提供了一个Stack子类,模拟“栈”;
- 虽然Vector也是以数组的形式存储集合元素,但因为它实现了线程同步功能,加之其它原因,性能较差;
七、Queue
1.PriorityQueue
- Queue的实现类
- 保存队列中的元素顺序以队列元素大小排序,而不是加入队列的顺序;
- 不允许插入null元素;
自然排序
定制排序
- 对元素的要求类似于TreeSet;
2.Deque
- 是Queue的子接口,代表一个双端队列,既可以当队列使用,也可以当栈使用;
3.ArrayDeque
- Deque接口的实现类
-
- 封装了一个动态的,允许再分配的Object[]数组,initialCapacity参数默认值为16;
4.LinkedList(基于链的线性表)
- List接口的实现类,有List集合的特点,可以根据索引来随机访问集合中的元素;
- 实现了Deque接口,可以被当成双端队列使用,“栈”、“队列”
5.各种线性表的选择
- 所有内部以数组作为底层实现的集合在随机访问时性能都比较好,eg. ArrayList、ArrayDeque
- 内部以链表形式作为底层实现的集合在执行插入、删除操作时有较好的性能,eg. LinkedList
- 提到排序的:PriorityQueue
八、Map
Set接口下有HashSet
、LinkedHashSet
、SortedSet
、TreeSet
、EnumSet
等子接口和实现类;
Map接口下有HashMap
、LinkedHashMap
、SortedMap
、TreeMap
、EnumMap
等子接口和实现类;
Map的类和子接口中Key集的存储形式和对应的Set集合中的元素存储形式完全相同;
从Java源码来看,Java先是实现了Map,然后通过包装一个所有value均为空对象的Map就实现了Set集合;
Map提供了一个Entry内部类来封装key-value对,而计算Entry存储时则只考虑Entry封装的key;
1.HashMap
- 线程不安全
- 可以使用null为key或value,最多只能有一个key-value对的key为null;
2.Hashtable(古早,和Vector一样)
- 线程安全,性能比HashMap差;
- 不允许使用null作为key和value;
3.LinkedHashMap
- HashSet的子类
- 需要维护元素的插入顺序,性能略低于HashMap;
- 以(双向)链表维护内部元素,所以迭代访问(遍历时)有较好性能;
scores.forEach((key,value) -> System.out.println(key + "-->" + value));
4.Properties
- Hashtable的子类
- 处理属性文件(ini、xml)
5.TreeMap
- 自然排序
- 定制排序
6.WeakHashMap
- HashMap的key保存了对实际对象的强引用
- WeakHashMap保存了对实际对象的弱引用
7.IdentityHashMap
- 与HashMap类似
- 不同点:在IdentityHashMap中,当且仅当两个key严格相等(key1 == key2)时,才认为两个key相等;在HashMap中,只要key1和key2通过equals方法比较返回true,且hashCode值相等,才认为两个key相等;
8.Map实现类的选择
性能:EnumMap > HashMap(IdentityHashMap) > LinkedHashMap > Hashtable > TreeMap
强调排序:TreeMap > HashMap
九、操作集合的工具类:Collections
- 排序
- 查找替换
- 同步控制
synchronizedXxx
- 设置不可变集合:
emptyXxx
、singletonXxx
、unmodifiableXxx
、(Java9新增的:Set、List、Map的of
方法)