跳转至

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);
    }
}

三、练习题目

集合框架练习

  1. 学生成绩排序

要求: - 创建 Student 类,包含姓名、学号、成绩等属性 - 使用 ArrayList 存储多个学生 - 按成绩降序排序 - 按学号升序排序

  1. 词频统计

要求: - 读取一段文本 - 统计每个单词出现的频率 - 按频率降序输出 - 使用 HashMap 实现

  1. 实现一个简单的购物车

要求: - 创建 Product 类,包含商品信息 - 使用 Map 存储购物车(商品 -> 数量) - 实现添加商品、删除商品、修改数量 - 计算总价

多线程练习

  1. 多线程求和

要求: - 创建一个包含 10000 个整数的数组 - 使用 4 个线程并行计算数组的和 - 使用线程池实现 - 比较单线程和多线程的性能

  1. 生产者-消费者模型

要求: - 创建一个共享缓冲区 - 生产者线程生产数据放入缓冲区 - 消费者线程从缓冲区取出数据 - 使用 wait/notify 实现线程通信

  1. 银行转账问题

要求: - 创建多个银行账户 - 多个线程同时进行转账操作 - 使用同步机制保证数据一致性 - 避免死锁


下一步建议: 学习 Java 的 IO 流、网络编程和反射等高级特性,进一步提升你的 Java 编程能力。