List
在 Java 中,
List
是一个非常重要的容器接口,它继承自Collection
接口,用于存储有序、可重复的元素集合。以下将从基本概念、常用实现类、基本操作、遍历方式以及性能特点等方面详细介绍 Java 的List
容器。基本概念
List
接口代表一个有序的元素序列,用户可以精确控制每个元素的插入位置,并且可以通过索引访问元素。List
允许存储重复的元素,这与Set
接口不同,Set
不允许存储重复元素。常用实现类
Java 提供了多个实现
List
接口的类,常见的有ArrayList
、LinkedList
和Vector
。1.
ArrayList
- 特点:基于动态数组实现,支持随机访问,通过索引访问元素的效率很高,时间复杂度为 \(O(1)\)。但在插入和删除元素时,尤其是在列表中间或开头进行操作,需要移动大量元素,效率较低,时间复杂度为 \(O(n)\)。
- 适用场景:适用于需要频繁随机访问元素,而插入和删除操作较少的场景。
2.
LinkedList
- 特点:基于双向链表实现,插入和删除元素的效率较高,尤其是在列表的开头或结尾进行操作,时间复杂度为 \(O(1)\)。但随机访问元素的效率较低,需要从头或尾开始遍历链表,时间复杂度为 \(O(n)\)。
- 适用场景:适用于需要频繁进行插入和删除操作,而随机访问操作较少的场景。
3.
Vector
- 特点:与
ArrayList
类似,也是基于动态数组实现,但它是线程安全的。由于线程安全机制带来了额外的开销,所以在单线程环境下性能不如ArrayList
。- 适用场景:适用于多线程环境下需要保证线程安全的场景。
Vector
在 Java 中,
Vector
是一个动态数组类,位于java.util
包下。它实现了List
接口,是线程安全的,这意味着多个线程可以同时访问和修改Vector
对象,而不会出现数据不一致的问题。不过,由于其线程安全机制带来了额外的开销,在单线程环境下,通常更推荐使用ArrayList
。以下是关于Vector
的详细使用方法:1. 导入
Vector
类要使用
Vector
类,首先需要在代码中导入它:import java.util.Vector;
2. 创建
Vector
对象可以使用以下几种方式创建
Vector
对象:// 创建一个默认初始容量为 10 的空 Vector Vector
vector1 = new Vector<>(); // 创建一个指定初始容量的空 Vector Vector vector2 = new Vector<>(20); // 创建一个包含指定集合元素的 Vector import java.util.ArrayList; import java.util.List; List list = new ArrayList<>(); list.add(1.1); list.add(2.2); Vector vector3 = new Vector<>(list);
- 复制而非引用:这里是将
list
中的元素复制到vector3
中,而不是将list
的引用赋给vector3
。也就是说,list
和vector3
是两个独立的对象,对其中一个对象的修改不会影响另一个对象。例如,后续对list
进行添加、删除元素等操作,不会改变vector3
中的元素;反之亦然。- 泛型类型匹配:
list
和vector3
的泛型类型必须兼容。在这个例子中,它们都是Double
类型,所以可以正常复制元素。如果类型不匹配,会导致编译错误。3. 添加元素
可以使用
add
方法向Vector
中添加元素:Vector
vector = new Vector<>(); // 在末尾添加元素 vector.add("apple"); vector.add("banana"); // 在指定位置插入元素 vector.add(1, "cherry");
- 首先执行
vector.add("apple");
,此时Vector
中有一个元素["apple"]
。- 接着执行
vector.add("banana");
,Vector
变为["apple", "banana"]
。- 最后执行
vector.add(1, "cherry");
,这会将"cherry"
插入到索引为 1 的位置,原来索引 1 及之后的元素会依次后移。所以最终Vector
中的元素顺序是["apple", "cherry", "banana"]
。- 元素在
Vector
中的存储顺序是由添加元素的顺序决定的- 调用
Collections.sort()
方法:Collections.sort(vector);
会对vector
中的元素进行排序。当比较两个字符串时,会从两个字符串的第一个字符开始,依次比较对应位置字符的 Unicode 编码值。 如果对应位置字符的编码值不同:编码值小的字符所在的字符串排在前面。例如,比较"apple"
和"banana"
,先比较第一个字符'a'
(编码值 97)和'b'
(编码值 98),因为 97 < 98,所以"apple"
排在"banana"
前面。 如果对应位置字符的编码值相同:则继续比较下一个位置的字符,直到找到不同的字符或者其中一个字符串结束。例如,比较"apple"
和"app"
,前三个字符都相同,当比较到第四个字符时,"apple"
有字符'l'
,而"app"
已经结束,此时较短的字符串"app"
排在前面。4. 访问元素
可以使用
get
方法根据索引访问Vector
中的元素:Vector
vector = new Vector<>(); vector.add("apple"); vector.add("banana"); // 获取索引为 0 的元素 String element = vector.get(0); System.out.println(element); 5. 修改元素
可以使用
set
方法修改Vector
中指定索引位置的元素:Vector
vector = new Vector<>(); vector.add("apple"); vector.add("banana"); // 将索引为 1 的元素修改为 "cherry" vector.set(1, "cherry"); System.out.println(vector.get(1)); 6. 删除元素
可以使用
remove
方法删除Vector
中的元素:Vector
vector = new Vector<>(); vector.add("apple"); vector.add("banana"); // 根据索引删除元素 vector.remove(0); // 根据元素值删除元素 vector.remove("banana"); 当
Vector
内部有多个"banana"
元素时,vector.remove("banana");
只会移除该Vector
中首次出现的"banana"
元素。while (vector.remove("banana")) { // 循环继续,直到没有 "banana" 元素可移除 }
7. 获取
Vector
的大小可以使用
size
方法获取Vector
中元素的数量:Vector
vector = new Vector<>(); vector.add("apple"); vector.add("banana"); int size = vector.size(); System.out.println("Vector 的大小为: " + size); 8. 遍历
Vector
可以使用多种方式遍历
Vector
中的元素:使用
for
循环Vector
vector = new Vector<>(); vector.add("apple"); vector.add("banana"); for (int i = 0; i < vector.size(); i++) { System.out.println(vector.get(i)); } 使用增强
for
循环Vector
vector = new Vector<>(); vector.add("apple"); vector.add("banana"); for (String element : vector) { System.out.println(element); } 使用迭代器
import java.util.Iterator; Vector
vector = new Vector<>(); vector.add("apple"); vector.add("banana"); Iterator iterator = vector.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } 迭代器刚创建时并不指向
Vector
中的任何元素,而是处于Vector
第一个元素之前的位置Java迭代器(Iterator)是 Java 集合框架中的一种机制,是一种用于遍历集合(如列表、集合和映射等)的接口。
它提供了一种统一的方式来访问集合中的元素,而不需要了解底层集合的具体实现细节。
Java Iterator(迭代器)不是一个集合,它是一种用于访问集合的方法,可用于迭代 ArrayList 和 HashSet 等集合。
next() - 返回迭代器的下一个元素,并将迭代器的指针移到下一个位置。
hasNext() - 用于判断集合中是否还有下一个元素可以访问。
remove() - 从集合中删除迭代器最后访问的元素(可选操作)。
Iterator
的remove()
方法必须在调用next()
方法之后才能调用,它会移除next()
方法返回的元素。如果在调用remove()
之前没有调用next()
,或者在一次next()
调用后多次调用remove()
,都会抛出IllegalStateException
异常。示例代码
import java.util.Iterator; import java.util.Vector; public class IteratorRemoveExample { public static void main(String[] args) { // 创建一个 Vector 并添加元素 Vector
vector = new Vector<>(); vector.add("apple"); vector.add("banana"); vector.add("cherry"); // 获取迭代器 Iterator iterator = vector.iterator(); // 遍历 Vector 并移除元素 while (iterator.hasNext()) { String element = iterator.next(); if ("banana".equals(element)) { // 移除当前元素 iterator.remove(); } } // 输出移除元素后的 Vector System.out.println("移除元素后的 Vector: " + vector); } } 9. 其他常用方法
isEmpty()
:判断Vector
是否为空。Vector
vector = new Vector<>(); boolean isEmpty = vector.isEmpty(); System.out.println("Vector 是否为空: " + isEmpty);
contains()
:判断Vector
是否包含指定元素Vector
vector = new Vector<>(); vector.add("apple"); boolean contains = vector.contains("apple"); System.out.println("Vector 是否包含 'apple': " + contains);
clear()
:清空Vector
中的所有元素。Vector
vector = new Vector<>(); vector.add("apple"); vector.clear(); System.out.println("Vector 的大小为: " + vector.size()); 示例代码总结
import java.util.Vector; public class VectorExample { public static void main(String[] args) { // 创建一个 Vector Vector
vector = new Vector<>(); // 添加元素 vector.add("apple"); vector.add("banana"); vector.add(1, "cherry"); // 访问元素 System.out.println("第一个元素: " + vector.get(0)); // 修改元素 vector.set(1, "date"); System.out.println("修改后的第二个元素: " + vector.get(1)); // 删除元素 vector.remove(0); System.out.println("删除第一个元素后,Vector 的大小: " + vector.size()); // 遍历元素 System.out.println("遍历 Vector:"); for (String element : vector) { System.out.println(element); } // 其他常用方法 System.out.println("Vector 是否为空: " + vector.isEmpty()); System.out.println("Vector 是否包含 'banana': " + vector.contains("banana")); vector.clear(); System.out.println("清空后,Vector 的大小: " + vector.size()); } }
ArrayList
在 Java 中,
ArrayList
是一个非常常用的容器类,它位于java.util
包下,实现了List
接口,是一种动态数组。以下将从多个方面详细介绍ArrayList
容器。1. 特点
- 动态大小:与传统的数组不同,
ArrayList
的大小是可以动态变化的。当添加元素时,如果内部数组容量不足,它会自动进行扩容。- 有序性:
ArrayList
会维护元素的插入顺序,即元素在ArrayList
中的存储顺序与插入顺序一致。- 允许重复元素:可以在
ArrayList
中存储多个相同的元素。- 支持随机访问:可以通过索引快速访问
ArrayList
中的元素,时间复杂度为 \(O(1)\)。2. 基本使用
2.1 导入类
要使用
ArrayList
,首先需要在代码中导入该类:import java.util.ArrayList;
2.2 创建
ArrayList
对象可以使用以下几种方式创建
ArrayList
对象:// 创建一个默认初始容量的空 ArrayList ArrayList
list1 = new ArrayList<>(); // 创建一个指定初始容量的空 ArrayList ArrayList list2 = new ArrayList<>(20); // 创建一个包含另一个集合元素的 ArrayList import java.util.Arrays; import java.util.List; List collection = Arrays.asList(1.1, 2.2, 3.3); ArrayList list3 = new ArrayList<>(collection);
Arrays.asList()
方法可以接受一个数组作为参数,并返回一个包含数组元素的List
。2.3 添加元素
可以使用
add
方法向ArrayList
中添加元素:ArrayList
list = new ArrayList<>(); // 在末尾添加元素 list.add("apple"); list.add("banana"); // 在指定位置插入元素 list.add(1, "cherry");
"apple"
、"cherry"
、"banana"
2.4 访问元素
可以使用
get
方法根据索引访问ArrayList
中的元素:ArrayList
list = new ArrayList<>(); list.add("apple"); list.add("banana"); // 获取索引为 0 的元素 String element = list.get(0); System.out.println(element); 2.5 修改元素
可以使用
set
方法修改ArrayList
中指定索引位置的元素:ArrayList
list = new ArrayList<>(); list.add("apple"); list.add("banana"); // 将索引为 1 的元素修改为 "cherry" list.set(1, "cherry"); System.out.println(list.get(1)); 2.6 删除元素
可以使用
remove
方法删除ArrayList
中的元素:ArrayList
list = new ArrayList<>(); list.add("apple"); list.add("banana"); // 根据索引删除元素 list.remove(0); // 根据元素值删除元素 list.remove("banana"); 2.7 获取
ArrayList
的大小可以使用
size
方法获取ArrayList
中元素的数量:ArrayList
list = new ArrayList<>(); list.add("apple"); list.add("banana"); int size = list.size(); System.out.println("ArrayList 的大小为: " + size); 3. 遍历
ArrayList
可以使用多种方式遍历
ArrayList
中的元素:3.1 使用
for
循环ArrayList
list = new ArrayList<>(); list.add("apple"); list.add("banana"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } 3.2 使用增强
for
循环ArrayList
list = new ArrayList<>(); list.add("apple"); list.add("banana"); for (String element : list) { System.out.println(element); } 3.3 使用迭代器
import java.util.Iterator; ArrayList
list = new ArrayList<>(); list.add("apple"); list.add("banana"); Iterator iterator = list.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } 4. 常用方法
isEmpty()
:判断ArrayList
是否为空。ArrayList
list = new ArrayList<>(); boolean isEmpty = list.isEmpty(); System.out.println("ArrayList 是否为空: " + isEmpty);
contains()
:判断ArrayList
是否包含指定元素。ArrayList
list = new ArrayList<>(); list.add("apple"); boolean contains = list.contains("apple"); System.out.println("ArrayList 是否包含 'apple': " + contains);
clear()
:清空ArrayList
中的所有元素。ArrayList
list = new ArrayList<>(); list.add("apple"); list.clear(); System.out.println("ArrayList 的大小为: " + list.size()); 5. 扩容机制
ArrayList
内部使用数组来存储元素,当添加元素时,如果数组容量不足,会触发扩容操作。默认情况下,ArrayList
的初始容量为 10,当元素数量达到数组容量时,会创建一个新的数组,新数组的容量为原数组容量的 1.5 倍(oldCapacity + (oldCapacity >> 1)
),然后将原数组中的元素复制到新数组中。6. 性能分析
- 插入和删除操作:在
ArrayList
的末尾插入或删除元素的时间复杂度为 \(O(1)\),但在中间或开头插入或删除元素时,需要移动后续元素,时间复杂度为 \(O(n)\)。- 随机访问操作:通过索引访问元素的时间复杂度为 \(O(1)\),这是
ArrayList
的一个优点。7. 与
Vector
的比较
- 线程安全性:
Vector
是线程安全的,而ArrayList
是非线程安全的。在多线程环境下,如果需要保证线程安全,可以使用Vector
;在单线程环境下,建议使用ArrayList
,因为它的性能更高。- 扩容机制:
Vector
扩容时默认将容量翻倍,而ArrayList
扩容时将容量增加为原来的 1.5 倍。综上所述,
ArrayList
是一个功能强大、使用方便的动态数组容器,适用于大多数需要存储和操作一组元素的场景。
LinkedList
LinkedList
是 Java 集合框架中List
接口的一个实现类,同时也实现了Deque
接口,它基于双向链表的数据结构来存储元素。以下从基本概念、特点、常用操作、性能分析、适用场景等方面详细介绍LinkedList
。基本概念
LinkedList
使用链表来存储元素,每个元素(节点)包含三部分:数据、指向前一个节点的引用和指向后一个节点的引用,这种结构被称为双向链表。这使得LinkedList
在插入和删除元素时具有较高的效率。特点
- 有序性:
LinkedList
会维护元素的插入顺序,即元素在LinkedList
中的存储顺序与插入顺序一致。- 允许重复元素:可以在
LinkedList
中存储多个相同的元素。- 支持 null 元素:
LinkedList
允许存储null
元素。- 双向链表结构:每个节点都包含指向前一个节点和后一个节点的引用,这使得在链表的任意位置进行插入和删除操作都比较高效。
- 非线程安全:在多线程环境下,如果多个线程同时访问和修改
LinkedList
,可能会导致数据不一致的问题。如果需要在多线程环境下使用,可以考虑使用Collections.synchronizedList()
方法将其转换为线程安全的列表。常用操作
1. 创建
LinkedList
对象import java.util.LinkedList; public class LinkedListExample { public static void main(String[] args) { // 创建一个空的 LinkedList LinkedList
linkedList = new LinkedList<>(); } } 2. 添加元素
import java.util.LinkedList; public class LinkedListAddExample { public static void main(String[] args) { LinkedList
linkedList = new LinkedList<>(); // 在末尾添加元素 linkedList.add("apple"); linkedList.add("banana"); // 在指定位置插入元素 linkedList.add(1, "cherry"); // 在头部添加元素 linkedList.addFirst("kiwi"); // 在尾部添加元素 linkedList.addLast("grape"); } }
"kiwi"
、"apple"
、"cherry"
、"banana"
、"grape"
3. 访问元素
import java.util.LinkedList; public class LinkedListAccessExample { public static void main(String[] args) { LinkedList
linkedList = new LinkedList<>(); linkedList.add("apple"); linkedList.add("banana"); // 通过索引访问元素 String element = linkedList.get(1); // 获取第一个元素 String firstElement = linkedList.getFirst(); // 获取最后一个元素 String lastElement = linkedList.getLast(); } } 4. 修改元素
import java.util.LinkedList; public class LinkedListModifyExample { public static void main(String[] args) { LinkedList
linkedList = new LinkedList<>(); linkedList.add("apple"); linkedList.add("banana"); // 修改指定索引位置的元素 linkedList.set(1, "cherry"); } }
"apple"
、"cherry"
5. 删除元素
import java.util.LinkedList; public class LinkedListRemoveExample { public static void main(String[] args) { LinkedList
linkedList = new LinkedList<>(); linkedList.add("apple"); linkedList.add("banana"); // 根据索引删除元素 linkedList.remove(0); // 删除第一个元素 linkedList.removeFirst(); // 删除最后一个元素 linkedList.removeLast(); } } 6. 遍历元素
import java.util.LinkedList; import java.util.Iterator; public class LinkedListTraversalExample { public static void main(String[] args) { LinkedList
linkedList = new LinkedList<>(); linkedList.add("apple"); linkedList.add("banana"); // 使用增强 for 循环遍历 for (String element : linkedList) { System.out.println(element); } // 使用迭代器遍历 Iterator iterator = linkedList.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } } 性能分析
- 插入和删除操作:在链表的头部或尾部插入和删除元素的时间复杂度为 \(O(1)\),因为只需要修改相邻节点的引用。在链表中间插入和删除元素的时间复杂度也为 \(O(1)\),但需要先定位到插入或删除的位置,定位操作的时间复杂度为 \(O(n)\)。
- 随机访问操作:通过索引访问元素的时间复杂度为 \(O(n)\),因为需要从头或尾开始遍历链表,直到找到指定索引的元素。
适用场景
- 频繁的插入和删除操作:由于
LinkedList
在插入和删除元素时具有较高的效率,因此适用于需要频繁进行插入和删除操作的场景,如实现栈、队列等数据结构。- 不频繁的随机访问操作:如果不需要频繁地通过索引访问元素,而是更注重插入和删除操作的性能,那么
LinkedList
是一个不错的选择。
特性 | ArrayList | LinkedList | Vector |
---|---|---|---|
数据结构 | 动态数组 | 双向链表 | 动态数组 |
线程安全性 | 非线程安全 | 非线程安全 | 线程安全 |
随机访问效率 | 高 (\(O(1)\)) | 低 (\(O(n)\)) | 高 (\(O(1)\)) |
插入删除效率 | 尾部快 (\(O(1)\)),其他位置慢 (\(O(n)\)) | 首尾快 (\(O(1)\)),中间定位慢 (\(O(n)\)) | 尾部快 (\(O(1)\)),其他位置慢 (\(O(n)\)) |
内存占用 | 连续内存,可能有空闲 | 每个节点有额外引用,开销大 | 连续内存,可能有空闲 |
Set
在 Java 中,
Set
是一个用于存储唯一元素的容器接口,它继承自Collection
接口。Set
不允许包含重复的元素,也就是说,如果你尝试向Set
中添加已经存在的元素,添加操作会失败。以下将从基本概念、常用实现类、基本操作、遍历方式等方面详细介绍 Java 的Set
容器。基本概念
Set
接口代表一个不包含重复元素的集合,它提供了添加、删除、检查元素是否存在等基本操作。由于Set
是一个接口,不能直接实例化,需要使用它的具体实现类。常用实现类
1.
HashSet
- 特点:基于哈希表实现,不保证元素的顺序,元素的存储顺序可能与插入顺序不同。
HashSet
通过元素的hashCode()
和equals()
方法来判断元素是否重复,因此存储在HashSet
中的元素需要正确重写这两个方法。- 性能:添加、删除和查找元素的时间复杂度为 \(O(1)\),性能较高。
- 适用场景:适用于需要快速查找元素,且不关心元素顺序的场景。
2.
TreeSet
- 特点:基于红黑树实现,会根据元素的自然顺序或指定的比较器对元素进行排序。存储在
TreeSet
中的元素必须实现Comparable
接口,或者在创建TreeSet
时提供一个Comparator
对象。- 性能:添加、删除和查找元素的时间复杂度为 \(O(log n)\)。
- 适用场景:适用于需要对元素进行排序的场景。
3.
LinkedHashSet
- 特点:继承自
HashSet
,基于哈希表和链表实现,它维护了一个插入顺序的链表,因此可以保证元素的插入顺序。- 性能:添加、删除和查找元素的时间复杂度为 \(O(1)\)。
- 适用场景:适用于需要保证元素插入顺序,同时又需要快速查找元素的场景。
HashSet
HashSet
是 Java 集合框架中Set
接口的一个重要实现类,位于java.util
包下。它主要用于存储不重复的元素,并且不保证元素的存储顺序。以下从多个方面详细介绍HashSet
。1. 基本概念
HashSet
基于哈希表(实际上是HashMap
)实现,它使用哈希算法来存储和查找元素。当向HashSet
中添加元素时,会根据元素的哈希码(通过hashCode()
方法计算)来确定元素在哈希表中的存储位置。如果两个元素的哈希码相同,还会使用equals()
方法来进一步判断它们是否相等,如果相等则认为是重复元素,不会重复存储。2. 特点
- 元素唯一性:
HashSet
不允许存储重复的元素。如果尝试添加已经存在于HashSet
中的元素,添加操作将不会生效。- 无序性:
HashSet
不保证元素的存储顺序,元素的存储顺序可能与插入顺序不同,并且每次运行程序时元素的顺序也可能不同。- 允许存储
null
元素:HashSet
允许存储一个null
元素。- 非线程安全:
HashSet
不是线程安全的,如果在多线程环境下使用,可能会出现数据不一致的问题。若需要在多线程环境下使用,可以使用Collections.synchronizedSet()
方法将其转换为线程安全的集合。3. 常用构造方法
HashSet()
:创建一个空的HashSet
,初始容量为 16,负载因子为 0.75。import java.util.HashSet; import java.util.Set; public class HashSetCreation { public static void main(String[] args) { Set
set = new HashSet<>(); } }
HashSet(int initialCapacity)
:创建一个具有指定初始容量的空HashSet
,负载因子为 0.75。import java.util.HashSet; import java.util.Set; public class HashSetWithInitialCapacity { public static void main(String[] args) { Set
set = new HashSet<>(20); } }
HashSet(int initialCapacity, float loadFactor)
:创建一个具有指定初始容量和负载因子的空HashSet
。- 负载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度,它是一个浮点数。在
HashSet
中,内部使用HashMap
来存储元素,负载因子决定了在哈希表的元素数量达到一定比例时,哈希表会进行扩容操作。import java.util.HashSet; import java.util.Set; public class HashSetWithCapacityAndLoadFactor { public static void main(String[] args) { Set
set = new HashSet<>(20, 0.8f); } }
HashSet(Collection extends E> c)
:创建一个包含指定集合中所有元素的HashSet
。import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; public class HashSetFromCollection { public static void main(String[] args) { List
list = new ArrayList<>(); list.add("apple"); list.add("banana"); Set set = new HashSet<>(list); } } 4. 常用方法
add(E e)
:向HashSet
中添加元素,如果元素已经存在,则添加失败并返回false
;否则添加成功并返回true
。import java.util.HashSet; import java.util.Set; public class HashSetAddExample { public static void main(String[] args) { Set
set = new HashSet<>(); boolean result1 = set.add("apple"); boolean result2 = set.add("apple"); System.out.println(result1); System.out.println(result2); } }
remove(Object o)
:从HashSet
中移除指定元素,如果元素存在则移除并返回true
;否则返回false
。import java.util.HashSet; import java.util.Set; public class HashSetRemoveExample { public static void main(String[] args) { Set
set = new HashSet<>(); set.add("apple"); boolean result = set.remove("apple"); System.out.println(result); } }
contains(Object o)
:检查HashSet
中是否包含指定元素,如果包含则返回true
;否则返回false
。import java.util.HashSet; import java.util.Set; public class HashSetContainsExample { public static void main(String[] args) { Set
set = new HashSet<>(); set.add("apple"); boolean result = set.contains("apple"); System.out.println(result); } }
size()
:返回HashSet
中元素的数量。import java.util.HashSet; import java.util.Set; public class HashSetSizeExample { public static void main(String[] args) { Set
set = new HashSet<>(); set.add("apple"); set.add("banana"); int size = set.size(); System.out.println(size); } }
isEmpty()
:检查HashSet
是否为空,如果为空则返回true
;否则返回false
。import java.util.HashSet; import java.util.Set; public class HashSetIsEmptyExample { public static void main(String[] args) { Set
set = new HashSet<>(); boolean result = set.isEmpty(); System.out.println(result); } }
clear()
:清空HashSet
中的所有元素。import java.util.HashSet; import java.util.Set; public class HashSetClearExample { public static void main(String[] args) { Set
set = new HashSet<>(); set.add("apple"); set.clear(); System.out.println(set.size()); } } 5. 遍历方式
- 增强
for
循环:import java.util.HashSet; import java.util.Set; public class HashSetTraversalForEach { public static void main(String[] args) { Set
set = new HashSet<>(); set.add("apple"); set.add("banana"); for (String element : set) { System.out.println(element); } } }
- 迭代器:
import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class HashSetTraversalIterator { public static void main(String[] args) { Set
set = new HashSet<>(); set.add("apple"); set.add("banana"); Iterator iterator = set.iterator(); while (iterator.hasNext()) { System.out.println(iterator.next()); } } } 6. 性能分析
- 添加、删除和查找操作:平均时间复杂度为 \(O(1)\),因为哈希表的特性使得可以通过哈希码快速定位元素的存储位置。但在哈希冲突较多的情况下,性能可能会下降。
- 遍历操作:时间复杂度为 \(O(n)\),其中 n 是
HashSet
中元素的数量。7. 注意事项
- 重写
hashCode()
和equals()
方法:如果要将自定义对象存储在HashSet
中,需要确保该对象正确重写了hashCode()
和equals()
方法,以保证元素的唯一性判断正确。import java.util.HashSet; import java.util.Set; class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && name.equals(person.name); } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + age; return result; } } public class HashSetCustomObject { public static void main(String[] args) { Set
set = new HashSet<>(); Person p1 = new Person("Alice", 20); Person p2 = new Person("Alice", 20); set.add(p1); set.add(p2); System.out.println(set.size()); } }
hashCode()
方法:该方法是Object
类中的一个实例方法,所有 Java 类都继承了这个方法。hashCode()
方法返回一个对象的哈希码,这是一个整数,用于在哈希表中快速定位对象。在HashSet
中,hashCode()
方法用于确定元素应该存储在哈希表的哪个 “桶(bucket)” 中。equals()
方法:同样是Object
类中的实例方法,用于比较两个对象是否相等。默认情况下,equals()
方法比较的是两个对象的引用是否相等,即是否指向同一个内存地址。但在很多情况下,我们需要根据对象的内容来判断它们是否相等,因此需要重写equals()
方法。综上所述,
HashSet
是一个高效的集合类,适用于需要存储不重复元素且对元素顺序没有要求的场景。
HashMap
HashMap
是 Java 中非常常用的一个数据结构,它位于java.util
包下,实现了Map
接口,用于存储键值对(key - value)。以下从多个方面详细介绍HashMap
。1. 基本概念
HashMap
基于哈希表实现,它通过键的哈希码(hashCode
)来确定键值对的存储位置,从而实现快速的查找、插入和删除操作。HashMap
不保证键值对的顺序,并且允许存储null
键和null
值,但null
键只能有一个。2. 数据结构
在 Java 8 之前,
HashMap
采用数组 + 链表的结构。数组中的每个元素称为一个桶(bucket),当发生哈希冲突(即不同的键计算出相同的哈希码)时,会将冲突的键值对以链表的形式存储在同一个桶中。从 Java 8 开始,为了提高在哈希冲突严重时的性能,
HashMap
采用数组 + 链表 + 红黑树的结构。当链表长度达到一定阈值(默认为 8)且数组长度达到 64 时,链表会转换为红黑树;当红黑树的节点数减少到一定阈值(默认为 6)时,红黑树会转换回链表。3. 常用构造方法
HashMap()
:创建一个默认初始容量为 16,负载因子为 0.75 的空HashMap
。import java.util.HashMap; import java.util.Map; public class HashMapCreation { public static void main(String[] args) { Map
map = new HashMap<>(); } }
HashMap(int initialCapacity)
:创建一个具有指定初始容量,负载因子为 0.75 的空HashMap
。import java.util.HashMap; import java.util.Map; public class HashMapWithInitialCapacity { public static void main(String[] args) { Map
map = new HashMap<>(32); } }
HashMap(int initialCapacity, float loadFactor)
:创建一个具有指定初始容量和负载因子的空HashMap
。import java.util.HashMap; import java.util.Map; public class HashMapWithCapacityAndLoadFactor { public static void main(String[] args) { Map
map = new HashMap<>(32, 0.8f); } }
HashMap(Map extends K,? extends V> m)
:创建一个包含指定Map
中所有键值对的HashMap
。import java.util.HashMap; import java.util.Map; public class HashMapFromAnotherMap { public static void main(String[] args) { Map
originalMap = new HashMap<>(); originalMap.put("apple", 1); originalMap.put("banana", 2); Map newMap = new HashMap<>(originalMap); } } 4. 常用方法
put(K key, V value)
:将指定的键值对插入到HashMap
中。如果键已经存在,则会更新该键对应的值。import java.util.HashMap; import java.util.Map; public class HashMapPutExample { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); map.put("apple", 3); } }
get(Object key)
:根据指定的键获取对应的值。如果键不存在,则返回null
。import java.util.HashMap; import java.util.Map; public class HashMapGetExample { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); Integer value = map.get("apple"); } }
remove(Object key)
:根据指定的键移除对应的键值对,并返回该键对应的值。如果键不存在,则返回null
。import java.util.HashMap; import java.util.Map; public class HashMapRemoveExample { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); Integer removedValue = map.remove("apple"); } }
containsKey(Object key)
:检查HashMap
中是否包含指定的键。如果包含则返回true
,否则返回false
。import java.util.HashMap; import java.util.Map; public class HashMapContainsKeyExample { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); boolean containsKey = map.containsKey("apple"); } }
containsValue(Object value)
:检查HashMap
中是否包含指定的值。如果包含则返回true
,否则返回false
。import java.util.HashMap; import java.util.Map; public class HashMapContainsValueExample { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); boolean containsValue = map.containsValue(1); } }
size()
:返回HashMap
中键值对的数量。import java.util.HashMap; import java.util.Map; public class HashMapSizeExample { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); int size = map.size(); } }
clear()
:清空HashMap
中的所有键值对。import java.util.HashMap; import java.util.Map; public class HashMapClearExample { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); map.clear(); } } 5. 遍历方式
- 遍历键集:
map.keySet()该方法返回一个包含
Map
中所有键的Set
集合。import java.util.HashMap; import java.util.Map; public class HashMapKeySetTraversal { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); for (String key : map.keySet()) { System.out.println("Key: " + key + ", Value: " + map.get(key)); } } }
- 遍历值集:
map.values()获取值的集合
import java.util.HashMap; import java.util.Map; public class HashMapValuesTraversal { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); for (Integer value : map.values()) { System.out.println("Value: " + value); } } }
- 遍历键值对:
import java.util.HashMap; import java.util.Map; public class HashMapEntrySetTraversal { public static void main(String[] args) { Map
map = new HashMap<>(); map.put("apple", 1); map.put("banana", 2); for (Map.Entry entry : map.entrySet()) { System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue()); } } } 1.
map.entrySet()
方法
map
是一个Map
类型的对象,例如HashMap
、TreeMap
等实现了Map
接口的类的实例。
entrySet()
是Map
接口中定义的一个方法,它的作用是返回一个包含Map
中所有键值对的Set
集合。集合中的每个元素都是一个Map.Entry
对象,Map.Entry
是Map
接口的一个内部接口,它表示Map
中的一个键值对。2.
Map.Entry
Map.Entry
是一个泛型接口,这里使用了泛型参数,表示
Map
中的键是String
类型,值是Integer
类型。通过指定泛型参数,可以确保在后续操作中使用正确的数据类型,避免类型转换错误。
3. 增强
for
循环
增强
for
循环(也称为foreach
循环)是 Java 5 引入的一种简化循环结构,用于遍历数组或集合。语法结构为:
for (元素类型 元素变量 : 集合或数组) { 循环体 }
。在这段代码中,
Map.Entry
定义了一个entry Map.Entry
类型的变量entry
,用于存储map.entrySet()
返回的集合中的每个元素(即每个键值对)。4.
entry.getKey()
和entry.getValue()
方法
entry.getKey()
是Map.Entry
接口中定义的方法,用于获取当前键值对的键。
entry.getValue()
也是Map.Entry
接口中定义的方法,用于获取当前键值对的值。6. 负载因子和扩容机制
- 负载因子(Load Factor):是哈希表在其容量自动增加之前可以达到多满的一种尺度,默认值为 0.75。当
HashMap
中键值对的数量达到 “容量 * 负载因子” 时,会触发扩容操作。- 扩容机制:当需要扩容时,
HashMap
会创建一个新的数组,其容量为原数组的两倍,然后将原数组中的所有键值对重新计算哈希码并插入到新数组中。7. 线程安全性
HashMap
是非线程安全的,如果在多线程环境下使用,可能会出现数据不一致的问题。如果需要在多线程环境下使用,可以使用ConcurrentHashMap
或者通过Collections.synchronizedMap()
方法将HashMap
转换为线程安全的Map
。8. 注意事项
- 键的
hashCode()
和equals()
方法:存储在HashMap
中的键需要正确重写hashCode()
和equals()
方法,以确保键的唯一性和哈希表的正常工作。- 性能考虑:在使用
HashMap
时,需要根据实际情况选择合适的初始容量和负载因子,以平衡内存使用和性能。综上所述,
HashMap
是一个功能强大、使用广泛的数据结构,适用于需要快速查找、插入和删除键值对的场景。
队列(Queue)
基本概念
队列是一种遵循先进先出(FIFO,First-In-First-Out)原则的数据结构,即最先进入队列的元素最先被移除。
LinkedList
类:LinkedList
是 Java 集合框架中的一个具体类,同样位于java.util
包下。它实现了多个接口,其中就包括Queue
接口,同时还实现了List
、Deque
等接口。这意味着LinkedList
不仅可以作为链表使用,还能当作队列、双端队列来使用,因为它提供了这些接口所定义方法的具体实现插入元素
add(E e)
- 功能:将指定元素插入队列。如果队列已满或由于其他原因无法插入元素,会抛出
IllegalStateException
异常。- 返回值:如果插入成功,返回
true
。- 示例代码
import java.util.LinkedList; import java.util.Queue; public class QueueAddExample { public static void main(String[] args) { Queue
queue = new LinkedList<>(); boolean result = queue.add("apple"); System.out.println("插入是否成功: " + result); } }
offer(E e)
- 功能:将指定元素插入队列。如果队列已满或由于其他原因无法插入元素,返回
false
。- 返回值:插入成功返回
true
,否则返回false
。- 示例代码
import java.util.LinkedList; import java.util.Queue; public class QueueOfferExample { public static void main(String[] args) { Queue
queue = new LinkedList<>(); boolean result = queue.offer("apple"); System.out.println("插入是否成功: " + result); } } 移除元素
remove()
- 功能:移除并返回队列的头部元素。如果队列为空,会抛出
NoSuchElementException
异常。- 返回值:队列的头部元素。
- 示例代码
import java.util.LinkedList; import java.util.Queue; public class QueueRemoveExample { public static void main(String[] args) { Queue
queue = new LinkedList<>(); queue.add("apple"); String removedElement = queue.remove(); System.out.println("移除的元素: " + removedElement); } }
poll()
- 功能:移除并返回队列的头部元素。如果队列为空,返回
null
。- 返回值:队列的头部元素,如果队列为空则返回
null
。- 示例代码
import java.util.LinkedList; import java.util.Queue; public class QueuePollExample { public static void main(String[] args) { Queue
queue = new LinkedList<>(); String removedElement = queue.poll(); System.out.println("移除的元素: " + removedElement); } } 查看头部元素
element()
- 功能:返回队列的头部元素,但不移除它。如果队列为空,会抛出
NoSuchElementException
异常。- 返回值:队列的头部元素。
- 示例代码
import java.util.LinkedList; import java.util.Queue; public class QueueElementExample { public static void main(String[] args) { Queue
queue = new LinkedList<>(); queue.add("apple"); String headElement = queue.element(); System.out.println("队列头部元素: " + headElement); } }
peek()
- 功能:返回队列的头部元素,但不移除它。如果队列为空,返回
null
。- 返回值:队列的头部元素,如果队列为空则返回
null
。- 示例代码
import java.util.LinkedList; import java.util.Queue; public class QueuePeekExample { public static void main(String[] args) { Queue
queue = new LinkedList<>(); String headElement = queue.peek(); System.out.println("队列头部元素: " + headElement); } } Java 中的队列接口和实现类
1.
Queue
接口
Queue
是 Java 集合框架中定义队列的接口,它继承自Collection
接口,定义了队列的基本操作方法,如入队(add()
、offer()
)、出队(remove()
、poll()
)、查看队首元素(element()
、peek()
)等。import java.util.LinkedList; import java.util.Queue; public class QueueExample { public static void main(String[] args) { // 使用 LinkedList 实现 Queue 接口 Queue
queue = new LinkedList<>(); // 入队操作 queue.add("apple"); queue.offer("banana"); // 出队操作 String firstElement = queue.poll(); System.out.println("出队元素: " + firstElement); // 查看队首元素 String peekElement = queue.peek(); System.out.println("队首元素: " + peekElement); } } 2.
Deque
接口
Deque
是双端队列(Double-Ended Queue)的接口,它继承自Queue
接口,允许在队列的两端进行插入和删除操作。常见的实现类有LinkedList
和ArrayDeque
。
LinkedList
:基于双向链表实现,适合频繁的插入和删除操作import java.util.Deque; import java.util.LinkedList; public class LinkedListDequeExample { public static void main(String[] args) { Deque
deque = new LinkedList<>(); // 在队首插入元素 deque.addFirst("apple"); // 在队尾插入元素 deque.addLast("banana"); // 从队首移除元素 String first = deque.removeFirst(); // 从队尾移除元素 String last = deque.removeLast(); } }
ArrayDeque
:基于数组实现,具有较高的性能,适合作为栈或队列使用。import java.util.ArrayDeque; import java.util.Deque; public class ArrayDequeExample { public static void main(String[] args) { Deque
deque = new ArrayDeque<>(); deque.add(1); deque.add(2); int element = deque.poll(); } } 3.
PriorityQueue
类
PriorityQueue
是一个优先队列,它不遵循 FIFO 原则,而是根据元素的优先级进行排序,优先级高的元素先出队。元素必须实现Comparable
接口或在创建PriorityQueue
时提供一个Comparator
对象import java.util.PriorityQueue; import java.util.Queue; public class PriorityQueueExample { public static void main(String[] args) { Queue
priorityQueue = new PriorityQueue<>(); priorityQueue.add(3); priorityQueue.add(1); priorityQueue.add(2); while (!priorityQueue.isEmpty()) { System.out.println(priorityQueue.poll()); } } }
栈(Stack)
基本概念
栈是一种遵循后进先出(LIFO,Last-In-First-Out)原则的数据结构,即最后进入栈的元素最先被移除。
Java 中的栈实现类
1.
Stack
类
Stack
是 Java 早期提供的用于实现栈的类,它继承自Vector
类,是线程安全的。但由于Vector
类的性能较低,现在更推荐使用Deque
接口的实现类来替代Stack
类。import java.util.Stack; public class StackExample { public static void main(String[] args) { Stack
stack = new Stack<>(); // 入栈操作 stack.push("apple"); stack.push("banana"); // 出栈操作 String topElement = stack.pop(); System.out.println("出栈元素: " + topElement); } } 2. 使用
Deque
接口实现栈可以使用
Deque
接口的实现类(如ArrayDeque
)来实现栈的功能,这种方式性能更好。import java.util.ArrayDeque; import java.util.Deque; public class DequeAsStackExample { public static void main(String[] args) { Deque
stack = new ArrayDeque<>(); // 入栈操作 stack.push("apple"); stack.push("banana"); // 出栈操作 String topElement = stack.pop(); System.out.println("出栈元素: " + topElement); } } 1.
push(E item)
- 功能:将一个元素压入栈顶。
- 返回值:返回被压入栈的元素。
- 示例代码
import java.util.Stack; public class StackPushExample { public static void main(String[] args) { Stack
stack = new Stack<>(); String pushedElement = stack.push("apple"); System.out.println("被压入栈的元素: " + pushedElement); } } 2.
pop()
- 功能:移除并返回栈顶元素。如果栈为空,会抛出
EmptyStackException
异常。- 返回值:栈顶元素。
- 示例代码
import java.util.Stack; public class StackPopExample { public static void main(String[] args) { Stack
stack = new Stack<>(); stack.push("apple"); String poppedElement = stack.pop(); System.out.println("弹出的元素: " + poppedElement); } } 3.
peek()
- 功能:返回栈顶元素,但不移除它。如果栈为空,会抛出
EmptyStackException
异常。- 返回值:栈顶元素。
- 示例代码
import java.util.Stack; public class StackPeekExample { public static void main(String[] args) { Stack
stack = new Stack<>(); stack.push("apple"); String topElement = stack.peek(); System.out.println("栈顶元素: " + topElement); } } 4.
empty()
- 功能:检查栈是否为空。
- 返回值:如果栈为空,返回
true
;否则返回false
。- 示例代码
import java.util.Stack; public class StackEmptyExample { public static void main(String[] args) { Stack
stack = new Stack<>(); boolean isEmpty = stack.empty(); System.out.println("栈是否为空: " + isEmpty); } } 5.
search(Object o)
- 功能:返回对象在栈中的位置,以 1 为基数。如果对象不在栈中,返回 -1。
- 返回值:对象在栈中的位置。
- 示例代码
import java.util.Stack; public class StackSearchExample { public static void main(String[] args) { Stack
stack = new Stack<>(); stack.push("apple"); stack.push("banana"); int position = stack.search("banana"); System.out.println("元素 'banana' 在栈中的位置: " + position); } } 总结
- 队列:
Queue
接口是队列的基本接口,Deque
接口扩展了队列的功能,支持双端操作。PriorityQueue
则提供了优先队列的实现。- 栈:虽然有
Stack
类,但推荐使用Deque
接口的实现类(如ArrayDeque
)来实现栈的功能,以获得更好的性能。