JavaSE(三)集合框架与多线程¶
集合框架和多线程是 Java 编程中的重要组成部分。集合框架提供了高效的数据存储和操作方式,而多线程编程让我们能够充分利用多核 CPU 的性能,提高程序的并发处理能力。本教程将深入讲解 Java 集合框架的核心类和多线程编程的关键技术。
一、Java 集合框架¶
(一)集合框架概述¶
Java 集合框架提供了一套性能优良、使用方便的接口和类,位于 java.util 包中。
集合框架的主要接口: - Collection:集合的根接口 - List:有序集合,允许重复元素 - Set:不包含重复元素的集合 - Queue:队列,通常遵循 FIFO(先进先出)原则 - Map:键值对映射,键不能重复
(二)List 接口¶
List 是有序集合,允许重复元素,可以通过索引访问元素。
1. ArrayList¶
ArrayList 是基于动态数组实现的 List,查询效率高,增删效率低。
import java.util.*;
public class ArrayListDemo {
public static void main(String[] args) {
// 创建 ArrayList
List<String> list = new ArrayList<>();
// 添加元素
list.add("Apple");
list.add("Banana");
list.add("Orange");
list.add("Apple"); // 允许重复
// 在指定位置插入元素
list.add(1, "Grape");
// 访问元素
System.out.println("第一个元素: " + list.get(0));
System.out.println("列表大小: " + list.size());
// 遍历列表
System.out.println("\n遍历列表:");
for (String fruit : list) {
System.out.println(fruit);
}
// 使用迭代器遍历
System.out.println("\n使用迭代器:");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// 删除元素
list.remove("Apple"); // 删除第一个匹配的元素
list.remove(0); // 删除指定位置的元素
// 检查元素
System.out.println("\n是否包含 Banana: " + list.contains("Banana"));
// 转换为数组
Object[] array = list.toArray();
}
}
2. LinkedList¶
LinkedList 是基于双向链表实现的 List,增删效率高,查询效率低。
import java.util.*;
public class LinkedListDemo {
public static void main(String[] args) {
// 创建 LinkedList
LinkedList<String> list = new LinkedList<>();
// 添加元素
list.add("A");
list.add("B");
list.add("C");
// 在头部和尾部添加元素
list.addFirst("First");
list.addLast("Last");
// 获取头部和尾部元素
System.out.println("头部元素: " + list.getFirst());
System.out.println("尾部元素: " + list.getLast());
// 删除头部和尾部元素
list.removeFirst();
list.removeLast();
// 遍历
for (String item : list) {
System.out.println(item);
}
}
}
(三)Set 接口¶
Set 是不包含重复元素的集合。
1. HashSet¶
HashSet 基于 HashMap 实现,不保证元素的顺序,查询和增删效率都很高。
import java.util.*;
public class HashSetDemo {
public static void main(String[] args) {
// 创建 HashSet
Set<String> set = new HashSet<>();
// 添加元素(重复元素不会被添加)
set.add("Java");
set.add("Python");
set.add("C++");
set.add("Java"); // 重复,不会被添加
System.out.println("Set 大小: " + set.size());
// 遍历(顺序不确定)
for (String language : set) {
System.out.println(language);
}
// 检查元素
System.out.println("是否包含 Java: " + set.contains("Java"));
// 删除元素
set.remove("Python");
}
}
2. TreeSet¶
TreeSet 基于 TreeMap 实现,元素按自然顺序或自定义比较器排序。
import java.util.*;
public class TreeSetDemo {
public static void main(String[] args) {
// 创建 TreeSet(元素按升序排列)
Set<Integer> set = new TreeSet<>();
set.add(5);
set.add(2);
set.add(8);
set.add(1);
set.add(3);
// 遍历(有序)
for (Integer num : set) {
System.out.println(num);
}
// 使用自定义比较器(降序)
Set<Integer> descSet = new TreeSet<>(Collections.reverseOrder());
descSet.add(5);
descSet.add(2);
descSet.add(8);
System.out.println("\n降序排列:");
for (Integer num : descSet) {
System.out.println(num);
}
}
}
(四)Map 接口¶
Map 存储键值对,键不能重复。
1. HashMap¶
HashMap 基于哈希表实现,查询和增删效率都很高,不保证顺序。
import java.util.*;
public class HashMapDemo {
public static void main(String[] args) {
// 创建 HashMap
Map<String, Integer> map = new HashMap<>();
// 添加键值对
map.put("Apple", 5);
map.put("Banana", 3);
map.put("Orange", 7);
map.put("Grape", 4);
// 访问值
System.out.println("Apple 的数量: " + map.get("Apple"));
// 遍历 Map
System.out.println("\n遍历键值对:");
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 遍历键
System.out.println("\n遍历键:");
for (String key : map.keySet()) {
System.out.println(key);
}
// 遍历值
System.out.println("\n遍历值:");
for (Integer value : map.values()) {
System.out.println(value);
}
// 检查键和值
System.out.println("\n是否包含键 Apple: " + map.containsKey("Apple"));
System.out.println("是否包含值 5: " + map.containsValue(5));
// 删除键值对
map.remove("Banana");
// 替换值
map.replace("Apple", 10);
}
}
2. TreeMap¶
TreeMap 基于红黑树实现,键按自然顺序或自定义比较器排序。
import java.util.*;
public class TreeMapDemo {
public static void main(String[] args) {
// 创建 TreeMap(键按升序排列)
Map<String, Integer> map = new TreeMap<>();
map.put("C", 3);
map.put("A", 1);
map.put("B", 2);
map.put("D", 4);
// 遍历(按键排序)
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
// 获取第一个和最后一个键
System.out.println("\n第一个键: " + ((TreeMap<String, Integer>) map).firstKey());
System.out.println("最后一个键: " + ((TreeMap<String, Integer>) map).lastKey());
}
}
(五)Queue 接口¶
Queue 是队列,通常遵循 FIFO 原则。
1. LinkedList 作为 Queue¶
import java.util.*;
public class QueueDemo {
public static void main(String[] args) {
// 使用 LinkedList 作为 Queue
Queue<String> queue = new LinkedList<>();
// 入队
queue.offer("A");
queue.offer("B");
queue.offer("C");
// 查看队首元素(不删除)
System.out.println("队首元素: " + queue.peek());
// 出队
System.out.println("出队: " + queue.poll());
System.out.println("出队: " + queue.poll());
// 队列大小
System.out.println("队列大小: " + queue.size());
}
}
2. PriorityQueue(优先队列)¶
PriorityQueue 是基于优先级堆的无界优先队列。
import java.util.*;
public class PriorityQueueDemo {
public static void main(String[] args) {
// 创建优先队列(默认是升序)
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(5);
pq.offer(2);
pq.offer(8);
pq.offer(1);
// 按优先级出队
while (!pq.isEmpty()) {
System.out.println(pq.poll());
}
// 创建降序优先队列
PriorityQueue<Integer> descPQ = new PriorityQueue<>(Collections.reverseOrder());
descPQ.offer(5);
descPQ.offer(2);
descPQ.offer(8);
System.out.println("\n降序:");
while (!descPQ.isEmpty()) {
System.out.println(descPQ.poll());
}
}
}
(六)Collections 工具类¶
Collections 提供了大量操作集合的静态方法。
import java.util.*;
public class CollectionsDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(5);
list.add(2);
list.add(8);
list.add(1);
list.add(3);
// 排序
Collections.sort(list);
System.out.println("升序排序: " + list);
// 降序排序
Collections.sort(list, Collections.reverseOrder());
System.out.println("降序排序: " + list);
// 打乱
Collections.shuffle(list);
System.out.println("打乱后: " + list);
// 查找最大值和最小值
System.out.println("最大值: " + Collections.max(list));
System.out.println("最小值: " + Collections.min(list));
// 二分查找(需要先排序)
Collections.sort(list);
int index = Collections.binarySearch(list, 5);
System.out.println("5 的索引: " + index);
// 创建不可修改的集合
List<Integer> unmodifiableList = Collections.unmodifiableList(list);
// unmodifiableList.add(10); // 会抛出 UnsupportedOperationException
// 创建同步集合(线程安全)
List<Integer> synchronizedList = Collections.synchronizedList(list);
}
}
(七)集合框架的竞赛级应用¶
1. 去重操作¶
import java.util.*;
public class RemoveDuplicates {
public static void main(String[] args) {
int[] arr = {1, 2, 3, 2, 4, 1, 5, 3, 6};
// 使用 HashSet 去重
Set<Integer> set = new HashSet<>();
for (int num : arr) {
set.add(num);
}
// 转换为数组
int[] result = new int[set.size()];
int index = 0;
for (int num : set) {
result[index++] = num;
}
System.out.println(Arrays.toString(result));
}
}
2. 统计频率¶
import java.util.*;
public class FrequencyCount {
public static void main(String[] args) {
String str = "hello world hello java world java";
// 统计单词频率
Map<String, Integer> frequency = new HashMap<>();
String[] words = str.split(" ");
for (String word : words) {
frequency.put(word, frequency.getOrDefault(word, 0) + 1);
}
// 输出频率
for (Map.Entry<String, Integer> entry : frequency.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
二、多线程编程¶
(一)线程基础¶
线程是程序执行的最小单位,Java 提供了多种创建线程的方式。
1. 继承 Thread 类¶
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
// 创建并启动线程
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start(); // 启动线程
thread2.start();
System.out.println("主线程结束");
}
}
2. 实现 Runnable 接口¶
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
// 创建 Runnable 对象
MyRunnable runnable = new MyRunnable();
// 创建线程并启动
Thread thread1 = new Thread(runnable, "线程1");
Thread thread2 = new Thread(runnable, "线程2");
thread1.start();
thread2.start();
}
}
3. 使用 Lambda 表达式(Java 8+)¶
public class ThreadDemo3 {
public static void main(String[] args) {
// 使用 Lambda 表达式创建线程
Thread thread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "Lambda线程");
thread.start();
}
}
(二)线程同步¶
当多个线程同时访问共享资源时,可能会产生数据不一致的问题,需要使用同步机制。
1. synchronized 关键字¶
class Counter {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
// 同步代码块
public void decrement() {
synchronized (this) {
count--;
}
}
public int getCount() {
return count;
}
}
public class SynchronizedDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 创建多个线程同时修改计数器
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
System.out.println("最终计数: " + counter.getCount());
}
}
2. Lock 接口¶
import java.util.concurrent.locks.*;
class Counter2 {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 释放锁
}
}
public int getCount() {
return count;
}
}
(三)线程通信¶
线程之间可以通过 wait()、notify() 和 notifyAll() 方法进行通信。
class ProducerConsumer {
private final int MAX_SIZE = 5;
private LinkedList<Integer> list = new LinkedList<>();
public void produce(int value) throws InterruptedException {
synchronized (this) {
while (list.size() == MAX_SIZE) {
wait(); // 等待消费者消费
}
list.add(value);
System.out.println("生产: " + value);
notifyAll(); // 通知消费者
}
}
public void consume() throws InterruptedException {
synchronized (this) {
while (list.isEmpty()) {
wait(); // 等待生产者生产
}
int value = list.removeFirst();
System.out.println("消费: " + value);
notifyAll(); // 通知生产者
}
}
}
public class ProducerConsumerDemo {
public static void main(String[] args) {
ProducerConsumer pc = new ProducerConsumer();
// 生产者线程
Thread producer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
pc.produce(i);
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 消费者线程
Thread consumer = new Thread(() -> {
for (int i = 0; i < 10; i++) {
try {
pc.consume();
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
producer.start();
consumer.start();
}
}
(四)线程池¶
线程池可以重用线程,避免频繁创建和销毁线程的开销。
import java.util.concurrent.*;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交任务
for (int i = 0; i < 10; i++) {
final int taskNum = i;
executor.submit(() -> {
System.out.println("任务 " + taskNum + " 由 " +
Thread.currentThread().getName() + " 执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
// 关闭线程池
executor.shutdown();
}
}
(五)Callable 和 Future¶
Callable 接口类似于 Runnable,但可以返回结果并抛出异常。
import java.util.concurrent.*;
public class CallableDemo {
public static void main(String[] args) throws Exception {
// 创建 Callable 任务
Callable<Integer> task = () -> {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
return sum;
};
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(1);
// 提交任务并获取 Future
Future<Integer> future = executor.submit(task);
// 获取结果(会阻塞直到任务完成)
Integer result = future.get();
System.out.println("1 到 100 的和: " + result);
// 关闭线程池
executor.shutdown();
}
}
(六)并发集合¶
Java 提供了线程安全的并发集合。
import java.util.concurrent.*;
import java.util.*;
public class ConcurrentCollectionDemo {
public static void main(String[] args) throws InterruptedException {
// ConcurrentHashMap
Map<String, Integer> concurrentMap = new ConcurrentHashMap<>();
// CopyOnWriteArrayList
List<String> copyOnWriteList = new CopyOnWriteArrayList<>();
// ConcurrentLinkedQueue
Queue<String> concurrentQueue = new ConcurrentLinkedQueue<>();
// 创建多个线程并发修改
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
final int threadNum = i;
executor.submit(() -> {
concurrentMap.put("key" + threadNum, threadNum);
copyOnWriteList.add("item" + threadNum);
concurrentQueue.offer("queue" + threadNum);
});
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
System.out.println("ConcurrentHashMap: " + concurrentMap);
System.out.println("CopyOnWriteArrayList: " + copyOnWriteList);
System.out.println("ConcurrentLinkedQueue: " + concurrentQueue);
}
}
三、练习题目¶
集合框架练习¶
- 学生成绩排序
要求: - 创建 Student 类,包含姓名、学号、成绩等属性 - 使用 ArrayList 存储多个学生 - 按成绩降序排序 - 按学号升序排序
- 词频统计
要求: - 读取一段文本 - 统计每个单词出现的频率 - 按频率降序输出 - 使用 HashMap 实现
- 实现一个简单的购物车
要求: - 创建 Product 类,包含商品信息 - 使用 Map 存储购物车(商品 -> 数量) - 实现添加商品、删除商品、修改数量 - 计算总价
多线程练习¶
- 多线程求和
要求: - 创建一个包含 10000 个整数的数组 - 使用 4 个线程并行计算数组的和 - 使用线程池实现 - 比较单线程和多线程的性能
- 生产者-消费者模型
要求: - 创建一个共享缓冲区 - 生产者线程生产数据放入缓冲区 - 消费者线程从缓冲区取出数据 - 使用 wait/notify 实现线程通信
- 银行转账问题
要求: - 创建多个银行账户 - 多个线程同时进行转账操作 - 使用同步机制保证数据一致性 - 避免死锁
下一步建议: 学习 Java 的 IO 流、网络编程和反射等高级特性,进一步提升你的 Java 编程能力。