面试题
TCP/IP协议、TCP和UDP的区别
1. TCP/IP协议族
TCP/IP是一组网络通信协议的集合,定义了互联网的数据传输标准。它分为四层(或五层)模型:
- 应用层(HTTP、FTP、DNS等)
- 传输层(TCP、UDP)
- 网络层(IP、ICMP)
- 网络接口层(以太网、Wi-Fi等)
作用:提供端到端的数据传输,涵盖寻址(IP)、可靠性(TCP)、效率(UDP)等。
2. TCP vs UDP
二者均属于传输层协议,但设计目标不同:
| 特性 | TCP(传输控制协议) | UDP(用户数据报协议) |
|---|---|---|
| 连接方式 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认、重传、排序) | 不可靠(尽最大努力交付) |
| 数据顺序 | 保证数据顺序 | 不保证顺序 |
| 速度/开销 | 慢(头部大,控制机制复杂) | 快(头部小,无控制机制) |
| 流量控制 | 有(滑动窗口) | 无 |
| 拥塞控制 | 有(慢启动、拥塞避免等) | 无 |
| 适用场景 | 网页(HTTP)、邮件(SMTP)、文件传输(FTP) | 视频流、语音通话(VoIP)、DNS查询、游戏 |
3. 关键区别总结
- 可靠性需求:TCP适合需要数据完整性的场景(如文件下载),UDP适合容忍丢包但追求低延迟的场景(如直播)。
- 连接管理:TCP需建立连接,UDP直接发送数据。
- 头部大小:TCP头部至少20字节,UDP仅8字节。
4. 为什么需要UDP?
尽管TCP可靠,但其复杂机制会引入延迟。UDP通过牺牲可靠性换取效率,适合实时性要求高的应用。例如:
- DNS查询:快速响应比重传更重要。
- 在线游戏:延迟比偶尔丢包更影响体验。
5. 协议选择建议
- 选TCP:当数据必须完整到达(如银行交易)。
- 选UDP:当速度优先,且应用层可处理丢包(如视频会议)。
理解这些差异有助于根据需求选择合适的传输协议。
TCP、UDP、HTTP、WebSocket 和 Socket 的区别
1. TCP 和 UDP(传输层协议)
TCP(传输控制协议)
- 特点:
- 面向连接:通信前需三次握手建立连接(可靠)。
- 可靠传输:数据确认、重传、排序、流量/拥塞控制。
- 速度较慢:因额外控制机制,头部较大(至少20字节)。
- 适用场景:
- 需要数据完整性的应用(如网页浏览、文件传输、电子邮件)。
UDP(用户数据报协议)
- 特点:
- 无连接:直接发送数据,无需建立连接。
- 不可靠:不保证数据到达、不排序、无重传机制。
- 速度快:头部小(仅8字节),适合低延迟场景。
- 适用场景:
- 实时性要求高的应用(如视频流、语音通话、在线游戏)。
对比:
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 可靠(确认+重传) | 不可靠 |
| 数据顺序 | 保证顺序 | 不保证顺序 |
| 速度 | 慢 | 快 |
| 头部大小 | 至少20字节 | 8字节 |
2. HTTP(应用层协议)
- 特点:
- 基于TCP:默认使用TCP端口80(HTTP)或443(HTTPS)。
- 无状态:每次请求独立,服务器不记录客户端状态(依赖Cookie/Session)。
- 短连接:传统HTTP/1.1每次请求需新建TCP连接(HTTP/1.1支持长连接,但本质仍是请求-响应模式)。
- 适用场景:
- 网页浏览、API调用等单向请求-响应场景。
3. WebSocket(应用层协议)
- 特点:
- 基于TCP:在HTTP协议升级后建立持久化连接(端口80/443)。
- 全双工通信:服务器和客户端可主动双向实时推送数据。
- 有状态:连接建立后保持活跃,避免重复握手开销。
- 适用场景:
- 实时聊天、股票行情、在线游戏等需要持续双向通信的场景。
HTTP vs WebSocket:
| 特性 | HTTP | WebSocket |
|---|---|---|
| 通信模式 | 请求-响应(单向) | 全双工(双向) |
| 连接生命周期 | 短连接(默认) | 长连接 |
| 协议基础 | 基于TCP | 基于TCP(HTTP升级) |
| 实时性 | 低(需轮询) | 高(服务器可主动推送) |
4. Socket(编程接口,非协议)
- 特点:
- 操作系统提供的API:用于网络通信(如
send(),recv())。 - 支持TCP/UDP:开发者可通过Socket编程直接使用TCP或UDP。
- 灵活但复杂:需手动处理连接、数据包等底层细节。
- 操作系统提供的API:用于网络通信(如
- 适用场景:
- 需要自定义协议或底层控制的场景(如开发P2P应用、自定义实时通信协议)。
Socket 与 TCP/UDP 的关系:
- Socket是编程接口,TCP/UDP是传输层协议。
- 通过Socket可以调用TCP(
SOCK_STREAM)或UDP(SOCK_DGRAM)。
5. 总结:关键区别与联系
| 名称 | 层级 | 特点 | 典型应用场景 |
|---|---|---|---|
| TCP | 传输层 | 可靠、面向连接、速度慢 | 网页、文件传输、电子邮件 |
| UDP | 传输层 | 不可靠、无连接、速度快 | 视频流、游戏、DNS查询 |
| HTTP | 应用层 | 基于TCP、无状态、短连接 | 网页浏览、REST API |
| WebSocket | 应用层 | 基于TCP、全双工、长连接 | 实时聊天、股票推送 |
| Socket | 接口 | 提供TCP/UDP的编程能力 | 自定义网络应用开发 |
关系示意图
Socket(API)
│
├── TCP → HTTP(单向请求-响应)
│ └── WebSocket(双向通信)
│
└── UDP → 实时音视频、游戏如何选择?
- 需要可靠传输:TCP + HTTP/WebSocket。
- 需要低延迟:UDP(如QUIC协议已用于HTTP/3)。
- 需要双向实时通信:WebSocket。
- 需要完全自定义协议:Socket + TCP/UDP。
理解这些概念后,可以更精准地选择适合业务需求的网络技术。
Java I/O 操作详解
Java I/O (输入/输出) 是 Java 中处理数据输入和输出的核心 API。Java 提供了丰富的 I/O 类库,可以分为几个主要类别:
1. 基本 I/O 流分类
按数据流向
- 输入流:从数据源读取数据 (InputStream/Reader)
- 输出流:向目标写入数据 (OutputStream/Writer)
按数据类型
- 字节流:以字节为单位 (InputStream/OutputStream)
- 字符流:以字符为单位 (Reader/Writer)
按功能
- 节点流:直接与数据源连接
- 处理流:对节点流进行包装,提供额外功能
2. 核心 I/O 类
字节流
InputStream(抽象类)FileInputStream(文件输入)ByteArrayInputStream(字节数组输入)ObjectInputStream(对象反序列化)
OutputStream(抽象类)FileOutputStream(文件输出)ByteArrayOutputStream(字节数组输出)ObjectOutputStream(对象序列化)
字符流
Reader(抽象类)FileReader(文件读取)InputStreamReader(字节到字符转换)BufferedReader(缓冲读取)
Writer(抽象类)FileWriter(文件写入)OutputStreamWriter(字符到字节转换)BufferedWriter(缓冲写入)
3. 常用 I/O 操作示例
文件复制 (字节流)
try (InputStream in = new FileInputStream("source.txt");
OutputStream out = new FileOutputStream("target.txt")) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}读取文本文件 (字符流)
try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}写入文本文件 (字符流)
java
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
writer.write("Hello, World!");
writer.newLine();
writer.write("This is a test.");
} catch (IOException e) {
e.printStackTrace();
}4. NIO (New I/O)
Java NIO 提供了更高效的 I/O 操作方式:
核心组件
Channel:双向数据传输通道Buffer:数据容器Selector:多路复用器
NIO 文件复制示例
try (FileChannel inChannel = new FileInputStream("source.txt").getChannel();
FileChannel outChannel = new FileOutputStream("target.txt").getChannel()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (inChannel.read(buffer) != -1) {
buffer.flip(); // 切换为读模式
outChannel.write(buffer);
buffer.clear(); // 清空缓冲区
}
} catch (IOException e) {
e.printStackTrace();
}5. Java 7 引入的 Files 类
Java 7 在 java.nio.file 包中引入了 Files 类,简化了文件操作:
// 读取所有行
List<String> lines = Files.readAllLines(Paths.get("file.txt"), StandardCharsets.UTF_8);
// 写入文件
Files.write(Paths.get("output.txt"), "Hello".getBytes());
// 复制文件
Files.copy(Paths.get("source.txt"), Paths.get("target.txt"));
// 移动/重命名文件
Files.move(Paths.get("old.txt"), Paths.get("new.txt"));6. 最佳实践
- 始终关闭流 - 使用 try-with-resources 语句
- 对于大文件,使用缓冲流提高性能
- 考虑使用 NIO 处理高并发 I/O
- 注意字符编码问题 (推荐明确指定 UTF-8)
- 处理路径时使用
Paths.get()和Path接口
Java I/O 系统非常强大,选择适当的类和方法可以显著提高应用程序的性能和可靠性。
Java 多线程编程详解
Java 多线程是 Java 并发编程的核心部分,允许程序同时执行多个任务。以下是 Java 多线程的全面介绍:
1. 线程基础概念
线程与进程的区别
- 进程:操作系统分配资源的基本单位,独立内存空间
- 线程:进程内的执行单元,共享进程资源
Java 线程实现方式
- 继承 Thread 类
class MyThread extends Thread {
public void run() {
System.out.println("线程运行中");
}
}
MyThread t = new MyThread();
t.start();- 实现 Runnable 接口 (推荐)
class MyRunnable implements Runnable {
public void run() {
System.out.println("线程运行中");
}
}
Thread t = new Thread(new MyRunnable());
t.start();- 实现 Callable 接口 (可返回结果)
class MyCallable implements Callable<String> {
public String call() throws Exception {
return "任务完成";
}
}
FutureTask<String> task = new FutureTask<>(new MyCallable());
Thread t = new Thread(task);
t.start();
System.out.println(task.get()); // 获取返回结果Java 网络编程详解
Java 网络编程主要基于 TCP/IP 协议栈,提供了丰富的 API 来实现网络通信。以下是 Java 网络编程的核心内容:
1. 网络编程基础
网络模型
- OSI 七层模型:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层
- TCP/IP 四层模型:网络接口层、网络层(IP)、传输层(TCP/UDP)、应用层(HTTP/FTP等)
关键概念
- IP地址:标识网络中的设备(IPv4/IPv6)
- 端口号:标识应用程序(0-65535)
- Socket:网络通信的端点
2. Java 网络编程核心类
InetAddress 类
// 获取本地主机信息
InetAddress localHost = InetAddress.getLocalHost();
// 通过主机名获取IP
InetAddress address = InetAddress.getByName("www.example.com");
// 获取所有IP
InetAddress[] addresses = InetAddress.getAllByName("www.example.com");3. TCP 编程
服务端实现
// 创建ServerSocket,监听指定端口
try (ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("服务器启动,等待连接...");
// 接受客户端连接
Socket socket = serverSocket.accept();
System.out.println("客户端连接成功: " + socket.getInetAddress());
// 获取输入输出流
try (InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
PrintWriter writer = new PrintWriter(out, true)) {
// 读取客户端数据
String clientMessage = reader.readLine();
System.out.println("收到客户端消息: " + clientMessage);
// 发送响应
writer.println("你好,客户端!");
}
} catch (IOException e) {
e.printStackTrace();
}客户端实现
java
// 创建Socket连接服务器
try (Socket socket = new Socket("localhost", 8888);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
PrintWriter writer = new PrintWriter(out, true);
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
// 发送消息
writer.println("你好,服务器!");
// 接收响应
String serverResponse = reader.readLine();
System.out.println("服务器响应: " + serverResponse);
} catch (IOException e) {
e.printStackTrace();
}4. UDP 编程
服务端实现
java
// 创建DatagramSocket,监听指定端口
try (DatagramSocket socket = new DatagramSocket(8888)) {
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
System.out.println("UDP服务器启动...");
socket.receive(packet); // 接收数据包
String message = new String(packet.getData(), 0, packet.getLength());
System.out.println("收到来自 " + packet.getAddress() + " 的消息: " + message);
// 发送响应
byte[] response = "UDP服务器响应".getBytes();
DatagramPacket responsePacket = new DatagramPacket(
response, response.length, packet.getAddress(), packet.getPort());
socket.send(responsePacket);
} catch (IOException e) {
e.printStackTrace();
}客户端实现
try (DatagramSocket socket = new DatagramSocket()) {
InetAddress address = InetAddress.getByName("localhost");
byte[] message = "你好,UDP服务器".getBytes();
// 发送数据包
DatagramPacket packet = new DatagramPacket(
message, message.length, address, 8888);
socket.send(packet);
// 接收响应
byte[] buffer = new byte[1024];
DatagramPacket response = new DatagramPacket(buffer, buffer.length);
socket.receive(response);
System.out.println("收到服务器响应: " +
new String(response.getData(), 0, response.getLength()));
} catch (IOException e) {
e.printStackTrace();
}MySQL 索引与事务详解
一、索引(Index)
1. 索引是什么?
索引是数据库中一种特殊的数据结构,它类似于书籍的目录,能够帮助数据库系统快速定位数据,而无需扫描整个表。
2. 索引的作用
- 加速数据检索:显著提高SELECT查询速度
- 保证数据唯一性:唯一索引可防止重复数据
- 加速表连接:提高多表连接查询效率
- 优化排序和分组:对ORDER BY和GROUP BY操作有帮助
3. 常见索引类型
| 索引类型 | 描述 |
|---|---|
| B-Tree索引 | MySQL默认索引类型,适合全键值、键值范围或键前缀查找 |
| 哈希索引 | 基于哈希表实现,仅支持等值比较查询,Memory引擎默认使用 |
| 全文索引 | 用于全文搜索,仅MyISAM和InnoDB(5.6+)支持 |
| 空间索引 | 用于地理空间数据类型,MyISAM支持 |
4. 索引使用示例
-- 创建普通索引
CREATE INDEX idx_name ON users(username);
-- 创建唯一索引
CREATE UNIQUE INDEX idx_email ON users(email);
-- 创建复合索引
CREATE INDEX idx_name_age ON users(username, age);
-- 查看表索引
SHOW INDEX FROM users;5. 索引优化建议
- 为WHERE、JOIN、ORDER BY子句中的列创建索引
- 避免过度索引,每个索引都会占用空间并降低写入性能
- 使用复合索引时遵循"最左前缀原则"
- 对区分度高的列建索引(如身份证号比性别更适合建索引)
- 长字符串列考虑使用前缀索引
二、事务(Transaction)
1. 事务是什么?
事务是一组原子性的SQL操作,要么全部执行成功,要么全部失败回滚。
2. 事务的特性(ACID)
- 原子性(Atomicity):事务是不可分割的工作单位
- 一致性(Consistency):事务使数据库从一个一致状态变到另一个一致状态
- 隔离性(Isolation):事务执行不受其他事务干扰
- 持久性(Durability):事务一旦提交,其结果就是永久性的
3. 事务控制语句
sql
-- 开始事务
START TRANSACTION;
-- 或
BEGIN;
-- 提交事务
COMMIT;
-- 回滚事务
ROLLBACK;
-- 设置保存点
SAVEPOINT point_name;
-- 回滚到保存点
ROLLBACK TO point_name;4. 事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 最低隔离级别,可读取未提交数据 |
| READ COMMITTED | 不可能 | 可能 | 可能 | 只能读取已提交数据(Oracle默认) |
| REPEATABLE READ | 不可能 | 不可能 | 可能 | 同一事务中多次读取结果一致(MySQL默认) |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 | 最高隔离级别,完全串行执行,性能最差 |
设置隔离级别:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;5. 事务使用示例
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1;
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2;
-- 如果执行成功
COMMIT;
-- 如果出现错误
-- ROLLBACK;6. 事务最佳实践
- 尽量缩短事务执行时间
- 避免在事务中进行耗时操作(如网络请求)
- 根据业务需求选择合适的隔离级别
- 注意死锁问题,尽量按相同顺序访问多表
- 大事务可考虑拆分为多个小事务
三、索引与事务的关系
- 索引影响事务性能:
- 索引加速读操作,但会增加写操作的开销
- 事务中的大量写操作在有索引的表上会更慢
- 事务隔离级别影响索引使用:
- 高隔离级别可能导致更多的锁争用,影响索引效率
- REPEATABLE READ下可能出现"幻读"问题,即使使用了索引
- InnoDB的聚簇索引:
- InnoDB表的数据存储本身就是按主键组织的聚簇索引
- 事务的原子性和隔离性实现依赖于索引结构
理解索引和事务是优化MySQL性能的关键,合理使用索引可以显著提高查询效率,而正确使用事务可以保证数据的一致性和完整性。
StringBuffer 和 StringBuilder 详解
1. 基本概念
StringBuffer
- 线程安全:所有方法都是同步的(synchronized)
- 性能:由于同步开销,性能相对较低
- 适用场景:多线程环境下需要修改字符串内容
StringBuilder
- 非线程安全:没有同步方法
- 性能:比StringBuffer更高
- 适用场景:单线程环境下需要修改字符串内容
2. 共同特点
- 都是
java.lang包下的类 - 都继承自
AbstractStringBuilder - 都是可变字符序列
- 都提供了一系列修改字符串的方法
- 初始容量都是16个字符,可自动扩容
3. 主要区别
| 特性 | StringBuffer | StringBuilder |
|---|---|---|
| 线程安全 | 是 (方法同步) | 否 |
| 性能 | 较低 | 较高 |
| 版本引入 | Java 1.0 | Java 5.0 |
| 适用场景 | 多线程环境 | 单线程环境 |
| 同步开销 | 有 | 无 |
4. 常用方法
两者提供相同的API:
// 创建
StringBuffer sb1 = new StringBuffer(); // 初始容量16
StringBuilder sb2 = new StringBuilder("初始值");
// 追加内容
sb1.append("追加文本");
sb1.append(100); // 追加数字
sb1.append(true); // 追加布尔值
// 插入内容
sb1.insert(2, "插入"); // 在指定位置插入
// 删除内容
sb1.delete(1, 3); // 删除1-3位置的字符
sb1.deleteCharAt(0); // 删除指定位置字符
// 替换内容
sb1.replace(0, 2, "替换"); // 替换指定范围内容
// 反转
sb1.reverse();
// 获取长度和容量
int len = sb1.length();
int cap = sb1.capacity();
// 设置长度
sb1.setLength(10);
// 转换为String
String result = sb1.toString();Java集合框架详解及对比
一、Java集合框架全景图
Collection接口
├── List接口 (有序、可重复)
│ ├── ArrayList
│ ├── LinkedList
│ ├── Vector
│ └── Stack
│
├── Set接口 (无序、唯一)
│ ├── HashSet
│ │ └── LinkedHashSet
│ ├── TreeSet
│ └── EnumSet
│
└── Queue接口 (队列)
├── PriorityQueue
├── Deque接口
│ ├── ArrayDeque
│ └── LinkedList
└── BlockingQueue接口
├── ArrayBlockingQueue
├── LinkedBlockingQueue
└── PriorityBlockingQueue
Map接口 (键值对)
├── HashMap
│ └── LinkedHashMap
├── TreeMap
├── Hashtable
└── ConcurrentHashMap二、主要集合类详细对比
1. List系列对比
| 特性 | ArrayList | LinkedList | Vector | Stack |
|---|---|---|---|---|
| 底层实现 | 动态数组 | 双向链表 | 动态数组 | 继承自Vector |
| 线程安全 | 否 | 否 | 是(synchronized) | 是(synchronized) |
| 随机访问性能 | O(1) 极快 | O(n) 慢 | O(1) | O(1) |
| 插入删除性能 | 末尾O(1),中间O(n) | 头尾O(1),中间O(n) | 类似ArrayList | 类似Vector |
| 内存占用 | 较小(连续内存) | 较大(节点开销) | 类似ArrayList | 类似Vector |
| 扩容机制 | 1.5倍 | 无需扩容 | 2倍 | 同Vector |
| 最佳使用场景 | 查询多,增删少 | 增删多,查询少 | 已淘汰 | 已淘汰 |
2. Set系列对比
| 特性 | HashSet | LinkedHashSet | TreeSet | EnumSet |
|---|---|---|---|---|
| 底层实现 | HashMap | LinkedHashMap | TreeMap | 位向量 |
| 排序特性 | 无序 | 插入顺序 | 自然排序/Comparator | 枚举定义顺序 |
| null值 | 允许1个null | 允许1个null | 不允许(除非Comparator允许) | 不允许 |
| 时间复杂度 | 添加/删除/查找O(1) | 添加/删除/查找O(1) | 添加/删除/查找O(log n) | O(1) |
| 线程安全 | 否 | 否 | 否 | 否 |
| 最佳使用场景 | 常规去重需求 | 需要保持插入顺序 | 需要排序 | 枚举类型集合 |
3. Queue/Deque对比
| 特性 | ArrayDeque | LinkedList | PriorityQueue | ArrayBlockingQueue |
|---|---|---|---|---|
| 底层实现 | 循环数组 | 双向链表 | 二叉堆(数组) | 数组 |
| 是否阻塞 | 否 | 否 | 否 | 是 |
| 排序 | FIFO/LIFO | FIFO/LIFO | 优先级排序 | FIFO |
| 线程安全 | 否 | 否 | 否 | 是 |
| null值 | 不允许 | 允许 | 不允许 | 不允许 |
| 最佳使用场景 | 高频插入删除 | 同时需要List功能 | 任务调度 | 生产者-消费者模型 |
4. Map系列对比
| 特性 | HashMap | LinkedHashMap | TreeMap | Hashtable | ConcurrentHashMap |
|---|---|---|---|---|---|
| 底层实现 | 数组+链表/红黑树 | 链表维护插入顺序 | 红黑树 | 数组+链表 | 分段数组+链表/红黑树 |
| 排序特性 | 无序 | 插入顺序/访问顺序 | 键的自然排序 | 无序 | 无序 |
| null键值 | 允许1个null键和多个null值 | 同HashMap | 不允许 | 不允许 | 不允许 |
| 线程安全 | 否 | 否 | 否 | 是(synchronized) | 是(分段锁) |
| 时间复杂度 | O(1) | O(1) | O(log n) | O(1) | O(1) |
| 扩容机制 | 2倍 | 同HashMap | 无需扩容 | 2倍+1 | 分段扩容 |
| 最佳使用场景 | 常规键值存储 | 需要保持插入顺序 | 需要键排序 | 已淘汰 | 高并发环境 |
三、集合选择指南
- 需要键值对存储:
- 单线程:
HashMap - 多线程:
ConcurrentHashMap - 需要排序:
TreeMap - 保持插入顺序:
LinkedHashMap
- 单线程:
- 只需要存储元素:
- 允许重复:
ArrayList(查询多)/LinkedList(增删多) - 不允许重复:
- 常规:
HashSet - 需要排序:
TreeSet - 保持插入顺序:
LinkedHashSet
- 常规:
- 允许重复:
- 队列需求:
- 普通队列:
ArrayDeque - 优先级队列:
PriorityQueue - 阻塞队列:
ArrayBlockingQueue/LinkedBlockingQueue
- 普通队列:
- 线程安全选择:
- List:
Collections.synchronizedList(new ArrayList<>()) - Set:
Collections.synchronizedSet(new HashSet<>()) - Map:
ConcurrentHashMap(优于Hashtable)
- List:
四、Java 8+新特性对集合的影响
HashMap优化:
- 当链表长度>8时转为红黑树,提高最坏情况性能
- 链表长度<6时转回链表
新增API:
java// forEach map.forEach((k, v) -> System.out.println(k + ": " + v)); // removeIf list.removeIf(e -> e.length() > 5); // computeIfAbsent map.computeIfAbsent(key, k -> new ArrayList<>()).add(value);Stream API:
java
javaList<String> filtered = list.stream() .filter(s -> s.startsWith("A")) .sorted() .collect(Collectors.toList());
五、性能优化建议
初始化容量:
java
javanew ArrayList<>(100); // 避免频繁扩容 new HashMap<>(256, 0.75f);遍历选择:
ArrayList:普通for循环最快LinkedList:使用迭代器HashMap:entrySet遍历最优
避免装箱拆箱:
- 考虑使用
IntArrayList(第三方库)替代ArrayList<Integer>
- 考虑使用
并发控制:
- 读多写少:
CopyOnWriteArrayList - 写多:
ConcurrentHashMap
- 读多写少:
特殊场景:
- 枚举集合:
EnumSet/EnumMap性能最优 - 原始类型集合:考虑Trove、FastUtil等第三方库
- 枚举集合:
六、总结图谱
选择集合的决策树:
是否需要键值对?
├── 是 → 是否需要线程安全?
│ ├── 是 → ConcurrentHashMap
│ └── 否 → 是否需要排序?
│ ├── 是 → TreeMap
│ ├── 否 → 是否需要插入顺序?
│ ├── 是 → LinkedHashMap
│ └── 否 → HashMap
└── 否 → 是否允许重复?
├── 是 → List
│ ├── 查询多 → ArrayList
│ └── 增删多 → LinkedList
└── 否 → Set
├── 需要排序 → TreeSet
├── 需要插入顺序 → LinkedHashSet
└── 常规需求 → HashSetJava集合框架提供了丰富的数据结构实现,理解各集合类的特点及适用场景,能够帮助开发者编写出更高效、更健壮的代码。
