家人们谁懂啊!异常处理从入门到封神的骚操作,看这篇就够!
nanshan 2025-05-02 12:24 15 浏览 0 评论
作为Java程序员,异常处理是我们日常开发中不可或缺的重要技能。本文将系统性地介绍Java异常处理的各个方面,从基础概念到高级应用,帮助你全面掌握这一关键技术。
一、异常处理基础概念
1.1 什么是异常?
异常(Exception)是程序在执行过程中发生的意外事件,它会打断正常的指令流。在Java中,异常是一个对象,它封装了错误事件的信息。
通俗理解:就像生活中的意外情况,比如你正开车去上班(正常流程),突然爆胎了(异常),你需要停下来处理这个意外情况。
1.2 异常的分类
Java中的异常可以分为两大类:
异常类型 | 特点 | 继承自 | 处理要求 | 例子 |
Checked Exception (检查型异常) | 编译器强制检查,必须处理 | Exception | 必须捕获或声明抛出 | IOException, SQLException |
Unchecked Exception (非检查型异常) | 编译器不强制检查 | RuntimeException | 可处理也可不处理 | NullPointerException, ArrayIndexOutOfBoundsException |
此外,还有Error类表示严重错误,通常程序无法处理,如OutOfMemoryError。
// 检查型异常示例 - 必须处理
try {
FileInputStream fis = new FileInputStream("test.txt");
} catch (FileNotFoundException e) {
System.out.println("文件未找到: " + e.getMessage());
}
// 非检查型异常示例 - 可不处理(但不推荐)
int[] arr = {1, 2, 3};
System.out.println(arr[3]); // 可能抛出ArrayIndexOutOfBoundsException
二、异常处理机制
Java提供了完善的异常处理机制,主要通过五个关键字实现:try、catch、finally、throw、throws。
2.1 try-catch-finally 基本结构
try {
// 可能抛出异常的代码
riskyOperation();
} catch (SpecificException e) {
// 处理特定异常
System.out.println("处理SpecificException: " + e.getMessage());
} catch (GeneralException e) {
// 处理更一般的异常
System.out.println("处理GeneralException: " + e.getMessage());
} finally {
// 无论是否发生异常都会执行的代码
cleanupResources();
}
通俗理解:就像你尝试做一道复杂菜品(try),如果盐放多了(catch SaltTooMuchException),你可以加水稀释;如果烧焦了(catch BurnedException),你可以重做;最后不管成功与否(finally),你都要清理厨房。
2.2 throw 和 throws
- throw:用于在方法内部主动抛出一个异常对象
- throws:用于方法声明,表示该方法可能抛出的异常类型
// 抛出异常示例
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException("余额不足,当前余额: " + balance);
}
balance -= amount;
}
// 调用该方法时需要处理异常
try {
account.withdraw(1000);
} catch (InsufficientFundsException e) {
System.out.println(e.getMessage());
}
三、常见异常类及处理示例
3.1 常见运行时异常(RuntimeException)详解
异常类 | 触发条件 | 根本原因 | 预防措施 | 处理建议 | 代码示例 |
NullPointerException | 调用null对象的方法或属性 | 未初始化对象或方法返回null | 使用Optional类,进行null检查 | 修复代码逻辑,添加null检查 | String str = null; str.length(); |
ArrayIndexOutOfBoundsException | 访问数组非法索引 | 索引<0或>=数组长度 | 检查数组长度,使用增强for循环 | 验证索引范围 | int[] arr = new int[3]; arr[3] = 1; |
ClassCastException | 错误的类型转换 | 对象实际类型与目标类型不兼容 | 使用instanceof检查 | 先检查再转换 | Object obj = "hello"; Integer num = (Integer)obj; |
IllegalArgumentException | 传递非法参数 | 方法参数不符合要求 | 方法开头验证参数 | 调用前验证参数 | public void setAge(int age) { if(age<0) throw... } |
NumberFormatException | 字符串转数字失败 | 字符串包含非数字字符 | 使用正则表达式验证 | 捕获并提示用户 | Integer.parseInt("12a3"); |
ArithmeticException | 算术运算错误 | 除数为零等数学错误 | 检查除数/模数 | 数学运算前验证 | int x = 5/0; |
IllegalStateException | 对象状态不正确 | 方法调用时机不当 | 设计状态机验证 | 检查对象状态 | iterator.next()(未调用hasNext) |
运行时异常处理示例:
public class RuntimeExceptionsDemo {
public static void main(String[] args) {
// 1. NullPointerException 防护
String text = potentiallyNullMethod();
if (text != null) { // 显式null检查
System.out.println(text.length());
}
// 或使用Java 8 Optional
Optional.ofNullable(potentiallyNullMethod())
.ifPresent(t -> System.out.println(t.length()));
// 2. 数组边界检查
int[] numbers = {1, 2, 3};
int index = 3;
if (index >= 0 && index < numbers.length) {
System.out.println(numbers[index]);
}
// 3. 安全类型转换
Object obj = getSomeObject();
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.toUpperCase());
}
}
private static String potentiallyNullMethod() {
return Math.random() > 0.5 ? "Hello" : null;
}
private static Object getSomeObject() {
return Math.random() > 0.5 ? "Text" : 123;
}
}
3.2 常见检查型异常(Checked Exception)深度解析
异常类 | 典型场景 | 根本原因 | 处理策略 | 恢复方案 | 代码示例 |
IOException | I/O操作失败 | 文件损坏、权限不足、设备故障 | 捕获并记录日志 | 重试或使用备用方案 | Files.readAllBytes(path) |
FileNotFoundException | 文件未找到 | 路径错误、文件不存在 | 验证文件路径 | 提示用户检查路径 | new FileInputStream("missing.txt") |
SQLException | 数据库错误 | SQL语法错误、连接问题、约束冲突 | 事务回滚 | 重连或提示用户 | stmt.executeQuery("SELECT...") |
InterruptedException | 线程中断 | 线程被其他线程中断 | 恢复中断状态 | 清理资源并退出 | Thread.sleep(1000) |
ClassNotFoundException | 类加载失败 | 类路径缺失、版本不匹配 | 检查依赖配置 | 添加必要依赖 | Class.forName("com.example.Missing") |
CloneNotSupportedException | 克隆失败 | 对象未实现Cloneable | 实现Cloneable | 使用其他复制方式 | obj.clone() |
ParseException | 解析失败 | 格式不匹配 | 验证输入格式 | 提示正确格式 | SimpleDateFormat.parse() |
检查型异常处理示例:
public class CheckedExceptionsDemo {
// 文件处理示例
public static void processFile(String filePath) {
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (FileNotFoundException e) {
System.err.println("文件未找到: " + e.getMessage());
// 恢复方案1: 使用默认文件
try {
processFile("default.txt");
} catch (IOException ex) {
System.err.println("连默认文件也无法读取");
}
} catch (IOException e) {
System.err.println("IO错误: " + e.getMessage());
// 恢复方案2: 返回空结果
}
}
// 数据库操作示例
public void updateUserEmail(int userId, String newEmail) {
Connection conn = null;
try {
conn = dataSource.getConnection();
conn.setAutoCommit(false);
PreparedStatement stmt = conn.prepareStatement(
"UPDATE users SET email = ? WHERE id = ?");
stmt.setString(1, newEmail);
stmt.setInt(2, userId);
int affected = stmt.executeUpdate();
if (affected == 0) {
throw new SQLException("用户不存在");
}
conn.commit();
} catch (SQLException e) {
if (conn != null) {
try {
conn.rollback(); // 事务回滚
} catch (SQLException ex) {
System.err.println("回滚失败: " + ex.getMessage());
}
}
throw new DataAccessException("更新用户邮箱失败", e);
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.err.println("关闭连接失败");
}
}
}
}
}
3.3 Error类及其子类深度分析
Error类型 | 触发条件 | 是否可恢复 | JVM状态 | 处理建议 | 典型场景 |
OutOfMemoryError | 堆内存耗尽 | 通常不可恢复 | 不稳定 | 增加堆内存或优化代码 | 加载大文件、内存泄漏 |
StackOverflowError | 调用栈过深 | 可能可恢复 | 线程终止 | 检查递归终止条件 | 无限递归 |
NoClassDefFoundError | 类定义缺失 | 可恢复 | 部分功能失效 | 检查类路径配置 | 运行时缺少依赖 |
LinkageError | 类链接失败 | 通常不可恢复 | 不稳定 | 检查版本兼容性 | 类版本冲突 |
Error处理示例:
public class ErrorHandlingDemo {
// 内存不足防护
public void processLargeData() {
try {
byte[] hugeArray = new byte[Integer.MAX_VALUE]; // 可能抛出OutOfMemoryError
} catch (OutOfMemoryError e) {
System.err.println("内存不足,采用分批处理策略");
processInBatches(); // 降级方案
}
}
// 栈溢出防护
public int recursiveMethod(int n) {
try {
if (n <= 0) return 1;
return n * recursiveMethod(n-1);
} catch (StackOverflowError e) {
System.err.println("递归过深,改用迭代实现");
return iterativeFactorial(n); // 降级方案
}
}
private int iterativeFactorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
}
四、异常处理最佳实践
4.1 异常处理原则
- 具体明确:捕获最具体的异常类型,而不是笼统的Exception
- 早抛出晚捕获:在低层方法中抛出异常,在高层业务逻辑中捕获处理
- 避免空catch块:至少要记录异常信息
- 资源释放:使用try-with-resources确保资源释放
- 异常转化:将低层异常转化为对调用者有意义的异常
4.2 try-with-resources (Java 7+)
自动资源管理语法,简化资源清理代码:
// 传统方式
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("test.txt"));
// 使用资源
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// try-with-resources方式
try (BufferedReader br = new BufferedReader(new FileReader("test.txt"))) {
// 使用资源
} catch (IOException e) {
e.printStackTrace();
}
// 无需finally块,资源会自动关闭
4.3 自定义异常
创建业务相关的异常类可以更好地表达错误情况:
// 自定义异常类
public class InsufficientFundsException extends Exception {
private double shortage;
public InsufficientFundsException(String message, double shortage) {
super(message);
this.shortage = shortage;
}
public double getShortage() {
return shortage;
}
}
// 使用自定义异常
public class BankAccount {
private double balance;
public void withdraw(double amount) throws InsufficientFundsException {
if (amount > balance) {
throw new InsufficientFundsException(
"余额不足,缺少: " + (amount - balance),
amount - balance);
}
balance -= amount;
}
}
4.4 异常处理黄金法则
try {
// 可能抛出异常的代码
} catch (SpecificException e) {
// 1. 记录日志
log.error("Context info", e);
// 2. 考虑恢复或降级
if (canRecover(e)) {
recover();
} else {
// 3. 转换为业务异常
throw new BusinessException("User friendly message", e);
}
} finally {
// 4. 清理资源
closeResources();
}
五、高级异常处理技巧
5.1 异常链
保留原始异常信息,便于问题追踪:
try {
// 某些操作
} catch (IOException e) {
throw new BusinessException("业务处理失败", e); // 将原始异常e传入
}
5.2 多异常捕获 (Java 7+)
try {
// 可能抛出多种异常的代码
} catch (IOException | SQLException e) {
// 统一处理IO和SQL异常
System.out.println("数据访问错误: " + e.getMessage());
}
5.3 异常处理性能考量
异常处理有一定性能开销,应避免在正常流程中使用异常:
// 不推荐 - 使用异常控制流程
try {
while (true) {
list.remove(0);
}
} catch (IndexOutOfBoundsException e) {
// 结束循环
}
// 推荐 - 正常流程控制
while (!list.isEmpty()) {
list.remove(0);
}
六、异常处理对比分析
6.1 检查型异常 vs 非检查型异常
比较维度 | 检查型异常 | 非检查型异常 |
继承关系 | 继承Exception但不继承RuntimeException | 继承RuntimeException |
处理要求 | 必须捕获或声明抛出 | 可处理可不处理 |
使用场景 | 可预见的、可恢复的错误 | 程序错误、不可恢复的错误 |
设计目的 | 强制程序员处理已知可能的问题 | 处理程序bug或系统错误 |
例子 | IOException, SQLException | NullPointerException, ArrayIndexOutOfBoundsException |
6.2 try-catch-finally vs try-with-resources
比较维度 | try-catch-finally | try-with-resources |
语法复杂度 | 较高,需要手动关闭资源 | 简洁,自动关闭资源 |
资源管理 | 需要在finally块中手动关闭 | 自动调用close()方法 |
异常处理 | 可能掩盖原始异常 | 保留原始异常 |
适用版本 | 所有Java版本 | Java 7+ |
适用场景 | 需要精细控制资源释放 | 简单资源管理 |
七、实际应用案例
7.1 用户登录异常处理
public class AuthService {
public User login(String username, String password)
throws AuthException {
if (username == null || password == null) {
throw new IllegalArgumentException("用户名和密码不能为空");
}
try {
User user = userDao.findByUsername(username);
if (user == null) {
throw new UserNotFoundException("用户不存在");
}
if (!user.getPassword().equals(hash(password))) {
throw new WrongPasswordException("密码错误");
}
if (user.isLocked()) {
throw new AccountLockedException("账户已锁定");
}
return user;
} catch (DataAccessException e) {
throw new AuthException("系统错误,请稍后再试", e);
}
}
// 使用示例
public static void main(String[] args) {
AuthService auth = new AuthService();
try {
User user = auth.login("admin", "123456");
System.out.println("登录成功: " + user);
} catch (UserNotFoundException e) {
System.out.println("登录失败: " + e.getMessage());
// 提示用户注册
} catch (WrongPasswordException e) {
System.out.println("登录失败: " + e.getMessage());
// 提示密码错误,剩余尝试次数
} catch (AccountLockedException e) {
System.out.println("登录失败: " + e.getMessage());
// 提示联系管理员
} catch (AuthException e) {
System.out.println("系统错误: " + e.getMessage());
// 记录日志,显示通用错误信息
}
}
}
7.2 文件处理综合示例
import java.io.*;
import java.nio.file.*;
public class FileProcessor {
public void processFile(String inputPath, String outputPath) throws FileProcessException {
Path input = Paths.get(inputPath);
Path output = Paths.get(outputPath);
// 使用try-with-resources自动关闭资源
try (BufferedReader reader = Files.newBufferedReader(input);
BufferedWriter writer = Files.newBufferedWriter(output)) {
String line;
while ((line = reader.readLine()) != null) {
String processed = processLine(line);
writer.write(processed);
writer.newLine();
}
} catch (NoSuchFileException e) {
throw new FileProcessException("文件不存在: " + e.getFile(), e);
} catch (AccessDeniedException e) {
throw new FileProcessException("无访问权限: " + e.getFile(), e);
} catch (IOException e) {
throw new FileProcessException("处理文件时发生IO错误", e);
}
}
private String processLine(String line) {
// 模拟处理逻辑
return line.toUpperCase();
}
// 自定义异常
public static class FileProcessException extends Exception {
public FileProcessException(String message, Throwable cause) {
super(message, cause);
}
}
// 使用示例
public static void main(String[] args) {
FileProcessor processor = new FileProcessor();
try {
processor.processFile("input.txt", "output.txt");
System.out.println("文件处理成功");
} catch (FileProcessException e) {
System.err.println("文件处理失败: " + e.getMessage());
// 打印原始异常堆栈
e.getCause().printStackTrace();
// 根据不同类型提供不同恢复策略
if (e.getCause() instanceof NoSuchFileException) {
System.out.println("请检查文件路径是否正确");
} else if (e.getCause() instanceof AccessDeniedException) {
System.out.println("请检查文件权限");
} else {
System.out.println("系统错误,请联系管理员");
}
}
}
}
八、异常处理常见问题解答
Q1: 什么时候该创建自定义异常?
A: 当以下情况时考虑创建自定义异常:
- Java内置异常无法准确描述你的问题
- 需要携带额外的错误信息
- 希望对特定业务错误进行特殊处理
- 需要统一异常处理逻辑
Q2: 应该在什么层次捕获异常?
A: 通常的指导原则:
- 在能处理异常的最近层次捕获
- 在UI层捕获并展示用户友好的错误信息
- 在服务层捕获并记录日志,可能转换异常类型
- 在DAO层捕获并转换为数据访问异常
Q3: 为什么有时候要包装异常?
A: 包装异常(异常链)的好处:
- 保留完整的错误堆栈信息
- 将低层技术异常转换为高层业务异常
- 避免暴露实现细节
- 统一异常类型便于处理
Q4: 空catch块有什么危害?
A: 空catch块的危害包括:
- 错误被静默忽略,难以排查
- 程序可能处于不一致状态
- 违反快速失败(Fail-fast)原则
- 至少应该记录日志
九、总结
Java异常处理是编写健壮、可靠应用程序的关键技能。通过本文,我们系统地学习了:
- 异常的分类和基本处理机制
- try-catch-finally的正确使用方式
- 检查型异常和非检查型异常的区别与应用场景
- 异常处理的最佳实践和常见陷阱
- 高级特性如try-with-resources和多异常捕获
- 如何设计和实现自定义异常
- 实际项目中的异常处理策略
Java 异常就像代码里的 “不速之客”!try-catch 是防坑结界,finally 负责擦屁股,漏处理分分钟让程序原地 “诈尸”!
家人们谁懂啊!写文写到头秃才整出这些干货!快关注博主,收藏文章,转发给你那还在和代码 “打架” 的怨种兄弟!
相关推荐
- 使用nginx配置域名及禁止直接通过IP访问网站
-
前段时间刚搭建好这个网站,一直没有关注一个问题,那就是IP地址也可以访问我的网站,今天就专门研究了一下nginx配置问题,争取把这个问题研究透彻。1.nginx配置域名及禁止直接通过IP访问先来看n...
- 如何在 Linux 中使用 PID 号查找进程名称?
-
在Linux的复杂世界中,进程是系统运行的核心,每个进程都由一个唯一的「进程ID」(PID)标识。无论是系统管理员在排查失控进程,还是开发者在调试应用程序,知道如何将PID映射到对应的进程名称都是一项...
- Linux服务器硬件信息查询与日常运维命令总结
-
1.服务器硬件信息查询1.1CPU信息查询命令功能描述示例lscpu显示CPU架构、核心数、线程数等lscpucat/proc/cpuinfo详细CPU信息(型号、缓存、频率)cat/proc/c...
- Ubuntu 操作系统常用命令详解(ubuntu常用的50个命令)
-
UbuntuLinux是一款流行的开源操作系统,广泛应用于服务器、开发、学习等场景。命令行是Ubuntu的灵魂,也是高效、稳定管理系统的利器。本文按照各大常用领域,详细总结Ubuntu必学...
- 从 0 到 1:打造基于 Linux 的私有 API 网关平台
-
在当今微服务架构盛行的时代,API网关作为服务入口和安全屏障,其重要性日益凸显。你是否想过,不依赖商业方案,完全基于开源组件,在Linux上构建一个属于自己的私有API网关平台?今天就带你...
- Nginx搭建简单直播服务器(nginx 直播服务器搭建)
-
前言使用Nginx+Nginx-rtmp-module在Ubuntu中搭建简单的rtmp推流直播服务器。服务器环境Ubuntu16.04相关概念RTMP:RTMP协议是RealTi...
- Linux连不上网?远程卡?这篇网络管理指南你不能错过!
-
大家好!今天咱们聊个所有Linux用户都躲不开的“老大难”——网络管理。我猜你肯定遇到过这些崩溃时刻:新装的Linux系统连不上Wi-Fi,急得直拍桌子;远程服务器SSH连不上,提示“Connecti...
- 7天从0到上线!手把手教你用Python Flask打造爆款Web服务
-
一、为什么全网开发者都在疯学Flask?在当今Web开发的战场,Flask就像一把“瑞士军刀”——轻量级架构让新手3天速成,灵活扩展能力又能支撑百万级用户项目!对比Django的“重型装甲”,Flas...
- nginx配置文件详解(nginx反向代理配置详解)
-
Nginx是一个强大的免费开源的HTTP服务器和反向代理服务器。在Web开发项目中,nginx常用作为静态文件服务器处理静态文件,并负责将动态请求转发至应用服务器(如Django,Flask,et...
- 30 分钟搞定 Docker 安装与 Nginx 部署,轻松搭建高效 Web 服务
-
在云计算时代,利用容器技术快速部署应用已成为开发者必备技能。本文将手把手教你在阿里云轻量应用服务器上,通过Docker高效部署Nginx并发布静态网站,全程可视化操作,新手也能轻松上手!一、准...
- Nginx 配置实战:从摸鱼到部署,手把手教你搞定生产级配置
-
各位摸鱼搭子们!今天咱不聊代码里的NullPointerException,改聊点「摸鱼必备生存技能」——Nginx配置!先灵魂拷问一下:写了一堆接口却不会部署?服务器被恶意请求打崩过?静态资源加载...
- 如何使用 Daphne + Nginx + supervisor部署 Django
-
前言:从Django3.0开始支持ASGI应用程序运行,使Django完全具有异步功能。Django目前已经更新到5.0,对异步支持也越来越好。但是,异步功能将仅对在ASGI下运行的应用程序可用...
- Docker命令最全详解(39个最常用命令)
-
Docker是云原生的核心,也是大厂的必备技能,下面我就全面来详解Docker核心命令@mikechen本文作者:陈睿|mikechen文章来源:mikechen.cc一、Docker基本命令doc...
- ubuntu中如何查看是否已经安装了nginx
-
在Ubuntu系统中,可以通过以下几种方法检查是否已安装Nginx:方法1:使用dpkg命令(适用于Debian/Ubuntu)bashdpkg-l|grepnginx输出...
- OVN 概念与实践(德育概念的泛化在理论和实践中有什么弊端?)
-
今天我们来讲解OVN的概念和基础实践,要理解本篇博客的内容,需要前置学习:Linux网络设备-Bridge&VethPairLinux网络设备-Bridge详解OVS+Fa...
你 发表评论:
欢迎- 一周热门
-
-
UOS服务器操作系统防火墙设置(uos20关闭防火墙)
-
极空间如何无损移机,新Z4 Pro又有哪些升级?极空间Z4 Pro深度体验
-
手机如何设置与显示准确时间的详细指南
-
NAS:DS video/DS file/DS photo等群晖移动端APP远程访问的教程
-
如何在安装前及安装后修改黑群晖的Mac地址和Sn系列号
-
如何修复用户配置文件服务在 WINDOWS 上登录失败的问题
-
一加手机与电脑互传文件的便捷方法FileDash
-
日本海上自卫队的军衔制度(日本海上自卫队的军衔制度是什么)
-
10个免费文件中转服务站,分享文件简单方便,你知道几个?
-
爱折腾的特斯拉车主必看!手把手教你TESLAMATE的备份和恢复
-
- 最近发表
-
- 使用nginx配置域名及禁止直接通过IP访问网站
- 如何在 Linux 中使用 PID 号查找进程名称?
- Linux服务器硬件信息查询与日常运维命令总结
- Ubuntu 操作系统常用命令详解(ubuntu常用的50个命令)
- 从 0 到 1:打造基于 Linux 的私有 API 网关平台
- Nginx搭建简单直播服务器(nginx 直播服务器搭建)
- Linux连不上网?远程卡?这篇网络管理指南你不能错过!
- 7天从0到上线!手把手教你用Python Flask打造爆款Web服务
- nginx配置文件详解(nginx反向代理配置详解)
- 30 分钟搞定 Docker 安装与 Nginx 部署,轻松搭建高效 Web 服务
- 标签列表
-
- linux 查询端口号 (58)
- docker映射容器目录到宿主机 (66)
- 杀端口 (60)
- yum更换阿里源 (62)
- internet explorer 增强的安全配置已启用 (65)
- linux自动挂载 (56)
- 禁用selinux (55)
- sysv-rc-conf (69)
- ubuntu防火墙状态查看 (64)
- windows server 2022激活密钥 (56)
- 无法与服务器建立安全连接是什么意思 (74)
- 443/80端口被占用怎么解决 (56)
- ping无法访问目标主机怎么解决 (58)
- fdatasync (59)
- 405 not allowed (56)
- 免备案虚拟主机zxhost (55)
- linux根据pid查看进程 (60)
- dhcp工具 (62)
- mysql 1045 (57)
- 宝塔远程工具 (56)
- ssh服务器拒绝了密码 请再试一次 (56)
- ubuntu卸载docker (56)
- linux查看nginx状态 (63)
- tomcat 乱码 (76)
- 2008r2激活序列号 (65)