Java异常处理与IO
大约 7 分钟
一、异常体系
异常处理
- 检查性异常:通常由用户错误引起的,在编译时要求处理完成。通常可以使用try catch捕获并处理,或者使用throws抛出可能存在的异常。
- 运行时异常:编译时不要求处理,通常是由于程序中的错误导致的。
- 错误:例如栈的溢出等隐藏性问题。
Java异常语句:
- try:包裹可能会抛出异常的代码块,一个try语句可以对应多个catch语句。
- catch:捕获异常并处理异常的代码块
- finally:无论正常还是异常都会执行的代码块
- throw:手动抛出异常
- throws:在方法声明中指定方法可能抛出的异常
- Exception:所有异常类的父类,提供了一些方法来获取异常信息:getMessage()、printStackTrace()等。
1.1 Throwable 结构
Java 中所有异常和错误的根类是 Throwable:
- Error:严重错误,例如
OutOfMemoryError,一般不捕获、不处理。 - Exception:异常,程序运行中可能出现的问题,一般需要处理。
- 检查型异常(Checked Exception)
- 编译期强制检查,必须显式处理(
try-catch或throws)。 - 如:
IOException、SQLException等。
- 编译期强制检查,必须显式处理(
- 运行时异常(RuntimeException)
- 编译器不强制要求处理,可以选择不写
try-catch。 - 如:
NullPointerException、IndexOutOfBoundsException、IllegalArgumentException等。
- 编译器不强制要求处理,可以选择不写
- 检查型异常(Checked Exception)
二、异常处理:try-catch-finally
2.1 基本语法
try {
// 可能抛出异常的代码
} catch (IOException e) {
// 捕获并处理 IOException
} catch (Exception e) {
// 捕获其它异常(放在后面)
} finally {
// 无论是否发生异常,都会执行
}
注意:
catch是从上到下匹配的,子类异常放在父类异常前面。finally一般用于关闭资源(文件、网络连接、数据库连接等)。
2.2 finally 的执行特性
public int test() {
try {
return 1;
} finally {
System.out.println("finally 一定会执行");
}
}
- 即使
try或catch中有return,finally仍然会执行。 - 不推荐在
finally中写return,容易造成逻辑混乱。
三、throws 与 throw
3.1 throws:声明异常
public void readFile(String path) throws IOException {
// 这里可能抛出 IOException
}
- 用在方法签名上,表示此方法可能抛出某些检查型异常。
- 调用方必须继续处理(继续
throws或try-catch)。
3.2 throw:主动抛出异常
public void setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("年龄不合法");
}
this.age = age;
}
throw后面必须是一个Throwable对象。- 常用于参数校验或业务逻辑中的非法状态。
四、自定义异常
4.1 自定义运行时异常
public class BizException extends RuntimeException {
public BizException(String message) {
super(message);
}
}
- 继承
RuntimeException。 - 调用方可以不强制捕获,适合业务异常。
4.2 自定义检查型异常
public class MyCheckedException extends Exception {
public MyCheckedException(String message) {
super(message);
}
}
- 继承
Exception。 - 必须在方法签名中声明
throws或使用try-catch处理。
五、IO 基础:字节流与字符流
5.1 IO 体系分类
- 按数据单位:
- 字节流:
InputStream/OutputStream,处理二进制数据(图片、音频等)。 - 字符流:
Reader/Writer,处理文本数据,注意字符编码。
- 字节流:
- 按流向:
- 输入流:从外部读入到程序(
InputStream、Reader)。 - 输出流:从程序写出到外部(
OutputStream、Writer)。
- 输入流:从外部读入到程序(
六、字节流:InputStream / OutputStream
6.1 FileInputStream / FileOutputStream
// 读取文件(字节流)
try (FileInputStream fis = new FileInputStream("input.txt")) {
int b;
while ((b = fis.read()) != -1) {
System.out.print((char) b);
}
} catch (IOException e) {
e.printStackTrace();
}
// 写入文件(字节流)
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String content = "Hello IO";
fos.write(content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
6.2 缓冲流:BufferedInputStream / BufferedOutputStream
- 在节点流外面再套一层缓冲流,提高 IO 性能。
try (BufferedInputStream bis =
new BufferedInputStream(new FileInputStream("input.bin"));
BufferedOutputStream bos =
new BufferedOutputStream(new FileOutputStream("output.bin"))) {
byte[] buffer = new byte[1024];
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
七、字符流:Reader / Writer
7.1 FileReader / FileWriter
// 读取文本文件(字符流)
try (FileReader reader = new FileReader("a.txt")) {
int ch;
while ((ch = reader.read()) != -1) {
System.out.print((char) ch);
}
} catch (IOException e) {
e.printStackTrace();
}
// 写入文本文件(字符流)
try (FileWriter writer = new FileWriter("b.txt", true)) { // true 代表追加
writer.write("第一行\n");
writer.write("第二行\n");
} catch (IOException e) {
e.printStackTrace();
}
7.2 BufferedReader / BufferedWriter
try (BufferedReader br =
new BufferedReader(new FileReader("a.txt"));
BufferedWriter bw =
new BufferedWriter(new FileWriter("b.txt"))) {
String line;
while ((line = br.readLine()) != null) {
bw.write(line);
bw.newLine(); // 换行
}
} catch (IOException e) {
e.printStackTrace();
}
八、字节流与字符流之间的转换
8.1 InputStreamReader / OutputStreamWriter
- 将字节流转换为字符流,处理编码问题。
try (BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream("a.txt"), "UTF-8"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
try (BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(
new FileOutputStream("b.txt"), "UTF-8"))) {
bw.write("使用指定编码写文件");
} catch (IOException e) {
e.printStackTrace();
}
九、try-with-resources(自动关闭资源)
在 Java 7 之前,关闭资源通常写在 finally 里:
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("a.txt"));
String line = br.readLine();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Java 7 之后,可以使用 try-with-resources 简化写法:
try (BufferedReader br = new BufferedReader(new FileReader("a.txt"))) {
String line = br.readLine();
System.out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
要求:
- 放在
try()中的资源类型必须实现AutoCloseable接口(大部分 IO、JDBC 等都实现了)。 - 代码执行完毕后会自动调用
close()。
package io;
import java.io.*; // 引入IO包,包含了大部分操作输入、输出的类
public class IoDemo {
public static void main(String[] args) throws IOException { // 需添加异常处理
/*
* IO操作
* */
// 读取字符:read,将System.in包含在BufferedReader对象中来创建一个字符流
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("请输入字符,按下q键退出。");
char c;
do {
c = (char) br.read(); // 一个个读取控制台字符,直到遇到q结束
System.out.println(c);
} while (c != 'q');
// 读取字符串:readLine,读取一整行字符串
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String str;
System.out.println("请输入字符,输入exit退出。");
do {
str = br.readLine();
System.out.println("输入的字符串为:" + str);
if (str.equals("exit")) {
System.out.println("已退出");
}
} while (!str.equals("exit"));
// 控制台输出:System.out.write(),输出字符流
int a = 65;
System.out.write(a); // 输出A,
System.out.write('\n');
System.out.println(a); // 输出65
读写文件:FileInputStream
FileInputStream:从文件中读取数据,入参为path。很适合读取二进制文件,例如图片、音频、视频等,可以进行逐节读取并处理数据。
FileInputStream f = new FileInputStream("/Users/timetravel/code-ba/java-codes/files/enum.json");
int data;
while ((data = f.read()) != -1) {
System.out.print((char) data); // 将字节转换为字符输出,一次只获取一个字符,所以需要使用while
}
// 必须关闭流,以释放系统资源,可以使用try-with-resources语句自动关闭流
f.close();
// 读取文本:通常使用FileReader或BufferedReader,可以更方便处理字符编码和文本行
try (FileReader fis = new FileReader("/Users/timetravel/code-ba/java-codes/files/json/enum.json")) {
int s;
while ((s = fis.read()) != -1) {
System.out.print((char) s);
}
} catch (IOException e) {
// 抛出异常
e.printStackTrace();
}
System.out.println("\n");
// 文件写入:FileOutputStream,如果目标文件不存在,则会创建该文件及对应目录。不建议使用FileOutputStream写入中文,会导致乱码
OutputStream o = new FileOutputStream("/Users/timetravel/code-ba/java-codes/files/json/menu.json");
o.write('A'); // 文件中存在一个A,说明write时采用字节写入
// Demo1:将1.txt内容复制到2.txt中
try (FileReader fis = new FileReader("/Users/timetravel/code-ba/java-codes/files/txt/1.txt")) {
try (OutputStream out = new FileOutputStream("/Users/timetravel/code-ba/java-codes/files/txt/2.txt")) {
int s;
while ((s = fis.read()) != -1) {
out.write((char) s);
System.out.print((char) s);
}
} catch (IOException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("\n");
// Demo2:中文复制,修复可能存在的乱码问题
try (FileReader fis = new FileReader("/Users/timetravel/code-ba/java-codes/files/txt/春晓.txt")) {
try (OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(new File("/Users/timetravel/code-ba/java-codes/files/txt/春晓copy.txt")), "UTF-8")) {
int s;
while ((s = fis.read()) != -1) {
osw.append((char) s);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
} catch (IOException e) {
e.printStackTrace();
}
// Java中的目录操作
// mkdir():创建一个文件夹,目录也是File对象
String dirname = "/Users/timetravel/code-ba/java-codes/files/videos";
File file = new File(dirname);
file.mkdirs();
// 读取目录,获取下属所有文件和目录
if (file.isDirectory()) {
String s[] = file.list();
for (int i = 0; i < s.length; i++) {
File f = new File(dirname + "/" + s[i]);
if (f.isDirectory()) {
System.out.println(s[i] + " 是一个目录");
} else {
System.out.println(s[i] + " 是一个文件");
}
}
}
// 删除目录:file.delete(),需要确保该目录下没有任何文件才能被删除
File dir = new File(dirname);
boolean status = dir.delete();
System.out.println("删除目录状态:" + status);
}
}
十、异常与 IO 使用建议
- 捕获异常时不要空
catch:至少打印日志或抛出业务异常。 finally中尽量不要写return,避免覆盖原有返回值或异常。- 操作文件、网络、数据库等 IO 时,优先使用 try-with-resources 自动关闭资源。
- 读取文本时注意编码,避免乱码(特别是跨平台、跨系统场景)。
Loading...
