第八节
一、集合
集合分为两类:一类是单列集合元素是一个一个的,另一类是双列集合元素是一对一对的。
- Collection代表单列集合,每个元素(数据)只包含一个值。
- Map代表双列集合,每个元素包含两个值(键值对)。
二、Collection单列集合

Collection集合特点
List系列集合:添加的元素是有序、可重复、有索引。
- ArrayList、LinekdList :有序、可重复、有索引。
Set系列集合:添加的元素是无序、不重复、无索引。
-
HashSet: 无序、不重复、无索引;
-
LinkedHashSet: 有序、不重复、无索引。
-
TreeSet:按照大小默认升序排序、不重复、无索引。
- Collection规定的方法(功能)是全部单列集合都会继承的。
Collection的常见方法:
| 方法名 | 说明 |
|---|---|
| public boolean add(E e) | 把给定的对象添加到当前集合中 |
| public void clear() | 清空集合中所有的元素 |
| public boolean remove(E e) | 把给定的对象在当前集合中删除 |
| public boolean contains(Object obj) | 判断当前集合中是否包含给定的对象 |
| public boolean isEmpty() | 判断当前集合是否为空 |
| public int size() | 返回集合中元素的个数。 |
| public Object[] toArray() | 把集合中的元素,存储到数组中 |
//8.把集合转换为指定类型的数组,可以使用下面的代码
String[] array1 = c.toArray(new String[c.size()]);
System.out.println(Arrays.toString(array1)); //[java1,java2, java2, java3]
三、集合的遍历方式
1.迭代器
概述
- 迭代器是用来遍历集合的专用方式(数组没有迭代器),在Java中迭代器的代表是Iterator。
Collection集合获取迭代器的方法
| 方法名称 | 说明 |
|---|---|
| Iterator iterator() | 返回集合中的迭代器对象,该迭代器对象默认指向当前集合的第一个元素 |
Iterator迭代器中的常用方法
| 方法名称 | 说明 |
|---|---|
| boolean hasNext() | 询问当前位置是否有元素存在,存在返回true ,不存在返回false |
| E next() | 获取当前位置的元素,并同时将迭代器对象指向下一个元素处。 |
Collection<String> c = new ArrayList<>();
Iterator<String> it = c.iterator();
while(it.hasNext()){
String e = it.next();
System.out.println(s);
}
1.当调用iterator()方法获取迭代器时,当前指向第一个元素
2.hasNext()方法则判断这个位置是否有元素,如果有则返回true,进入循环
3.调用next()方法获取元素,并将当月元素指向下一个位置,
4.等下次循环时,则获取下一个元素,依此内推
2.增强for循环
- 增强for可以用来遍历集合或者数组。
- 增强for遍历集合,本质就是迭代器遍历集合的简化写法。
//格式
for(元素的数据类型 变量名 : 数组或者集合) {
//在此处使用变量即可,该变量就是元素
}
//
Collection<String> c = new ArrayList<>();
for(String s: c){
System.out.println(s);
}
3.forEach遍历集合
常用方法
| 方法名称 | 说明 |
|---|---|
| default void forEach(Consumer<? super T> action) | 结合lambda遍历集合 |
Collection<String> lists = new ArrayList<>();
lists.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.*out*.println(s);
}
});
// 简化
lists.forEach(s -> {
System.*out*.println(s);
});
// 继续简化
lists.forEach(s -> System.out.println(s));*
四、List集合
1.List的方法
- List集合支持索引,Collection的功能List也都继承了。
| 方法名称 | 说明 |
|---|---|
| void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
| E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
| E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
| E get(int index) | 返回指定索引处的元素 |
List遍历
- 普通for循环(List有索引)
- 迭代器
- 增强for
- Lambda表达式
2.ArrayList
ArrayList底层的原理
ArrayList集合底层是基于数组结构实现的,也就是说当你往集合容器中存储元素时,底层本质上是往数组中存储元素。
- 查询速度快(根据索引查询数据快):查询数据通过地址值和索引定位,查询任意数据耗时相同。
- 删除效率低:可能需要把后面很多的数据进行前移。
- 添加效率极低:可能需要把后面很多的数据后移,再添加元素;或者也可能需要进行数组的扩容。
原理
①利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组
②添加第一个元素时,底层会创建一个新的长度为10的数组
③存满时,会扩容1.5倍
④如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准
ArrayList集合适合的应用场景
ArrayList适合:根据索引查询数据比如根据随机索引取数据(高效)!或者数据量不是很大时!
ArrayList不适合:数据量大的同时又要频繁的进行增删操作!
想要使用ArrayList存储数据,并对数据进行操作:
-第一步:创建ArrayList容器对象。一般使用空参数构造方法。
-第二步:调用ArrayList类的常用方法对容器中的数据进行操作。
| 构造器 | 说明 |
|---|---|
| public ArrayList() | 创建一个空的集合对象 |
| 常用方法名 | 说明 |
|---|---|
| public boolean add(E e) | 将指定的元素添加到此集合的末尾 |
| public void add(int index,E element) | 在此集合中的指定位置插入指定的元素 |
| public E get(int index) | 返回指定索引处的元素 |
| public int size() | 返回集合中的元素的个数 |
| public E remove(int index) | 删除指定索引处的元素,返回被删除的元素 |
| public boolean remove(Object o) | 删除指定的元素,返回删除是否成功 |
| public E set(int index,E element) | 修改指定索引处的元素,返回被修改的元素 |
3. LinkedList
LinkedList底层原理
基于双链表实现的。
特点:查询慢,增删相对较快,但对首尾元素进行增删改查的速度是极快的。
链表中的结点是独立的对象,在内存中是不连续的,每个结点包含数据值和下一个结点的地址。
| 方法名称 | 说明 |
|---|---|
| public void addFirst(E e) | 在该列表开头插入指定的元素 |
| public void addLast(E e) | 将指定的元素追加到此列表的末尾 |
| public E getFirst() | 返回此列表中的第一个元素 |
| public E getLast() | 返回此列表中的最后一个元素 |
| public E removeFirst() | 从此列表中删除并返回第一个元素 |
| public E removeLast() | 从此列表中删除并返回最后一个元素 |
LinkedList的应用场景:可设计队列
队列的特点:先进先出,后进后出
五、Set集合
1.Set集合
Set要用到的常用方法,基本上就是Collection提供的
Set系列集合特点:无序:添加数据的顺序和获取出的数据顺序不一致; 不重复; 无索引
HashSet : 无序、不重复、无索引。
LinkedHashSet:有序、不重复、无索引。
TreeSet:排序、不重复、无索引。
2.哈希值
就是一个int类型的数值,ava中每个对象都有一个哈希值。
Java中的所有对象,都可以调用Obejct类提供的hashCode方法,返回该对象自己的哈希值。
public int hashCode():返回对象的哈希码值。
对象哈希值的特点
同一个对象多次调用hashCode()方法返回的哈希值是相同的。
不同的对象,它们的哈希值一般不相同,但也有可能会相同(哈希碰撞)。
哈希表是一种增删改查数据性能都较好的结构
3.HashSet集合
HashSet集合底层原理
HashSet集合底层是基于哈希表实现的,哈希表根据JDK版本的不同,也是有点区别的
- JDK8以前:哈希表 = 数组+链表
① 创建一个默认长度16,默认加载因为0.75的数组,数组名table
② 根据元素的哈希值跟数组的长度计算出应存入的位置
③ 判断当前位置是否为null,如果是null直接存入,如果位置不为null,表示有元素,则调用equals方法比较属性值,如果一样,则不存,如果不一样,则存入数组。
④ 当数组存满到16*0.75=12时,就自动扩容,每次扩容原先的两倍
JDK 8之前,新元素存入数组,占老元素位置,老元素挂下面
JDK 8开始之后,新元素直接挂在老元素下面
HashSet集合中存储元素时,底层调用了元素的两个方法:一个是hashCode方法获取元素hashCode值(哈希值);另一个是调用了元素的equals方法,用来比较新添加的元素和集合中已有的元素是否相同。
- 只有新添加元素的hashCode值和集合中以后元素的hashCode值相同、新添加的元素调用equals方法和集合中已有元素比较结果为true, 才认为元素重复。
- 如果hashCode值相同,equals比较不同,则以链表的形式连接在数组的同一个索引为位置
- JDK8以后:哈希表 = 数组+链表+红黑树
JDK8开始后,为了提高性能,当链表的长度超过8时,就会把链表转换为红黑树


HashSet去重原理
HashSet集合默认不能对内容一样的两个不同对象去重复!
比如内容一样的两个学生对象存入到HashSet集合中去, HashSet集合是不能去重复的!
要想保证在HashSet集合中没有重复元素,需要重写元素类的hashCode和equals方法。
4.LinkedHashSet底层原理
- LinkedHashSet类是HashSet的子类
底层原理
LinkedHashSet底层采用的是也是哈希表(数组,链表,红黑树)结构,只不过额外新增了一个双向链表来维护元素的存取顺序。
特点
有序、不重复、无索引
5.TreeSet集合
特点:不重复、无索引、可排序(默认升序排序 ,按照元素的大小,由小到大排序)
底层是基于红黑树实现的排序。
注意
对于数值类型:Integer , Double,默认按照数值本身的大小进行升序排序。
对于字符串类型:默认按照首字符的编号升序排序。
对于自定义类型如Student对象,TreeSet默认是无法直接排序的。
自定义排序规则
TreeSet集合存储自定义类型的对象时,必须指定排序规则,支持如下两种方式来指定比较规则。
方式一
让自定义的类(如学生类)实现Comparable接口,重写里面的compareTo方法来指定比较规则。
public class Student implements Comparable<Student>{
private String name;
private int age;
private double height;
//无参数构造方法
public Student(){}
//全参数构造方法
public Student(String name, int age, double height){
this.name=name;
this.age=age;
this.height=height;
}
//...get、set、toString()方法自己补上..
//按照年龄进行比较,只需要在方法中让this.age和o.age相减就可以。
/*
原理:
在往TreeSet集合中添加元素时,add方法底层会调用compareTo方法,根据该方法的
结果是正数、负数、还是零,决定元素放在后面、前面还是不存。
*/
@Override
public int compareTo(Student o) {
//this:表示将要添加进去的Student对象
//o: 表示集合中已有的Student对象
return this.age-o.age;
}
}
方式二
通过调用TreeSet集合有参数构造器,可以设置Comparator对象(比较器对象,用于指定比较规则。)
public TreeSet(Comparator<? super E> comparator)
Set<Student> students = new TreeSet<>(new Comparator<Student>{
@Override
public int compare(Student o1, Student o2){
//需求:按照学生的身高排序
return Double.compare(o1,o2);
}
});
两种方式中,关于返回值的规则:
- 如果认为第一个元素 > 第二个元素 返回正整数即可。
- 如果认为第一个元素 < 第二个元素返回负整数即可。
- 如果认为第一个元素 = 第二个元素返回0即可,此时Treeset集合只会保留一个元素,认为两者重复。
注意:如果类本身有实现Comparable接口,TreeSet集合同时也自带比较器,默认使用集合自带的比较器排序。
6.总结
1、如果希望记住元素的添加顺序,需要存储重复的元素,又要频繁的根据索引查询数据?
用ArrayList集合(有序、可重复、有索引),底层基于数组的。(常用)
2、如果希望记住元素的添加顺序,且增删首尾数据的情况较多?
用LinkedList集合(有序、可重复、有索引),底层基于双链表实现的。
3.、如果不在意元素顺序,也没有重复元素需要存储,只希望增删改查都快?
用HashSet集合(无序,不重复,无索引),底层基于哈希表实现的。 (常用)
4、如果希望记住元素的添加顺序,也没有重复元素需要存储,且希望增删改查都快?
用LinkedHashSet集合(有序,不重复,无索引), 底层基于哈希表和双链表。
5、如果要对元素进行排序,也没有重复元素需要存储?且希望增删改查都快?
用TreeSet集合,基于红黑树实现。
6.Collection的其他操作
可变参数
- 可变参数是一种特殊的形式参数,定义在方法、构造器的形参列表处,它可以让方法接收多个同类型的实际参数。
- 可变参数在方法内部,本质上是一个数组
public class ParamTest{
public static void main(String[] args){
//不传递参数,下面的nums长度则为0, 打印元素是[]
test();
//传递3个参数,下面的nums长度为3,打印元素是[10, 20, 30]
test(10,20,30);
//传递一个数组,下面数组长度为4,打印元素是[10,20,30,40]
int[] arr = new int[]{10,20,30,40}
test(arr);
}
public static void test(int...nums){
//可变参数在方法内部,本质上是一个数组
System.out.println(nums.length);
System.out.println(Arrays.toString(nums));
System.out.println("----------------");
}
}
- 一个形参列表中,只能有一个可变参数;否则会报错
- 一个形参列表中如果多个参数,可变参数需要写在最后;否则会报错
Collections工具类
Collections并不是集合,它比Collection多了一个s,一般后缀为s的类很多都是工具类。这里的Collections是用来操作Collection的工具类。
六、Map双列集合
1.Map集合
Map集合中的每一个元素是以key=value的形式存在的,一个key=value就称之为一个键值对,而且在Java中有一个类叫Entry类,Entry的对象用来表示键值对对象。
所有的Map集合的特点:键不能重复,值可以重复,每一个键只能找到自己对应的值。

Map集合体系的特点
注意:Map系列集合的特点都是由键决定的,值只是一个附属品,值是不做要求的
HashMap(由键决定特点): 无序、不重复、无索引; (用的最多)
LinkedHashMap (由键决定特点):由键决定的特点:有序、不重复、无索引。
TreeMap (由键决定特点):按照大小默认升序排序、不重复、无索引。
Map<String, Integer> map = new HashMap<>(); // 一行经典代码。 按照键 无序,不重复,无索引。
Map<String, Integer> map = new LinkedHashMap<>(); // 有序,不重复,无索引。
Map是双列集合的祖宗,它的功能是全部双列集合都可以继承过来使用的。
Map的常用方法如下:
| 方法名称 | 说明 |
|---|---|
| public V put(K key,V value) | 添加元素 |
| public int size() | 获取集合的大小 |
| public void clear() | 清空集合 |
| public boolean isEmpty() | 判断集合是否为空,为空返回true , 反之 |
| public V get(Object key) | 根据键获取对应值 |
| public V remove(Object key) | 根据键删除整个元素 |
| public boolean containsKey(Object key) | 判断是否包含某个键 |
| public boolean containsValue(Object value) | 判断是否包含某个值 |
| public Set keySet() | 获取全部键的集合 |
| public Collection values() | 获取Map集合的全部值 |
Map集合遍历方式
方式1:键找值
先获取Map集合全部的键,再通过遍历键来找值
需要用到的方法
| 方法名称 | 说明 |
|---|---|
| public Set keySet() | 获取所有键的集合 |
| public V get(Object key) | 根据键获取其对应的值 |
Map<String, Double> map = new HashMap<>();
map.put("张三", 162);
map.put("李四", 169);
map.put("王五", 165);
// 1、获取Map集合的全部键
Set<String> keys = map.keySet();
// System.out.println(keys);
// [张三, 李四, 王五]
//key
// 2、遍历全部的键,根据键获取其对应的值
for (String key : keys) {
// 根据键获取对应的值
double value = map.get(key);
System.out.println(key + "=====>" + value);
}
方式2:键值对
把“键值对“看成一个整体进行遍历
| Map提供的方法 | 说明 |
|---|---|
| Set<Map.Entry<K, V>> entrySet() | 获取所有“键值对”的集合 |
| Map.Entry提供的方法 | 说明 |
|---|---|
| K getKey() | 获取键 |
| V getValue() | 获取值 |
Map<String, Double> map = new HashMap<>();
map.put("张三", 162);
map.put("李四", 169);
map.put("王五", 165);
// map = {张三=169, 李四=183, 王五=165}
// entries = [(张三=169), (李四=183), (王五=165)]
// entry = (蜘蛛精=169.8) ...
// 1、调用Map集合提供entrySet方法,把Map集合转换成键值对类型的Set集合
Set<Map.Entry<String, Double>> entries = map.entrySet();
for (Map.Entry<String, Double> entry : entries) {
String key = entry.getKey();
double value = entry.getValue();
System.out.println(key + "---->" + value);
}
方式3:Lambda
| 方法名称 | 说明 |
|---|---|
| default void forEach(BiConsumer<? super K, ? super V> action) | 结合lambda遍历Map集合 |
map.forEach((k , v) -> {
System.out.println(k +"----->" + v);
});
2.HashMap
HashMap集合的特点是由键决定的: 键是无序、不能重复,而且没有索引的。
HashMap集合的底层原理
HashMap跟HashSet的底层原理是一模一样的,都是基于哈希表实现的。
实际上:原来学的Set系列集合的底层就是基于Map实现的,只是Set集合中的元素只要键数据,不要值数据而已。
public HashSet() {
map = new HashMap<>();
}
HashMap底层数据结构: 哈希表结构
JDK8之前的哈希表 = 数组+链表
JDK8之后的哈希表 = 数组+链表+红黑树
哈希表是一种增删改查数据,性能相对都较好的数据结构
往HashMap集合中键值对数据时,底层步骤如下
第1步:当你第一次往HashMap集合中存储键值对时,底层会创建一个长度为16的数组
第2步:把键然后将键和值封装成一个对象,叫做Entry对象
第3步:再根据Entry对象的键计算hashCode值(和值无关)
第4步:利用hashCode值和数组的长度做一个类似求余数的算法,会得到一个索引位置
第5步:判断这个索引的位置是否为null,如果为null,就直接将这个Entry对象存储到这个索引位置
如果不为null,则还需要进行第6步的判断
第6步:继续调用equals方法判断两个对象键是否相同
如果equals返回false,则以链表的形式往下挂
如果equals方法true,则认为键重复,此时新的键值对会替换就的键值对。
HashMap底层需要注意这几点:
1.底层数组默认长度为16,如果数组中有超过12个位置已经存储了元素,则会对数组进行扩容2倍
数组扩容的加载因子是0.75,意思是:16*0.75=12
2.数组的同一个索引位置有多个元素、并且在8个元素以内(包括8),则以链表的形式存储
JDK7版本:链表采用头插法(新元素往链表的头部添加)
JDK8版本:链表采用尾插法(新元素我那个链表的尾部添加)
3.数组的同一个索引位置有多个元素、并且超过了8个,则以红黑树形式存储
HashMap底层是基于哈希表实现的
HashMap集合是一种增删改查数据,性能都较好的集合
但是它是无序,不能重复,没有索引支持的(由键决定特点)
HashMap的键依赖hashCode方法和equals方法保证键的唯一
如果键存储的是自定义类型的对象,可以通过重写hashCode和equals方法,这样可以保证多个对象内容一样时,HashMap集合就能认为是重复的。
3.LinkedHashMap
特点:LinkedHashMap集合的特点也是由键决定的:有序的(键存储和取出的顺序是一样的)、不重复、无索引。
LinkedHashMap的底层原理,和LinkedHashSet底层原理是一样的。底层多个一个双向链表来维护键的存储顺序。
4.TreeMap
特点:由键决定的,默认按照键的升序排列,键不重复,也是无索引的。
TreeMap集合的底层原理和TreeSet也是一样的,底层都是红黑树实现的。所以可以对键进行排序。
TreeMap集合同样也支持两种方式来指定排序规则
让类实现Comparable接口,重写比较规则。
TreeMap集合有一个有参数构造器,支持创建Comparator比较器对象,以便用来指定比较规则。
七、集合嵌套
// 1、定义一个Map集合存储全部的省份信息,和其对应的城市信息。
Map<String, List<String>> map = new HashMap<>();
List<String> cities1 = new ArrayList<>();
Collections.addAll(cities1, "南京市","扬州市","苏州市" ,"无锡市","常州市");
map.put("江苏省", cities1);
八、Stream
什么是Stream
Stream流,是Jdk8开始新增的一套API (java.util.stream.*),可以用于操作集合或者数组的数据。
优势: Stream流大量的结合了Lambda的语法风格来编程,提供了一种更加强大,更加简单的方式操作集合或者数组中的数据,代码更简洁,可读性更好。
Stream流的使用步骤
1.先得到集合或者数组的Stream流
2.然后调用Stream流的方法对数据进行处理。
3.获取处理的结果。
获取Stream流
获取集合的Stream流
| Collection提供的如下方法 | 说明 |
|---|---|
| default Stream stream() | 获取当前集合对象的Stream流 |
获取数组的Stream流
| Arrays类提供的如下 方法 | 说明 |
|---|---|
| public static Stream stream(T[] array) | 获取当前数组的Stream流 |
| Stream类提供的如下 方法 | 说明 |
|---|---|
| public static Stream of(T… values) | 获取当前接收数据的Stream流 |
// 1、如何获取List集合的Stream流?
List<String> names = new ArrayList<>();
Collections.addAll(names, "张三","李四","王五");
Stream<String> stream = names.stream();
// 2、如何获取Set集合的Stream流?
Set<String> set = new HashSet<>();
Collections.addAll(set, "张三","李四","王五");
Stream<String> stream1 = set.stream();
stream1.filter(s -> s.contains("三")).forEach(s -> System.out.println(s));
// 3、如何获取Map集合的Stream流?
Map<String, Double> map = new HashMap<>();
map.put("张三", 172);
Set<String> keys = map.keySet();
Stream<String> ks = keys.stream();
Collection<Double> values = map.values();
Stream<Double> vs = values.stream();
Set<Map.Entry<String, Double>> entries = map.entrySet();
Stream<Map.Entry<String, Double>> kvs = entries.stream();
kvs.filter(e -> e.getKey().contains("三"))
.forEach(e -> System.out.println(e.getKey()+ "-->" + e.getValue()));
// 4、如何获取数组的Stream流?
String[] names2 = {"张三","李四","王五"};
Stream<String> s1 = Arrays.stream(names2);
Stream<String> s2 = Stream.of(names2);
中间方法指的是:调用完方法之后其结果是一个新的Stream流,于是可以继续调用方法,这样一来就可以支持链式编程
| Stream****提供的常用中间方法 | 说明 |
|---|---|
| Stream < T > filter(Predicate<? super T> predicate) | 用于对流中的数据进行过滤。 |
| Stream < T > sorted() | 对元素进行升序排序 |
| Stream < T >sorted(Comparator<? super T> comparator) | 按照指定规则排序 |
| Stream< T > limit(long maxSize) | 获取前几个元素 |
| Stream< T > skip(long n) | 跳过前几个元素 |
| Stream< T > distinct() | 去除流中重复的元素。 |
| < R >Stream< R > map(Function<? super T ,? extends R> mapper) | 对元素进行加工,并返回对应的新流 |
| static < T > Stream< T > concat(Stream a, Stream b) | 合并a和b两个流为一个流 |
终结方法指的是调用完成后,不会返回新Stream了,没法继续使用流了。
| Stream提供的常用终结方法 | 说明 |
|---|---|
| void forEach(Consumer action) | 对此流运算后的元素执行遍历 |
| long count() | 统计此流运算后的元素个数 |
| Optional < T > max(Comparator<? super T> comparator) | 获取此流运算后的最大值元素 |
| Optional< T> min(Comparator<? super T> comparator) | 获取此流运算后的最小值元素 |
收集Stream流:就是把Stream流操作后的结果转回到集合或者数组中去返回。
Stream流:方便操作集合/数组的手段; 集合/数组:才是开发中的目的。
| Stream提供的常用终结方法 | 说明 |
|---|---|
| R collect(Collector collector) | 把流处理后的结果收集到一个指定的集合中去 |
| Object[] toArray() | 把流处理后的结果收集到一个数组中去 |
| Collectors工具类提供了具体的收集方式 | 说明 |
|---|---|
| public static Collector toList() | 把元素收集到List集合中 |
| public static Collector toSet() | 把元素收集到Set集合中 |
| public static Collector toMap(Function keyMapper , Function valueMapper) | 把元素收集到Map集合中 |

422





