集合
什么是集合:
集合就是一个容器,它提供了一种存储空间可变的存储模型,存储的容量随着需求动态的分配空间;
数组与集合区别:
- 数组长度不可以发生改变,一经定义长度就固定了;集合的长度可以发生改变,动态的分配存储空间;
- 数组中既可以存储基本数据类型也可以存储引用数据类型;而集合中只能存储引用数据类型;
集合的分类:
Collection集合
概述:
Collection集合是单列集合的顶层接口,其子接口有List、Set;
Collection集合的常用方法:
常用方法 | 方法说明 |
---|---|
boolean add(E e) | 在集合末尾添加元素 |
boolean remove(Object o) | 从集合中删除指定的元素 |
boolean contains(Object o) | 查看集合中是否包含指定的元素 |
int size() | 查看集合中数组的个数 |
补充: (☆☆☆☆☆)
-
判空:判断集合是否为空
-
有两层含义:一个是集合为null,另一层为集合中的元素个数为0;
if(list != null && list.size() > 0)
-
原因:这里的判空运用到了短路逻辑与,首先判断我们的集合是否为空对象,如果是空对象那么就不再去判断集合的长度;如果集合对象不为空,那么就去验证集合中是否存在元素,如果集合长度大于0,则说明集合不为空;
-
为什么要先对集合对象进行判空,而不是先对集合的长度进行判断呢?
答:如果先判断集合的长度是否大于0,假如此时集合对象为空,没有被实例化,那么你通过集合对象去调用size方法就会报空指针异常;
-
迭代器
什么是迭代器:
迭代器是一个接口,是用来实现对集合的遍历;它拥有一个游标,可以指向集合中的下一个元素;
为什么需要它?
答:因为并不是每个Collection集合的子级都有索引,但是集合都需要进行遍历,为了解决这一问题就有了迭代器,每个子级都需要实现迭代器接口以及重写其中的hashNext()
和next()
方法;hashNext()
方法用来判断集合中是否还有元素,next()
是指向下一个元素的指针;
迭代器的使用步骤:
-
获取迭代器对象;
-
判断是否有下一个元素;
-
循环获取每一个元素;
-
代码示例:
// 获取迭代器对象 Iterator<Student> iter = list.iterator(); while(iter.hashNext()) { // 判断是否还有下一个元素,遍历集合 System.out.println(iter.next()); // 将迭代器指针移动到下一个元素 }
并发修改异常: (☆☆☆)
-
产生条件:(二者必须同时满足)
- 并发:至少两个对象在同一时间段对同一个集合进行操作;
- 修改:在并发期间,发生了修改集合元素的操作;
-
代码示例:
// 获取迭代器对象 Iterator<Student> iter = list.iterator(); while(iter.hashNext()) { // 判断是否还有下一个元素,遍历集合 list.add(new Student()); // 抛出ConcurrentModificationException System.out.println(iter.next()); // 将迭代器指针移动到下一个元素 }
-
说明:通过迭代器对集合的遍历使用的是迭代器对象进行操作的,但是在遍历过程中,却用了集合对象对集合元素进行了添加操作,这就符合了并发修改异常的产生条件,在循环这一时间段内,迭代器对象和集合对象同时对集合进行了操作,并且集合对象修改了我们的集合元素,所以抛出异常;
List集合
List集合的概述和特点:
- 概述:List集合是Collection集合的子接口,Collection集合中的方法List都有,除此之外List集合还自己特有的方法,即根据索引来进行操作的方法;
- 特点:List集合是有序的、元素可以重复的集合;
List集合中的特有方法:
方法 | 方法说明 |
---|---|
void add(int index, E element) | 在集合中指定的位置插入给定的元素 |
E remove(int index) | 删除集合中指定索引位置处的元素 |
E set(int index,E element) | 将集合中指定索引位置处的元素修改为给定的元素值,并返回被修改的元素 |
E get(int index) | 返回集合中指定索引位置处的元素 |
删除集合中指定的元素: (☆☆☆)
-
借助新集合删除:时间复杂度O(n),空间复杂度O(m)
List<String> list = new ArrayList<>(); list.add("张三"); list.add("李四"); list.add("王五"); list.add("张三"); // 创建新的集合 List<String> result = new ArrayList<>(); for(int i = 0; i < list.size(); i++) { String str = list.get(i); if(!str.equals("张三")) { // 只添加不是张三的元素 result.add(str); } } list = result;
-
在原有集合中删除:时间复杂度O(n),空间复杂度O(1);(常用)
(面试题:如何删除集合中的指定元素,答案:反向遍历);
// 通过反向遍历整个集合来删除 for(int i = list.size() - 1; i >= 0; i--) { String str = list.get(i); if(str.equals("张三")) { list.remove(i); } }
ArrayList集合与LinkedList集合
概述:
ArrayList集合与LinkedList集合是List集合的子集,拥有List中的所有方法;
ArrayList、LinkedList与Vector的区别: (☆☆☆)
ArrayList
底层是用数组实现,增删效率低、查询效率高,线程不安全,效率高;LinkedList
底层是用双向链表实现的,增删效率高、查询效率低,线程不安全,效率高;Vector
底层是用数组实现的,增删效率低、查询效率高,线程安全,效率低;
代码示例:
// LinkedList集合的操作与ArrayList一致,自行实践
public class Demo {
public static void main(String[] args) {
// 创建集合对象
List<String> list = new ArrayList<>();
// 添加元素
for(int i = 0; i < 4; i++) {
list.add("aa" + i);
}
// 插入元素
list.add(1, "cc");
// 打印集合
System.out.println(list);
// 删除指定位置元素
list.remove(0);
System.out.println(list);
// 修改集合元素
list.set(0, "dd");
System.out.println(list);
}
}
运行结果:
[aa0, cc, aa1, aa2, aa3]
[cc, aa1, aa2, aa3]
[dd, aa1, aa2, aa3]
Set集合
概述和特点:
Set集合是Collection结合的子集,Collection中的方法Set集合都有;
特点:
- Set集合中的元素唯一,没有重复元素;
- 元素存储无序;
- 没有索引;
HashSet集合
概述:
和set的特点一样,底层是用哈希表实现的(数组 + 链表);
哈希表的原理: (☆☆☆)
先对key进行操作获取哈希值,再去位桶数组中去比较是否有此哈希值,如果没有直接将该元素值存储在对应的哈希值位置,如果已经有了该哈希值,那么就去该哈希值对应的链表中去比较是否有相同的元素,如果元素值重复就覆盖它,如果没有重复就在链表的末尾加入该元素值;
HashSet元素唯一的原因:
HashSet
底层使用HashMap
来实现的,其唯一性是利用了HashMap
的key键唯一来保证的;即,HashSet
利用HashMap
的key键来存储Set集合中的元素;
其本质是依赖了hashCode
和equals
方法:
先计算其哈希值,然后和位桶数组中的哈希值进行比较,如果没有相同的哈希值,则直接加入到对应的哈希值位置处,如果重复了,与哈希值对应的链表中的元素进行比较,如果没有重复就加入到链表中,如果重复了,就覆盖它;
LinkedHashSet集合
概述和特点:
-
概述:底层用哈希表和链表实现;
-
特点:元素唯一;存取有序;其具备了List集合和Set集合的特点;
-
应用场景: 去重;(☆☆)
代码示例:
public class Demo{
public static void main(String[] args) {
LinkedHashSet<String> list = new LinkedHashSet<>();
list.add("张三");
list.add("李四");
list.add("张三");
list.add("王五");
list.add("赵六");
System.out.println(list);
}
}
运行结果:
[张三, 李四, 王五, 赵六]
TreeSet集合
特点:
- 元素唯一性;
- 元素有序(按照一定规则排序);
- 没有索引;
排序方式: (☆☆☆)
-
自然排序:
-
要求:
- 创建
TreeSet
集合的时候,使用无参构造方法; - 创建
TreeSet
集合时的泛型类必须实现Comparable
接口,重写compareTo()
方法;
- 创建
-
比较器的存储规则:底层是用二叉树实现的;
- 如果添加的是第一个元素,就会作为根节点;
- 从第二个元素开始:
- 根据你给的的
compareTo
方法的返回值来处理:- 正数:放在节点的右边;
- 负数:放在节点的左边;
- 0:覆盖元素;
- 根据你给的的
-
代码示例:
public class Student implements Comparable<Student> { // 类的组成部分省略 @Override public int compareTo(Student s) { // 按照年龄从小到大排序 int num = this.age - s.age; // 年龄相同时按照姓名的字母排序 return num == 0 ? this.name.compareTo(s.name) : num; } } public class Demo02{ public static void main(String[] args) { TreeSet<Student> set = new TreeSet<>(); Student s1 = new Student("张三", 13); Student s2 = new Student("李四", 14); Student s3 = new Student("王五", 13); Student s4 = new Student("赵六", 15); set.add(s1); set.add(s2); set.add(s3); set.add(s4); System.out.println(set); } }
-
-
比较器排序: 实现Comparator接口重写compare方法
一般以内部类的方式实现该接口,也可以通过创建实现类并将实现类对象传入的方式;
public class Demo03 { public static void main(String[] args) { TreeSet<Student> set = new TreeSet<Student>(new Comparator<Student>() { @Override public int compare(Student s1, Student s2) { int num = s1.getAge() - s2.getAge(); return num == 0 ? s1.getName().compareTo(s2.getName()) : num; } }); Student s1 = new Student("张三", 13); Student s2 = new Student("李四", 14); Student s3 = new Student("王五", 13); Student s4 = new Student("赵六", 15); set.add(s1); set.add(s2); set.add(s3); set.add(s4); System.out.println(set); } }
-
两种排序方式如何选择?
答:选择比较器排序,因为如果我们使用了API自动帮我们重写的
compareTo
方法,默认就会按照字典顺序进行排序,如果此时的排序规则并不是我们想要的,那么我们就不能改变其功能,而我们使用比较器排序,无论如何是我们自己定制的规则,可以根据自己的需求来定制我们想要的功能;
Map集合
Map集合的概述:
Map集合底层采用哈希表来实现的,它是双列集合,实现类有HashTable
、HashMap
、TreeMap
;
Map集合的特点:
- 键值对映射关系,一个键对应一个值;
- 键不能重复,允许有唯一一个键为null,值可以重复,值可以有多个为null;
- 元素存取无序;
- map集合中没有迭代器的概念;
常用方法:
方法 | 方法说明 |
---|---|
V put(K key,V value) | 向集合中添加元素 |
V remove(Object key) | 根据key来删除集合元素 |
boolean containsKey(Object key) | 判断是否包含指定的key |
boolean containsValue(Object value) | 判断是否包含指定的value |
int size() | 获取集合的长度 |
V get(Object key) | 根据key来获取value |
Set keySet() | 获取所有key的集合 |
Collection values() | 获取所有的value集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
Map集合的遍历:(☆☆☆☆☆)
-
方式一:
public class Demo04 { public static void main(String[] args) { HashMap<Integer, String> map = new HashMap<>(); map.put(1, "aa"); map.put(2, "bb"); map.put(3, "cc"); map.put(4, "dd"); // 先获取所有的key键,然后遍历key键去获取对应的value Set<Integer> keys = map.keySet(); for (Integer key : keys) { String value = map.get(key); System.out.println(key + " : " + value); } } }
-
方式二:
// 将所有的键值对取出,通过遍历逐一取出
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
- 两种方式的比较:(☆☆☆)
- 方式一:通过获取所有的key键,然后遍历所有的key键利用key键去找到相应的value值,其时间复杂度为O(n²),因为每次用key去获取value的时候,都需要遍历整个集合来进行匹配,直到找到我们所需要的value为止;
- 方式二:将所有的键值对都取出,然后单独取出每一个键值对,时间复杂度为O(n);所以方法二的更高;
HashMap集合
概述:
HashMap是Map接口的直接子类,其底层是用哈希表实现的,其key键唯一不可以重复;
存入HashMap集合中的元素没有顺序,因为其每次存入的位置是根据key键的哈希值来存入的;
HashMap与HashTable的区别:
HashMap与HashTable在使用方法上没有区别;
- HashTable中的方法加了同步锁,线程安全,效率低;HashMap中的方法没有加同步锁,线程不安全,效率高;
- HashTable不允许key和value为null;HashMap中允许key有唯一一个null,value可以有多个null;
代码示例:
public class Demo01 {
public static void main(String[] args) {
// 创建集合对象
HashMap<String, String> map = new HashMap<>();
// 添加元素
map.put("001", "aa");
map.put("002", "bb");
map.put("003", "cc");
map.put("004", "dd");
// 遍历集合元素
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
运行结果:
001 : aa
002 : bb
003 : cc
004 : dd
TreeMap集合
HashMap与TreeMap的区别:
-
HashMap
是基于Hash表的结构,根据键的hashCode
存储数据,TreeMap
是基于红黑二叉树的结构2.
HashMap
是无序的,TreepMap
实现了SortMap<K,V>
接口,对key进行了排序3.
HashMap
允许有空键和空值,TreeMap
不允许有空键和空值
代码示例:
public class Demo02 {
public static void main(String[] args) {
// 创建集合对象
TreeMap<String, String> map = new TreeMap<>();
// 添加元素
map.put("001", "aa");
map.put("002", "bb");
map.put("003", "cc");
map.put("004", "dd");
// 遍历集合元素
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry<String, String> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
}
运行结果:
001 : aa
002 : bb
003 : cc
004 : dd
集合的遍历
List集合的遍历
初始化集合:
class Dmeo01{
public static void main(String[] args) {
List<String> list = new ArrayList<>();
// 添加元素
list.add("aa");
list.add("bb");
list.add("cc");
list.add("dd");
list.add("ee");
}
}
普通for循环遍历:
public static void forEach01() {
for(int i = 0; i < list.size(); i++) {
System.out.print(list.get(i) + "\t");
}
}
迭代器遍历:
public static void forEach02() {
Iterator<String> iter = list.iterator();
while (iter.hasNext()) {
System.out.print(iter.next() + "\t");
}
}
增强for循环遍历: (如果在遍历时不对集合元素进行修改,推荐此种方式遍历List集合)
public static void forEach03() {
for (String str : list) {
System.out.print(str + "\t");
}
}
Set集合的遍历
集合的初始化:
public class Demo02 {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 5; i++) {
set.add("aa" + i);
}
}
}
迭代器遍历:
public static void forEach01() {
Iterator<String> iter = set.iterator();
while (iter.hasNext()) {
System.out.print(iter.next() + "\t");
}
}
增强for循环遍历:
public static void forEach02() {
for (String str : set) {
System.out.print(str + "\t");
}
}
Map集合的遍历
集合的初始化:
public class Demo04 {
public static void main(String[] args) {
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "aa");
map.put(2, "bb");
map.put(3, "cc");
map.put(4, "dd");
}
}
方式一:
public static void forEach01() {
// 先获取所有的key键,然后遍历key键去获取对应的value
Set<Integer> keys = map.keySet();
for (Integer key : keys) {
String value = map.get(key);
System.out.println(key + " : " + value);
}
}
方式二:
public static void forEach02() {
// 将所有的键值对取出,通过遍历逐一取出
Set<Map.Entry<Integer, String>> entries = map.entrySet();
for (Map.Entry<Integer, String> entry : entries) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
Collections工具类
概述:
Collections集合工具类封装了很多种对集合进行操作的方法;类似于对数组进行操作的Arrays工具类;
常用方法:
方法名 | 说明 |
---|---|
public static void sort(List list) | 将指定的列表按升序排序 |
public static void reverse(List<?> list) | 反转指定列表中元素的顺序 |
public static void shuffle(List<?> list) | 使用默认的随机源随机排列指定的列表 |
代码示例:
public class Demo02 {
public static void main(String[] args) {
// 创建集合对象
ArrayList<Integer> list = new ArrayList<>();
list.add(13);
list.add(11);
list.add(9);
list.add(20);
list.add(16);
// 将集合中的元素进行升序排列
Collections.sort(list);
System.out.println(list);
//将排序后的集合反转
Collections.reverse(list);
System.out.println(list);
// 将集合中的元素的顺序打乱
Collections.shuffle(list);
System.out.println(list);
}
}