JAVA之集合
JAVA之集合
集合概述
集合(Collection)在计算机科学中指的是一组元素的整体,用于处理一组相关的数据。Java 的集合框架提供了多种类型的集合类和接口,以便于存储和操作数据。 ### 1. 集合的定义
集合是由若干个确定的元素构成的整体。例如,数学中的集合包括有限集合(如班级所有同学)、无限集合(如自然数集合)等。
2. Java 集合框架
Java 的 java.util 包提供了丰富的集合类和接口,包括:
- Collection 接口:所有集合类的根接口,不包括
Map。 - List 接口:有序集合,允许重复元素。常用实现类:
ArrayList:动态数组实现,支持快速随机访问。LinkedList:链表实现,支持高效的插入和删除操作。
- Set 接口:无序集合,不允许重复元素。常用实现类:
HashSet:基于哈希表的实现,不保证顺序。LinkedHashSet:基于链表和哈希表,维护插入顺序。TreeSet:基于红黑树实现,按自然顺序或自定义顺序排序。
- Map 接口:键值对映射,不是 Collection
的子接口。常用实现类:
HashMap:基于哈希表,允许 null 键和 null 值。LinkedHashMap:维护插入顺序的哈希表实现。TreeMap:基于红黑树实现,按键的自然顺序或自定义顺序排序。
3. 集合的特性
- 动态大小:大多数集合类(如
ArrayList)可以动态调整大小,而数组的大小一旦初始化后不可变。 - 迭代器:集合元素的访问通过迭代器(Iterator)实现,提供统一的遍历方式,不依赖于具体的存储实现。
4. 遗留类和接口
- 遗留类:
Hashtable:线程安全的Map实现,已被HashMap取代。Vector:线程安全的List实现,已被ArrayList取代。Stack:基于Vector的栈实现,现代开发中通常使用Deque作为栈。
- 遗留接口:
Enumeration:已被Iterator取代,提供集合的遍历功能。
5. 泛型支持
Java
集合类支持泛型,使得集合只能存储指定类型的元素,提供类型安全性。例如,List<String> list = new ArrayList<>();
只能存储 String 类型的元素。
List
Java List 接口详解
List 是 Java
集合框架中的一个接口,代表有序集合,允许重复元素,并通过索引访问元素。List
的常见实现包括 ArrayList 和
LinkedList,每种实现都有其特定的用途和性能特点。
1. ArrayList 示例
`ArrayList 是 Java
集合框架中的一个重要类,提供了动态数组的实现方式,适合需要频繁访问元素的场景。下面是一些
ArrayList 的特点和使用方法的详细介绍:
ArrayList 的特点
动态大小:
ArrayList的容量会自动增长,以适应添加的元素。这是通过内部数组的扩展来实现的。快速随机访问: 支持通过索引直接访问元素,时间复杂度为 (O(1))。这使得
ArrayList非常适合需要快速访问的场景。插入和删除: 在列表中间插入和删除元素的操作较慢,因为这些操作可能涉及到移动其他元素以保持顺序。插入或删除操作的时间复杂度为 (O(n)),其中 (n) 是元素的数量。
线程不安全:
ArrayList不是线程安全的。如果多个线程同时访问一个ArrayList,并且至少有一个线程修改了列表结构,则必须在外部进行同步处理。允许
null元素:ArrayList可以存储null元素。
详细示例
创建和操作 ArrayList
1 | import java.util.ArrayList; |
ArrayList 方法简介
add(E e): 在列表的末尾添加元素e。add(int index, E element): 在指定位置index插入元素element。get(int index): 返回指定位置index的元素。remove(Object o): 移除首次出现的元素o。remove(int index): 移除指定位置index的元素。subList(int fromIndex, int toIndex): 返回列表中从fromIndex到toIndex(不包括toIndex)的子列表。size(): 返回列表中元素的数量。clear(): 移除列表中的所有元素。
性能考虑
虽然 ArrayList
提供了快速的随机访问,但在以下场景中,LinkedList
可能是更好的选择:
- 频繁的插入和删除操作:
如果需要在列表中间频繁插入或删除元素,
LinkedList的性能通常比ArrayList更好,因为LinkedList不需要移动其他元素。
ArrayList
是一个灵活且高效的实现,用于存储和访问动态数组中的元素。了解它的特点和使用方法有助于在开发中选择合适的数据结构,以实现最佳性能。
2. LinkedList 示例
LinkedList 是 Java
集合框架中的另一个重要类,基于双向链表实现,适用于频繁进行插入和删除操作的场景。下面是
LinkedList 的详细特点和使用示例:
LinkedList 的特点
- 插入和删除操作高效:
- 在链表的任意位置插入或删除元素时,只需要调整节点的引用。因此,插入和删除操作的时间复杂度为
(O(1)),当在已知位置进行时。这使得
LinkedList在这些操作中比ArrayList更高效,尤其是当操作发生在链表的中间部分时。
- 在链表的任意位置插入或删除元素时,只需要调整节点的引用。因此,插入和删除操作的时间复杂度为
(O(1)),当在已知位置进行时。这使得
- 较慢的随机访问:
- 由于
LinkedList是基于链表结构的,访问特定索引处的元素需要从链表的头部开始遍历,因此随机访问操作的时间复杂度为 (O(n)),其中 (n) 是链表的长度。这使得在需要频繁访问元素的场景中,LinkedList不如ArrayList高效。
- 由于
- 双向链表:
LinkedList使用双向链表实现,这意味着每个节点都持有对前一个和后一个节点的引用。这使得在链表的两端进行操作更加高效,比如addFirst和addLast操作都能在常数时间内完成。
- 线程不安全:
LinkedList不是线程安全的。如果需要在多线程环境下使用LinkedList,则需要在外部进行同步处理。
- 支持队列和双端队列操作:
LinkedList实现了Queue和Deque接口,因此可以用作队列或双端队列,支持更丰富的操作,比如offer、poll、peek等方法。
详细示例
创建和操作 LinkedList
1 | import java.util.LinkedList; |
LinkedList 方法简介
add(E e): 在列表的末尾添加元素e。add(int index, E element): 在指定位置index插入元素element。get(int index): 返回指定位置index的元素。remove(Object o): 移除首次出现的元素o。remove(int index): 移除指定位置index的元素。addFirst(E e): 在双端队列的头部添加元素e。addLast(E e): 在双端队列的尾部添加元素e。getFirst(): 返回双端队列头部的元素。getLast(): 返回双端队列尾部的元素。offer(E e): 将元素e添加到队列尾部(适用于队列操作)。poll(): 移除并返回队列头部的元素(适用于队列操作)。
性能考虑
- 插入和删除:
LinkedList在插入和删除操作中的性能较好,尤其是在中间位置,因为这些操作只涉及到节点引用的调整,而不需要移动其他元素。 - 访问:
如果频繁需要访问元素,尤其是随机访问,
ArrayList更适合,因为它的访问速度较快。
LinkedList提供了一种高效的链式数据结构,特别适合需要频繁插入和删除的场景。了解其特点和适用场景有助于在编程中选择最适合的数据结构以优化性能。
3. 使用 List 的接口方法
使用 List
接口的方法可以帮助我们高效地操作列表中的数据。下面是 List
接口常用方法的详细说明和示例:
List 接口常用方法
1. add(E e)
- 说明: 将元素
e添加到列表的末尾。 - 时间复杂度: 平均 (O(1))。
- 示例:
1
list.add("Java"); // 添加 "Java" 到列表末尾
2. get(int index)
- 说明: 返回指定位置
index的元素。 - 时间复杂度: (O(1))(随机访问)。
- 示例:
1
String element = list.get(1); // 获取索引为 1 的元素
3.
set(int index, E element)
- 说明: 用指定元素
element替换列表中指定位置index的元素。 - 时间复杂度: (O(1))(随机访问)。
- 示例:
1
list.set(1, "Ruby"); // 替换索引为 1 的元素为 "Ruby"
4. indexOf(Object o)
- 说明: 查找元素
o首次出现的位置。如果元素不存在,返回 -1。 - 时间复杂度: (O(n))(线性搜索)。
- 示例:
1
int index = list.indexOf("Ruby"); // 查找 "Ruby" 的索引
5. contains(Object o)
- 说明: 检查列表是否包含指定元素
o。 - 时间复杂度: (O(n))(线性搜索)。
- 示例:
1
boolean contains = list.contains("Java"); // 检查列表中是否包含 "Java"
6. clear()
- 说明: 移除列表中的所有元素。
- 时间复杂度: (O(n))(遍历列表)。
- 示例:
1
list.clear(); // 清空列表中的所有元素
完整示例
下面是使用这些方法的完整示例,展示了如何创建一个 List
实例,并使用这些方法操作列表:
1 | import java.util.ArrayList; |
方法总结
add(E e): 在列表末尾添加元素。get(int index): 获取指定索引的元素。set(int index, E element): 替换指定位置的元素。indexOf(Object o): 查找元素首次出现的位置。contains(Object o): 检查列表是否包含指定元素。clear(): 移除所有元素。
这些方法使得 List
成为一个非常灵活的数据结构,适用于需要动态大小和高效访问的场景。
4. List
的常见问题及优化
- 性能:
ArrayList适合随机访问,但在中间插入和删除较慢。LinkedList适合频繁插入和删除,但随机访问较慢。
- 线程安全:
ArrayList和LinkedList都不是线程安全的。如果需要线程安全的列表,可以使用Collections.synchronizedList方法来包装它们。
1 | import java.util.ArrayList; |
List 接口在 Java
中提供了有序集合的功能。ArrayList 和
LinkedList
是最常用的实现类,各自有不同的适用场景和性能特征。了解这些实现的特点和使用方法,有助于在合适的场景中选择正确的集合类型。
一些题目训练:
题目 1: 列表的排序和去重
描述: 给定一个包含重复元素的
ArrayList,对其进行排序,并去除重复的元素。
解决思路: 1. 使用 Set 去除重复元素。 2.
使用 List 的 sort
方法对去重后的列表进行排序。
示例代码:
1 | import java.util.ArrayList; |
题目 2: 使用
LinkedList 实现队列
描述: 使用 LinkedList
实现一个简单的队列,支持 enqueue 和 dequeue
操作。
解决思路: 1. 使用 LinkedList 的
addLast 方法进行入队操作。 2. 使用 LinkedList
的 removeFirst 方法进行出队操作。
示例代码:
1 | import java.util.LinkedList; |
题目 3: 在指定位置插入元素
描述: 在给定的 ArrayList
中的指定位置插入一个新元素,并打印插入后的列表。
解决思路: 1. 使用 ArrayList 的
add(index, element) 方法在指定位置插入元素。
示例代码:
1 | import java.util.ArrayList; |
题目 4: 从列表中删除特定元素
描述: 给定一个
ArrayList,删除所有特定的元素并打印结果。
解决思路: 1. 使用 ArrayList 的
remove(Object o) 方法删除特定元素。
示例代码:
1 | import java.util.ArrayList; |
题目 5: 获取列表的子列表
描述: 从给定的 ArrayList
中提取指定范围的子列表,并打印子列表。
解决思路: 1. 使用 ArrayList 的
subList(fromIndex, toIndex) 方法获取子列表。
示例代码:
1 | import java.util.ArrayList; |
题目 6: 反转列表
描述: 反转一个 ArrayList
的顺序并打印结果。
解决思路: 1. 使用 Collections.reverse
方法反转列表的顺序。
示例代码:
1 | import java.util.ArrayList; |
equals() 方法的编写指南
在 Java 中,equals() 方法用于比较对象是否相等。正确实现
equals() 方法对于确保集合(如
List、Set)能够正确识别和处理对象非常重要。
equals() 方法的要求
equals() 方法必须满足以下条件:
- 自反性(Reflexive): 对于非
null的x,x.equals(x)必须返回true。 - 对称性(Symmetric): 对于非
null的x和y,如果x.equals(y)为true,则y.equals(x)也必须为true。 - 传递性(Transitive): 对于非
null的x、y和z,如果x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)也必须为true。 - 一致性(Consistent): 对于非
null的x和y,只要x和y的状态不变,则x.equals(y)总是一致地返回true或false。 - 对
null的比较:x.equals(null)永远返回false。
equals() 方法的实现步骤
- 定义相等的逻辑: 确定哪些字段应该用来判断两个对象是否相等。
- 检查类型: 使用
instanceof关键字检查传入的对象是否是当前类的实例。 - 比较字段: 对于引用类型字段,使用
Objects.equals()方法比较;对于基本类型字段,直接使用==比较。
示例实现
以 Person 类为例,假设 Person
类有三个字段:firstName、lastName 和
age。我们可以这样实现 equals() 方法:
1 | import java.util.Objects; |
示例代码
下面是一个使用 Person 类的示例,测试
equals() 方法是否正确:
1 | import java.util.List; |
在这个示例中,虽然 new Person("Bob", "Smith", 20)
是一个新的实例,但由于 equals()
方法被正确实现,contains() 方法能够正确识别并返回
true。
备注
hashCode()方法: 在重写equals()方法时,也应该重写hashCode()方法,以确保相等对象具有相同的哈希码。Objects.equals()方法: 用于简化引用类型字段的比较,同时处理null值的情况。
通过正确实现 equals() 方法,可以确保 List
和其他集合类能够按预期工作,避免出现不必要的错误和问题。
Map
在 Java 中,Map 是一个用于存储键值对(key-value
pairs)的集合。每个键(key)在 Map
中是唯一的,每个键对应一个值(value)。Map
接口的主要实现类包括 HashMap、LinkedHashMap 和
TreeMap。
1. 常用 Map
实现类
HashMap: 基于哈希表实现,提供 O(1) 时间复杂度的插入、删除和查找操作。键和值均允许为null。LinkedHashMap: 继承自HashMap,保持插入顺序或访问顺序。插入和访问的时间复杂度为 O(1)。TreeMap: 基于红黑树实现,按自然顺序或指定的比较器对键进行排序。时间复杂度为 O(log n) 的操作。
2. 基本操作
创建
Map1
2
3Map<String, Integer> map = new HashMap<>();
Map<String, Integer> linkedMap = new LinkedHashMap<>();
Map<String, Integer> treeMap = new TreeMap<>();添加元素
1
2map.put("Apple", 1);
map.put("Banana", 2);访问元素
1
Integer value = map.get("Apple"); // 返回 1
删除元素
1
map.remove("Banana");
检查键或值是否存在
1
2boolean hasKey = map.containsKey("Apple"); // true
boolean hasValue = map.containsValue(2); // false获取键集合、值集合和键值对集合
1
2
3Set<String> keys = map.keySet();
Collection<Integer> values = map.values();
Set<Map.Entry<String, Integer>> entries = map.entrySet();清空
Map1
map.clear();
获取
Map的大小1
int size = map.size();
3. 遍历 Map
使用增强型 for 循环遍历键值对
1
2
3for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}使用
forEach方法1
map.forEach((key, value) -> System.out.println("Key: " + key + ", Value: " + value));
遍历键集合
1
2
3for (String key : map.keySet()) {
System.out.println("Key: " + key + ", Value: " + map.get(key));
}遍历值集合
1
2
3for (Integer value : map.values()) {
System.out.println("Value: " + value);
}
4. 特殊操作
合并值
1
map.merge("Apple", 5, Integer::sum); // 将 "Apple" 的值增加 5
计算或获取值
1
2map.computeIfAbsent("Orange", key -> 3); // 如果 "Orange" 不存在,设置其值为 3
map.computeIfPresent("Apple", (key, value) -> value + 1); // 如果 "Apple" 存在,将其值加 1替换值
1
2map.replace("Apple", 10); // 将 "Apple" 的值替换为 10
map.replace("Apple", 10, 15); // 仅当 "Apple" 的值为 10 时,将其值替换为 15获取或设置默认值
1
Integer value = map.getOrDefault("Apple", 0); // 如果 "Apple" 不存在,返回默认值 0
5. 性能特征
HashMap: 提供 O(1) 平均时间复杂度的操作,但不保证元素的顺序。LinkedHashMap: 提供 O(1) 时间复杂度的操作,并保持元素的插入顺序或访问顺序。TreeMap: 提供 O(log n) 时间复杂度的操作,并保持元素的排序。
示例代码
以下是一个综合示例,展示了 Map
的创建、基本操作和遍历:
1 | import java.util.*; |
hashCode
方法的编写指南
编写 hashCode 方法的指南涉及确保你的
hashCode 实现与 equals 方法兼容,并且符合 Java
的通用约定。
为什么要重写 hashCode
方法
hashCode 方法在 Java
中用于将对象映射到哈希表的桶(bucket)中。它与 equals
方法紧密相关:
- 一致性: 如果两个对象根据
equals方法是相等的,那么它们的hashCode方法必须返回相同的值。 - 性能优化: 哈希表(如
HashMap和HashSet)使用hashCode方法来快速查找、插入和删除元素。一个好的hashCode实现可以减少冲突,提高性能。
编写 hashCode
方法的基本准则
- 一致性: 如果对象的状态没有改变,多次调用
hashCode方法应该返回相同的值。 - 相等性: 如果两个对象通过
equals方法相等,那么它们的hashCode方法返回的值也应该相等。 - 唯一性: 虽然不是必须的,但
hashCode的实现应尽量减少不同对象的哈希值冲突,以提高性能。
hashCode
方法的实现指南
1. 基本实现
使用对象的关键字段生成哈希值。这通常涉及以下步骤:
- 选择字段: 选择那些用来判断对象是否相等的字段(即
equals方法中使用的字段)。 - 计算哈希值: 使用这些字段计算哈希值,通常使用
Objects.hash方法或者直接使用字段的hashCode方法。 - 返回哈希值: 确保
hashCode方法的实现符合一致性原则。
2. 示例实现
以下是一个简单的 Person 类的示例,包括
equals 和 hashCode 方法的实现:
1 | import java.util.Objects; |
3. 使用
Objects.hash
Objects.hash
是一个便利方法,它简化了使用对象字段计算哈希值的过程。例如:
1 |
|
4. 手动实现
hashCode
手动实现 hashCode
方法时,通常需要使用质数进行混合,以减少冲突的可能性。以下是一个简单的手动实现示例:
1 |
|
5. 避免使用可变字段
如果对象的字段是可变的,并且你使用这些字段来计算
hashCode,这可能会导致问题。例如,如果字段的值被修改,则对象的哈希值也会改变,这可能会破坏数据结构的完整性。在这些情况下,最好只使用不可变字段计算
hashCode。
注意事项
- 不应返回固定值:
hashCode方法应返回与对象状态相关的值,而不是固定值或常量。 - 一致性: 确保
hashCode方法与equals方法一致。两个相等的对象必须具有相同的哈希码。
EnumMap详解
EnumMap 是 Java Collections Framework 中的一种特殊类型的
Map,用于将枚举类型的枚举常量作为键。它具有一些特定的特点和用途。
1. EnumMap
的特点
- 键类型:
EnumMap只接受枚举类型作为键。 - 高效性:
EnumMap由于内部实现基于数组,操作效率比一般的HashMap更高。 - 有序性:
EnumMap按照枚举常量的自然顺序来存储键值对。 - null 键:
EnumMap不允许null作为键,如果尝试使用null键将抛出NullPointerException。 - 实现: 它是
AbstractMap的子类,并实现了Serializable接口。
2. 创建和初始化
EnumMap
要创建一个
EnumMap,首先需要定义一个枚举类型,然后使用该枚举类型作为
EnumMap 的键类型。可以通过不同的构造函数初始化
EnumMap。
示例代码
1 | import java.util.EnumMap; |
3. EnumMap
的操作方法
put(K key, V value): 插入或更新指定键的值。get(Object key): 返回指定键的值。remove(Object key): 删除指定键的映射。containsKey(Object key): 检查EnumMap是否包含指定键。containsValue(Object value): 检查EnumMap是否包含指定值。size(): 返回EnumMap中键值对的数量。clear(): 清空EnumMap中的所有键值对。keySet(): 返回EnumMap中所有键的Set视图。values(): 返回EnumMap中所有值的Collection视图。entrySet(): 返回EnumMap中所有键值对的Set视图。
4. EnumMap
的优势和使用场景
- 高效存储:
EnumMap提供了高效的键值对存储,尤其适合枚举类型作为键的场景。 - 可读性和维护性: 使用枚举作为键可以使代码更具可读性和维护性,避免使用整数或字符串表示枚举常量。
- 应用场景: 常用于需要映射枚举常量到某些值的场景,例如配置管理、状态机实现等。
5. 注意事项
- 键的类型:
EnumMap只能使用枚举类型作为键。 - 线程安全:
EnumMap不是线程安全的。如果在多线程环境中使用,需要额外的同步措施。
TreeMap
TreeMap 是 Java Collections Framework 中的一个实现
NavigableMap
接口的类,它基于红黑树(自平衡的二叉搜索树)实现。TreeMap
具有按自然顺序或自定义顺序对键进行排序的能力。
1. TreeMap
的特点
- 排序:
TreeMap按照键的自然顺序或由构造函数提供的Comparator排序键。 - 红黑树: 内部基于红黑树实现,这是一种自平衡的二叉搜索树,保证了操作的对数时间复杂度。
- 不允许
null键:TreeMap不允许null键,但允许null值。尝试插入null键会抛出NullPointerException。 - 线程安全:
TreeMap不是线程安全的。如果在多线程环境中使用,可能需要额外的同步措施。 - 性能: 操作如插入、删除、查找、遍历的时间复杂度为 (O(n))。
2. 内部实现
- 红黑树:
TreeMap使用红黑树的数据结构进行实现。红黑树是一种自平衡的二叉搜索树,通过对节点的颜色标记(红色或黑色)来维护平衡,确保所有基本操作的时间复杂度为 (O(n))。 - 节点结构: 每个节点包含键值对、左子节点、右子节点和父节点的引用以及颜色信息。
3. 常用操作方法
插入和更新
put(K key, V value): 将键值对插入到TreeMap中。如果键已经存在,则更新其对应的值。putAll(Map<? extends K, ? extends V> m): 将指定映射的所有键值对插入到TreeMap中。
查找
get(Object key): 返回指定键的值。如果键不存在,则返回null。containsKey(Object key): 检查TreeMap是否包含指定的键。containsValue(Object value): 检查TreeMap是否包含指定的值。
删除
remove(Object key): 删除指定键的映射。clear(): 清空TreeMap中的所有键值对。
遍历
keySet(): 返回TreeMap中所有键的Set视图,按升序排列。values(): 返回TreeMap中所有值的Collection视图,按照键的顺序排列。entrySet(): 返回TreeMap中所有键值对的Set视图,按键的自然顺序排列。
导航操作
firstKey(): 返回TreeMap中的第一个键。lastKey(): 返回TreeMap中的最后一个键。ceilingKey(K key): 返回大于或等于指定键的最小键。如果不存在,则返回null。floorKey(K key): 返回小于或等于指定键的最大键。如果不存在,则返回null。higherKey(K key): 返回大于指定键的最小键。如果不存在,则返回null。lowerKey(K key): 返回小于指定键的最大键。如果不存在,则返回null。subMap(K fromKey, K toKey): 返回TreeMap中指定范围的视图。headMap(K toKey): 返回TreeMap中小于指定键的视图。tailMap(K fromKey): 返回TreeMap中大于或等于指定键的视图。
4. 示例代码
1 | import java.util.TreeMap; |
5. 注意事项
- 键的比较:
TreeMap依赖键的比较操作。如果使用自然顺序,需要确保键实现了Comparable接口。如果使用自定义排序,需要提供一个Comparator。 - 性能:
TreeMap的性能通常优于HashMap,尤其在需要按排序顺序访问元素时。因为TreeMap保持元素的排序,而HashMap不提供任何顺序保证。 - 线程安全:
TreeMap不是线程安全的。在多线程环境下使用时需要额外的同步措施。
实际应用例子
TreeMap
是一个非常有用的数据结构,在很多实际应用中都可以发挥作用。
1. 排名系统
假设我们在开发一个游戏或考试系统,需要根据成绩对玩家或学生进行排名。TreeMap
可以用来维护玩家或学生的排名列表,其中键为分数,值为玩家或学生的姓名。
1 | import java.util.TreeMap; |
2. 订单管理
在电商系统中,可以使用 TreeMap
来管理客户订单,按订单日期排序,便于进行时间范围内的订单查询。
1 | import java.util.TreeMap; |
3. 任务调度
在任务调度系统中,可以使用 TreeMap
来按任务的到期时间进行排序。这样可以快速查找即将到期的任务,并进行优先级处理。
1 | import java.util.TreeMap; |
4. 会议日程管理
在会议管理系统中,可以使用 TreeMap
来管理会议日程,按会议时间排序,方便查看即将开始的会议和会议时间区间。
1 | import java.util.TreeMap; |
5. 温度记录
在气象监测系统中,可以使用 TreeMap
来记录和分析温度数据,按日期排序,方便查找和处理特定日期的温度记录。
1 | import java.util.TreeMap; |
Set详解
Set 是 Java
集合框架中的一种集合类型,用于存储不重复的元素。
1. Set 接口
Set 是 Java 集合框架中的一个接口,继承自
Collection
接口。它的主要特点是:集合中的元素没有重复,且没有指定的顺序。
常用实现类
HashSet: 基于哈希表的实现,不保证元素的顺序。提供了常数时间的性能用于基本操作(添加、删除、包含),时间复杂度为 (O(1))。LinkedHashSet: 继承自HashSet,在哈希表的基础上使用链表保持元素的插入顺序。提供较好的插入顺序的遍历性能。TreeSet: 基于红黑树的实现,元素按照自然顺序或构造时提供的比较器进行排序。提供对元素的排序操作,时间复杂度为 (O(n))。
2. 常用方法
添加和删除元素
add(E e): 添加元素。如果集合中已经存在该元素,则不会添加,并返回false。remove(Object o): 删除指定元素。如果集合中存在该元素,则删除并返回true,否则返回false。clear(): 清空集合中的所有元素。
查找和检查
contains(Object o): 检查集合是否包含指定的元素。时间复杂度为 (O(1)) 对于HashSet,(O(n)) 对于TreeSet。isEmpty(): 检查集合是否为空。
其他方法
size(): 返回集合中元素的数量。iterator(): 返回集合中元素的迭代器。toArray(): 返回包含集合中所有元素的数组。
3. 应用场景
- 去重: 用于存储唯一元素,如去重操作。
- 集合运算:
用于集合的交集、并集、差集操作。例如:
HashSet可以用于高效的集合运算。 - 排序: 如果需要按顺序存储元素并进行排序,则使用
TreeSet。
4. 性能特点
HashSet:- 时间复杂度:添加、删除、查找操作的平均时间复杂度为 (O(1))。
- 不保证元素的顺序。
LinkedHashSet:- 时间复杂度:添加、删除、查找操作的时间复杂度为 (O(1))。
- 保持元素的插入顺序,适合需要迭代顺序的场景。
TreeSet:- 时间复杂度:添加、删除、查找操作的时间复杂度为 (O(n))。
- 元素按自然顺序或提供的比较器排序,适合需要排序的场景。
5. 示例代码
使用 HashSet
1 | import java.util.HashSet; |
使用 LinkedHashSet
1 | import java.util.LinkedHashSet; |
使用 TreeSet
1 | import java.util.TreeSet; |
6. 注意事项
- 在
HashSet和LinkedHashSet中,元素的顺序是未定义的,而在TreeSet中,元素是有序的。 - 在使用
TreeSet时,如果自定义对象作为元素,需要确保对象实现了Comparable接口,或者在创建TreeSet时提供一个比较器。 Set的操作通常是 (O(1)) 或 (O(n)),但要注意选择合适的实现类以满足性能需求和功能需求。
实际例子
1. HashSet
实际应用示例
场景: 去除重复的元素
示例: 从一组电话号码中去除重复的电话号码
1 | import java.util.HashSet; |
输出: 1
Unique Phone Numbers: [555-555-5555, 123-456-7890, 098-765-4321]
解释: HashSet
自动去除了重复的电话号码,并且由于 HashSet
不保证元素的顺序,输出的顺序可能不一致。
2.
LinkedHashSet 实际应用示例
场景: 维护元素的插入顺序
示例: 记录用户访问的网页URL,并保持访问顺序
1 | import java.util.LinkedHashSet; |
输出: 1
Visited URLs: [http://example.com/home, http://example.com/about, http://example.com/contact]
解释: LinkedHashSet
保持了插入元素的顺序,适用于需要按访问顺序记录信息的场景。
3. TreeSet
实际应用示例
场景: 按自然顺序排序元素
示例: 对一组学生的考试分数进行排序
1 | import java.util.TreeSet; |
输出: 1
Sorted Exam Scores: [75, 85, 88, 92]
解释: TreeSet
对分数进行了自然顺序的排序,适用于需要排序的数据结构,如考试分数、排名等。
Queue
Queue 是一种线性数据结构,遵循先进先出(FIFO,First In
First
Out)原则。即,队列中的元素是按照插入顺序排列的,且只有队首的元素可以被访问和移除。
主要操作
offer(E e): 将元素e添加到队列的尾部。如果队列满,则返回false。add(E e): 将元素e添加到队列的尾部。如果队列满,则抛出IllegalStateException。poll(): 移除并返回队列的头部元素。如果队列为空,则返回null。remove(): 移除并返回队列的头部元素。如果队列为空,则抛出NoSuchElementException。peek(): 返回队列的头部元素但不移除。如果队列为空,则返回null。element(): 返回队列的头部元素但不移除。如果队列为空,则抛出NoSuchElementException。
实现类
LinkedList: 实现了Queue接口,基于双向链表。支持所有Queue操作。PriorityQueue: 实现了Queue接口,基于优先级队列,元素根据自然排序或构造时提供的比较器排序。ArrayDeque: 实现了Deque接口(双端队列),支持队列操作,基于动态数组。通常性能优于LinkedList。ConcurrentLinkedQueue: 实现了Queue接口,基于非阻塞算法,适用于并发场景。
实际应用
1. 基于
LinkedList 的 Queue
LinkedList
可以作为队列使用,适用于需要频繁插入和删除操作的场景。
1 | import java.util.LinkedList; |
2. 基于
PriorityQueue 的 Queue
PriorityQueue
可以用于按优先级处理元素,例如任务调度。
1 | import java.util.PriorityQueue; |
3. 基于
ArrayDeque 的 Queue
ArrayDeque
提供了高效的队列操作,适用于需要双端操作的场景。
1 | import java.util.ArrayDeque; |
LinkedList: 适用于需要频繁插入和删除的队列操作。PriorityQueue: 适用于按照优先级处理元素的场景。ArrayDeque: 适用于高效的队列操作,尤其是在需要双端操作的场景下表现优异。ConcurrentLinkedQueue: 适用于多线程环境中的并发队列操作。
PriorityQueue
PriorityQueue 是 Java
中实现优先级队列的数据结构,它遵循特定的优先级顺序处理元素。
PriorityQueue
是一个基于优先级的队列,其中的元素按自然顺序或自定义顺序排列。它不像传统的队列那样仅遵循先进先出的原则,而是根据优先级来决定元素的处理顺序。
主要特性
- 自然排序:
如果没有提供自定义比较器,
PriorityQueue会使用元素的自然顺序(即实现了Comparable接口的类)。 - 自定义排序: 可以通过提供
Comparator实现自定义排序。 - 不允许
null元素:PriorityQueue不允许存储null元素,插入null会抛出NullPointerException。 - 不保证排序:
PriorityQueue保证队首元素是优先级最高的,但不保证其他元素的顺序。
常用方法
add(E e): 添加元素e到队列中。如果队列满,则抛出IllegalStateException。offer(E e): 添加元素e到队列中。如果队列满,则返回false。peek(): 返回队列的头部元素但不移除。如果队列为空,则返回null。poll(): 移除并返回队列的头部元素。如果队列为空,则返回null。remove(Object o): 移除队列中首次出现的指定元素。如果元素不存在,则返回false。comparator(): 返回PriorityQueue使用的比较器。如果没有自定义比较器,返回null。size(): 返回队列中元素的数量。
构造函数
PriorityQueue(): 使用默认的自然顺序。PriorityQueue(Comparator<? super E> comparator): 使用指定的比较器。PriorityQueue(int initialCapacity): 使用指定的初始容量。PriorityQueue(Collection<? extends E> c): 使用指定集合的元素初始化队列。
使用示例
1. 自然排序
如果元素实现了 Comparable
接口,PriorityQueue 将按照元素的自然顺序进行排序。
1 | import java.util.PriorityQueue; |
2. 自定义排序
使用自定义 Comparator 对 PriorityQueue
进行排序。
1 | import java.util.PriorityQueue; |
3. 使用自定义对象
为自定义对象实现 Comparable 接口或提供
Comparator 进行排序。
1 | import java.util.PriorityQueue; |
性能和复杂度
- 插入操作:
O(log n),因为需要维持堆的性质。 - 删除操作:
O(log n),移除队首元素并重排堆。 - 访问队首元素:
O(1),队首元素是优先级最高的元素。
PriorityQueue提供了一个灵活的优先级排序机制,通过自然顺序或自定义比较器来处理元素。- 适用于需要按照优先级处理任务的场景,如任务调度、事件驱动等。
- 由于不支持
null元素,因此在插入时需要注意。PriorityQueue不保证整个队列的顺序,只保证队首元素是优先级最高的。
Deque
Deque(双端队列)是 Java
中提供的接口,允许在队列的两端进行插入和删除操作。Deque
继承自 Queue 接口,并提供了比普通队列更多的操作方法。
Deque 是 "Double Ended Queue"
的缩写,表示一个双端队列。它允许在队列的两端插入和删除元素,这与标准队列(只能从一端插入,一端删除)不同。Deque
接口扩展了 Queue 接口,提供了更多的操作方法。
主要特性
- 双端操作: 可以在队列的头部和尾部进行插入和删除操作。
- 无界或有界: 可以是无界(如
ArrayDeque)或有界(如LinkedList)。 - 线程不安全:
Deque的标准实现(如ArrayDeque和LinkedList)不是线程安全的。如果需要线程安全的版本,可以考虑使用ConcurrentLinkedDeque。
常用方法
Deque
接口提供了许多方法用于在双端进行操作,主要包括:
插入方法
addFirst(E e): 在队列的头部插入元素e。addLast(E e): 在队列的尾部插入元素e。offerFirst(E e): 尝试在队列的头部插入元素e,如果成功返回true,否则返回false。offerLast(E e): 尝试在队列的尾部插入元素e,如果成功返回true,否则返回false。
删除方法
removeFirst(): 移除并返回队列的头部元素。removeLast(): 移除并返回队列的尾部元素。pollFirst(): 移除并返回队列的头部元素,如果队列为空则返回null。pollLast(): 移除并返回队列的尾部元素,如果队列为空则返回null。
访问方法
getFirst(): 返回队列的头部元素但不移除。getLast(): 返回队列的尾部元素但不移除。peekFirst(): 返回队列的头部元素但不移除,如果队列为空则返回null。peekLast(): 返回队列的尾部元素但不移除,如果队列为空则返回null。
实现类
Deque 接口有两个主要的实现类:
ArrayDeque: 基于动态数组实现的双端队列,性能较好,适合用作栈和队列。LinkedList: 基于链表实现的双端队列,提供了双端操作,适合需要频繁插入和删除的场景。
使用示例
1. 使用
ArrayDeque
1 | import java.util.ArrayDeque; |
2. 使用
LinkedList
1 | import java.util.Deque; |
性能和复杂度
- 插入和删除操作:
ArrayDeque:O(1)时间复杂度,对于头部和尾部操作都非常高效。LinkedList:O(1)时间复杂度,对于头部和尾部操作也很高效,但相对而言,ArrayDeque由于其动态数组的特点可能会更好。
- 访问操作:
ArrayDeque:O(1)时间复杂度,访问队列的头部和尾部元素效率高。LinkedList:O(1)时间复杂度,同样高效,但由于其链表结构,内存占用较高。
Deque是一个非常灵活的数据结构,适合需要双端操作的场景。ArrayDeque和LinkedList都是Deque的实现类,各有优缺点。ArrayDeque在性能上可能更具优势,而LinkedList提供了更全面的链表操作。Deque的多样操作使其适合用于实现栈、队列等数据结构的各种变种。
Stack
Stack 是 Java
中提供的一种后进先出(LIFO,Last-In-First-Out)数据结构。它允许在栈的顶端进行元素的添加和移除操作。以下是有关
Stack
的详细笔记,包括其基本概念、方法、实现类、使用示例及实际应用。
Stack
是一种特殊的线性数据结构,其中的元素遵循后进先出原则。最后添加的元素会最先被移除。栈通常用于实现递归、回溯算法、解析表达式等场景。
主要特性
- 后进先出: 最近插入的元素最先被移除。
- 单端操作: 所有的插入和删除操作都在栈的顶端进行。
- 支持动态扩展: 默认实现是基于
Vector类的动态数组,可以根据需要调整大小。
常用方法
Stack 类提供了多种方法用于栈操作,主要包括:
基本操作
push(E e): 将元素e压入栈顶。pop(): 移除并返回栈顶的元素。peek(): 返回栈顶的元素但不移除。isEmpty(): 检查栈是否为空。size(): 返回栈中元素的数量。
示例代码
以下是使用 Stack 的基本示例:
1 | import java.util.Stack; |
实现类
Stack 是 Vector
的子类,因此它的底层实现是基于 Vector
的动态数组。其常见实现包括:
java.util.Stack: 标准的栈实现,基于Vector实现,提供了栈的基本操作。
性能和复杂度
push(E e):O(1)时间复杂度,在栈顶添加元素。pop():O(1)时间复杂度,从栈顶移除元素。peek():O(1)时间复杂度,查看栈顶元素。isEmpty():O(1)时间复杂度,检查栈是否为空。size():O(1)时间复杂度,获取栈的大小。
实际应用
- 递归和回溯: 栈可以用来模拟递归调用,帮助实现深度优先搜索(DFS)等回溯算法。
- 表达式求值: 用于解析和计算表达式,如中缀表达式转后缀表达式,或计算后缀表达式的值。
- 浏览器历史记录: 用于管理用户在网页浏览中的历史记录,用户可以使用“后退”按钮回到之前的页面。
Stack是一个经典的后进先出(LIFO)数据结构,主要用于处理需要后进先出操作的场景。Stack提供了基本的栈操作,如push、pop、peek等,所有操作都在栈顶进行。Stack的底层实现基于Vector,支持动态扩展。- 在现代 Java 编程中,
Deque(如ArrayDeque)也可以用于实现栈,提供了更高效的操作和更少的内存开销。
Iterator
Iterator 是 Java
集合框架中的一个重要接口,提供了一种统一的方法来遍历集合中的元素。它允许开发者通过标准的方式访问集合中的元素,而无需暴露集合的内部结构。
Iterator
是一个用于遍历集合元素的接口,提供了基本的方法来访问集合中的元素。以下是
Iterator 接口的主要方法及其功能:
主要方法
boolean hasNext(): 检查是否还有更多元素可以遍历。如果集合中还有未遍历的元素,返回true;否则,返回false。E next(): 返回集合中的下一个元素。调用此方法之前应确保hasNext()返回true。void remove(): 从集合中移除next()方法返回的最后一个元素。注意,调用remove()方法之前必须调用next()方法,否则会抛出IllegalStateException异常。
使用示例
以下是使用 Iterator 遍历 ArrayList
的示例:
1 | import java.util.ArrayList; |
注意事项
- ConcurrentModificationException:
- 当使用
Iterator遍历集合时,如果在遍历过程中对集合进行了结构性修改(例如,使用集合的add或remove方法),则会抛出ConcurrentModificationException。为了避免这种情况,应该只通过Iterator的remove()方法来删除元素。
- 当使用
- 安全的遍历:
Iterator是线程不安全的,因此在多线程环境中使用时,需要进行适当的同步操作。如果需要线程安全的迭代,可以考虑使用Collections.synchronizedList或CopyOnWriteArrayList等线程安全的集合实现。
- 支持集合接口:
- 所有实现了
Collection接口的集合类都支持Iterator,例如ArrayList、HashSet、LinkedList等。
- 所有实现了
性能
hasNext():O(1)时间复杂度,检查是否还有下一个元素。next():O(1)时间复杂度,获取下一个元素。remove():O(1)时间复杂度,从集合中移除最后一个返回的元素(仅在支持的集合中有效)。
实际应用
- 集合遍历:
Iterator提供了一种通用的方式来遍历集合中的元素,而无需关心集合的具体实现。
- 集合的安全删除:
- 使用
Iterator的remove()方法可以在遍历集合的同时安全地删除元素,而避免ConcurrentModificationException。
- 使用
- 自定义集合类:
- 实现自定义集合类时,可以提供自定义的
Iterator实现,以支持集合的自定义遍历逻辑。
- 实现自定义集合类时,可以提供自定义的
Iterator是一个用于遍历集合的接口,提供了标准化的遍历集合元素的方法。- 主要方法包括
hasNext()、next()和remove(),这些方法允许安全地访问和操作集合中的元素。- 在使用
Iterator时,应注意避免并发修改,并合理使用remove()方法进行元素删除。Iterator使得对集合元素的遍历变得更加灵活和一致,是 Java 集合框架中的重要组成部分。
实际例子:
Iterator 接口在 Java
集合框架中用于统一地遍历集合中的元素。
1. 遍历并处理集合元素
在处理集合中的每个元素时,使用 Iterator
可以保证代码的清晰和一致性。以下示例展示了如何遍历一个 List
并对每个元素执行某些操作:
1 | import java.util.ArrayList; |
2. 在遍历过程中删除特定元素
Iterator 提供的 remove()
方法允许在遍历过程中安全地删除集合中的元素。以下示例演示了如何删除所有长度小于
6 的字符串:
1 | import java.util.HashSet; |
3. 自定义迭代器遍历自定义集合
实现自定义集合类时,可以自定义 Iterator
实现,以支持特定的遍历逻辑。以下示例展示了如何实现一个简单的自定义集合和其迭代器:
1 | import java.util.Iterator; |
4. 使用
Iterator 遍历 Map 的
entrySet
Iterator 也可以用于遍历 Map
的条目(键值对)。以下示例演示了如何遍历 Map 的
entrySet 并打印每个键值对:
1 | import java.util.HashMap; |
5. 遍历 Queue 中的元素
Iterator 也可用于遍历 Queue 的元素,尽管
Queue 接口本身不定义 Iterator,但其实现类如
LinkedList 和 PriorityQueue 都提供了
Iterator。以下示例演示了如何遍历 PriorityQueue
的元素:
1 | import java.util.PriorityQueue; |





