Java异常处理与IO

时游大约 7 分钟

一、异常体系

异常处理

  1. 检查性异常:通常由用户错误引起的,在编译时要求处理完成。通常可以使用try catch捕获并处理,或者使用throws抛出可能存在的异常。
  2. 运行时异常:编译时不要求处理,通常是由于程序中的错误导致的。
  3. 错误:例如栈的溢出等隐藏性问题。

Java异常语句:

  1. try:包裹可能会抛出异常的代码块,一个try语句可以对应多个catch语句。
  2. catch:捕获异常并处理异常的代码块
  3. finally:无论正常还是异常都会执行的代码块
  4. throw:手动抛出异常
  5. throws:在方法声明中指定方法可能抛出的异常
  6. Exception:所有异常类的父类,提供了一些方法来获取异常信息:getMessage()、printStackTrace()等。

1.1 Throwable 结构

Java 中所有异常和错误的根类是 Throwable

  • Error:严重错误,例如 OutOfMemoryError,一般不捕获、不处理。
  • Exception:异常,程序运行中可能出现的问题,一般需要处理。
    • 检查型异常(Checked Exception)
      • 编译期强制检查,必须显式处理(try-catchthrows)。
      • 如:IOExceptionSQLException 等。
    • 运行时异常(RuntimeException)
      • 编译器不强制要求处理,可以选择不写 try-catch
      • 如:NullPointerExceptionIndexOutOfBoundsExceptionIllegalArgumentException 等。

二、异常处理: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 一定会执行");
    }
}
  • 即使 trycatch 中有 returnfinally 仍然会执行。
  • 不推荐在 finally 中写 return,容易造成逻辑混乱。

三、throws 与 throw

3.1 throws:声明异常

public void readFile(String path) throws IOException {
    // 这里可能抛出 IOException
}
  • 用在方法签名上,表示此方法可能抛出某些检查型异常。
  • 调用方必须继续处理(继续 throwstry-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,处理文本数据,注意字符编码。
  • 按流向:
    • 输入流:从外部读入到程序(InputStreamReader)。
    • 输出流:从程序写出到外部(OutputStreamWriter)。

六、字节流: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 自动关闭资源。
  • 读取文本时注意编码,避免乱码(特别是跨平台、跨系统场景)。
上次编辑于:
贡献者: 15327360835
Loading...