单列集合图解
注意点:
- List系列集合:有序、可重复、有索引
- Set系列集合:无序、不重复、无索引
Vector
集合已经被淘汰,了解即可Collection
接口是单列集合的祖先(最顶层),它的功能所有单列集合都可以使用- 切记:泛型只允许指定“引用数据类型”,当集合需要存储基本数据类型时,我们可以在泛型中指定基本数据类型对应的包装类
1
2// 集合指定基本数据类型的包装类
Collection<Integer> collection = new ArrayList<>();
Collection接口
常用方法
1 | public boolean add(E e) 把给定的对象添加到当前集合中 |
例题:
add()
:将给定的对象添加到当前集合中注意:1
2
3
4// 定义集合
Collection<String> collection = new ArrayList<>();
// 添加元素
boolean success = collection.add("张三");
a.add()
的返回值:true,表示添加成功,false,表示添加失败
b. 如果当前集合是List系列的,则返回值一定是true(一定会添加成功)
c. 如果当前集合是Set系列的,则返回值有可能是true,也有可能是false
原因:Set集合是不重复的,当指定添加的元素在集合中存在,则添加失败,add()
的返回值就是falseclear()
:清空集合1
2
3
4
5
6
7// 定义集合
Collection<String> collection = new ArrayList<>();
// 添加元素
boolean success = collection.add("张三");
boolean success = collection.add("李四");
// 清空集合
collection.clear()remove(E e)
:删除集合中指定的元素注意:1
2
3
4
5
6// 定义集合
Collection<String> collection = new ArrayList<>();
// 添加元素
boolean success = collection.add("张三");
// 删除集合中指定的元素
boolean resultRemove = collection.remove("张三");
a. 如果当前集合中没有指定删除的元素,则删除失败,返回值为:false
b. 如果当前集合中有指定删除的元素,则删除成功,返回值为:true
c. remove()的实参一定是对象(不能是索引)原因:
Collection
接口中的方法声明的是单列集合(Set + List)中共性的方法,>Set系列集合是没有索引的,所以如果在Collection
中声明有索引的方法是不合适的补充说明:List集合有索引、Set集合无索引,所以有索引的方法是List集合的特有方法,会被声明在List接口中
contains()
:判断当前集合中是否包含给定的对象
注意点: 如果当前集合存储的数据是自定义类,此时使用contains()
判断集合中是否包含给定的对象,自定义类需要重写equals()
1 | import java.util.Objects; |
1 | // 集合存储自定义对象 |
为什么要重写equals()
?
- Object是所有类的父类,equals()方法是声明定义在Object中的
- Object中的equals比较的是栈内存中的值,即:基本数据类型比较其值,引用数据类型比较的是地址值
- String类中的equals重写了,所以字符串调用equals比较的是 堆内存 中 存储的具体值,而不是栈内存中的地址值
- 自定义类,如果想要比较其属性中存储的值是否相等,则需要重写equals,否则equals()是Object继承下来的方法,比较的是地址值
- 技巧:重写的代码不用书写,IDEA可以自动生成(alt + insert,然后选择重写的方法equals即可)
isEmpty()
:判断当前集合是否是空集合
1 | Collection<String> collection = new ArrayList<>(); |
size()
:获取集合中的元素个数
1 | Collection<String> collection = new ArrayList<>(); |
Collection遍历(单列集合通用遍历方式)
Collection集合的遍历方式有三种:迭代器遍历,增强for、lambda
注意:这三种方式的遍历,都不允许在集合遍历时,使用集合中的方法对集合进行添加、删除等操作
- 迭代器遍历集合
迭代器是Java中专门用来遍历集合的注意:1
2
3
4
5
6
7public Iterator<E> iterator() // Collection接口中的方法,用来获取迭代器对象
// Iterator中的方法
// 判断当前指针所在位置是否有元素,有元素,返回true,没有,返回false
public boolean hasNext();
// 获取迭代器指针所在位置的元素,并且将指针向后移动一个位置
public E next();
a. 当我们获取一个新的迭代器对象时,迭代器指针此时的位置是在第一个元素上
b. 当指针所在的位置,没有元素,此时使用next(),程序会抛出错误(所以在使用next获取元素之前一定要,判断指针所在位置是否有元素)
c. 迭代器遍历完成后,指针不会复位(遍历开始时的初始位置)
d. 迭代器遍历时,不能使用集合中的方法,对集合进行添加、删除操作(可以使用迭代器方法删除、添加)
技巧:由于迭代器遍历完成后,指针不会复位,如果此时还想再次遍历集合,可以创新创建一个新的迭代器对象,再进行遍历
例题:迭代器遍历集合时,不能使用集合中的方法删除、添加元素,但可以使用迭代器中的方法添加、删除元素1
2
3
4
5
6
7
8
9
10
11
12
13// 创建集合对象
Collection<String> collection = new ArrayList<>();
// 添加数据
collection.add("张三");
collection.add("李四");
// 获取迭代器对象
Iterator<String> iterator = collection.iterator();
// 迭代器遍历集合
while(iterator.hasNext()){
String item = iterator.next();
System.out.println(item);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14// 创建集合对象
Collection<String> collection = new ArrayList<>();
// 添加数据
collection.add("张三");
collection.add("李四");
Iterator<String> iterator = collection.iterator();
while(iterator.hasNext()){
String item = iterator.next();
if(item.equals("张三")) {
// 从底层集合中删除此迭代器 返回的最后一个元素
iterator.remove();
}
System.out.println(item);
} - 增强for遍历
a. 增强for是JDK5出现的语法
b. 增强for的底层实现就是一个迭代器
c. 增强for遍历时,也是不能使用集合中的方法对集合进行添加、删除操作(原因:底层是迭代器)
d. 所有的 “单列集合” 以及 “数组” 才可以使用增强for遍历
格式:for(元素类型 变量名 : 集合/数组) {}
例题:1
2
3
4
5
6Collection<String> collection = new ArrayList<>();
collection.add("张三");
collection.add("李四");
for(String item : collection) {
System.out.println(item);
} - lambda
lambda
是为了简化匿名内部类而出现的新语法(JDK8)
例题:
准备数据匿名内部类的方式遍历(1
2
3
4Collection<String> collection = new ArrayList<>();
collection.add("张飞");
collection.add("孙权");
collection.add("诸葛亮");forEach
+ 匿名内部类)1
2
3
4
5
6
7// Consumer:函数式接口
collection.forEach(new Consumer<String>() {
public void accept(String s) {
System.out.println(s);
}
});lambda
方式遍历(lambda
+forEach
)注意:在遍历时,也是不能通过集合中的方法对集合中的元素进行添加、删除操作的1
2
3
4
5
6
7
8
9
10
11
12// forEach + Lambda 遍历集合
collection.forEach((String s) -> {
System.out.println(s);
});
// 简化:如果需要重写的方法只有一个参数,则可以将()去掉
collection.forEach(s -> {
System.out.println(s);
});
// 再次简化:如果方法体中只有一条语句,则可以将{}去掉
collection.forEach(s -> System.out.println(s));
List集合
a.
List
是Collection接口的子接口
b. 特点:有序、有索引、可重复
c.List
集合是有索引的,所以在List接口中声明了一系列操作索引的特有方法
List中的特有方法
1 | void add(int index,E element) 集合指定位置插入元素 |
例题:
1 | List<String> list = new ArrayList<>(); |
remove()的特殊用例(重要):
1 | boolean remove(Object object) 继承自Collection接口 |
1 | List<Integer> list = new ArrayList<>(); |
简单理解:参数是基本数据类型,优先选择remove(int index)
,参数是引用数据类型,优先选择remove(Object object)
List集合特有遍历方式
1 | 1. 列表迭代器 |
列表迭代器遍历
a. ListIterator:列表迭代器,是一个接口,继承Iterator
b. ListIterator接口在继承了Iterator接口中的方法继承上,还自定义了一些特有方法,如:add()
例题:
1 | List<String> list = new ArrayList<>(); |
普通for遍历
1 | List<String> list = new ArrayList<>(); |
ArrayList
- ArrayList是List接口的实现类
- ArrayList的数据结构:数组结构
- ArrayList的底层原理:
a. 利用空参构造创建的集合,在底层创建一个默认长度为0的数组
b. 当添加第一个元素时,底层会创建一个新的长度为10的数组
c. 当数组存满时,会扩容当前数组长度1.5倍(创建长度为1.5倍的新数组,再将原本数组拷贝到新数组中)
d. 依次类推,数组存满就扩容当前数组长度的1.5倍,再将原数组拷贝到新数组中
e. 特殊情况:如果一次添加多个元素,且数组的1.5倍还是放不下,则创建的新数组的长度以实际元素个数为准注意:size():获取集合内元素的个数,并不是底层数组的长度
AraryList
无特殊的API,与List接口中定义的基本一致
LinkedList
- LinkedList是List接口的实现类
- 底层数据结构是双链表,查询慢,增删快,但是如果是操作首尾元素,速度也很快
- LinkedList无特殊的API,基本上都是实现List接口中的方法
LinkedList特有方法
1 | 由于LinkedList操作首尾元素的数据很快,所以在LinkedList中定义了自己特有的API操作首尾元素 |
Set集合
- Set是一个接口,Collection的子接口
- Set集合:无序、不重复、无索引
- Set集合没有特殊的API,基本上都是从Collection继承下来API
- Set集合也没有特殊的遍历方式(迭代器、增强for、lambda)
由于Set集合是不重复的,所以当使用add()
添加元素时,可能会添加失败
1 | add():当集合中存在指定的元素,则添加失败,返回false |
HashSet
- Set接口的实现类
- 无特殊API,基本上与Set中的API一致
- HashSet底层采取 哈希表 存储数据
- 哈希表是一种对于 增删改查 数据性能都比较好的一种结构
- 无序、不重复、无索引
哈希表
- 哈希表的组成:
JDK8之前:数组 + 链表
JDK8开始:数组 + 链表 + 红黑树- 哈希值:
根据hashCode方法算出来的int类型的整数
该方法定义在Object类中,所有对象都可以调用,默认使用地址值进行计算
一般情况下,会重写hashCode方法,利用对象内部的属性值计算哈希值- 对象的哈希值特点:
如果没有重写hashCode方法,不同的对象(地址值)计算出来的哈希值是不同的
如果已经重写hashCode方法,不同的对象只要属性值相同,计算出的哈希值就是一样的
在小部分情况下,不同的属性值或者不同的地址值计算出来的哈希值也有可能一样(哈希碰撞)- 重写hasCode():IDEA中快捷键 alt + insert,然后选择重写hasCode()即可(IDEA会生成重写代码)
HashSet底层原理:
- JDK8以前底层原理:
a. 创建一个默认长度16,默认加载因子为0.75的数组,数组名为tab的数组
加载因子:当集合中存入的元素个数达到了 当前数组长度的 0.75倍时,此时数组的长度会扩容到当前数组的两倍
如:当前数组长度为:16,加载因子为:0.75
当集合中元素个数达到了 16 * 0.75 = 12 个时,数组扩容
现在数组长度:16 * 2 = 32
b. 根据元素的 哈希值 和 数组的长度 计算出该元素应存入的位置
c. 判断当前位置是否为null
如果是null直接存入
如果位置不为null,表示该位置有元素了,则调用equals方法比较属性值
一样:不存入
不一样:存入数组,形成链表
JDK8以前:新元素存入数组,老元素挂在新元素的下面
JDK8以后,新元素直接挂在老元素下面
当链表长度大于8,且数组长度大于等于64时,此时大于8的链表就会转成 红黑数 数据结构
注意:如果集合存储的是自定义对象,则自定义类中 要重写 hashCode 和 equals方法
原因:
- HashSet集合在存储数据时,需要用hashCode()计算哈希值,如果没有重写hashCode(),则hashCode()根据地址值计算哈希值,无法根据对象的属性值计算哈希值
- HashSet集合在存储数据时,需要使用equals()方法判断数组内的元素是否和当前存入元素是否相等,如果没有重写equals(),则根据地址值判断,重写了则是堆内存中的具体值比较
LinkedHashSet
特点:有序、不重复、无索引
底层结构:哈希表(与HashSet底层基本一致,区别:每个元素又额外的多了一个双链表的机制记录存储的顺序)
TreeSet
特点:可排序、不重复、无索引
- 底层数据结构:红黑树
- Java中,预定义类(String、Double、Integer等等)都是有实现
Comparable
接口的- 默认排序规则:
a.数值型:默认按照从小到大的顺序排序
b.字符、字符串:按照字符在ASCII表中的数字升序排序- 指定排序规则
方式一:JavaBean类实现Comparable接口指定的比较规则
方式二:创建TreeSet对象的时候,传递比较器Comparator指定规则
注意:当类中实现了Comparable接口指定排序规则了,此时创建TreeSet对象时,又传入了Comparator指定排序规则,此时会优先使用创建对象时,传入的比较规则
注意:Comparable与Comparator都是比较器:可以查阅资料,了解它们的区别
例题:
当集合存储的数据是自定义类,则自定义类要实现Comparable
接口,指定排序规则
1 | class Person implements Comparable<Person>{ |
TreeSet创建对象时,传入比较器指定比较规则
1 | TreeSet<String> treeSet2 = new TreeSet<>(new Comparator<String>() { |
集合使用场景
- 如果想要集合中的元素 可重复,建议使用ArrayList集合,基于数组的,ArrayList也是使用的最多的集合
- 如果想要集合中的元素 可重复 且 当前的 增删 操作 明显多于 查询操作,用LinkedList集合,基于链表
- 如果想对集合中的元素 去重,用HashSet集合,基于哈希表的,HashSet用的也比较多
- 如果想对集合中的元素去重,而且保证 存取顺序,用LinkedHashSet集合,基于哈希表和双链表,效率低于HashSet
- 如果想对集合中的元素进行 排序,用TreeSet集合,基于红黑树