JAVA10-IO与异常体系

Java.io是一个基于的输入输出包,其中有两大基类字节流字符流

  • 字节流
    • InputStream
    • OutputStream
  • 字符流
    • Reader
    • Writer

字节流可以让我们方便的操作字节数据,而字符流可以让我们方便的操作字符数据
字节流操作的最小单元是1个字节,而字符流操作的最小单元是1个Unicode字符
字符流还提供了对字节的编码解码的能力

Java中的流

  • FileInputStream
  • FileOutputStream
  • BufferedInputStream
  • BufferedOutputStream
  • FileReader
  • FileWriter
  • BufferedReader
  • BufferedWriter
  • InputStreamReader
  • OutputStreamWriter
  • PrintStream
  • PrintWriter
  • SequenceInputStream
  • ObjectInputStream
  • ObjectOutputStream
  • PipedInputStream
  • PipedOutputStream
  • DataInputStream
  • DataOutputStream
  • ByteArrayInputStream
  • ByteArrayOutputStream

可以看到java.io包中关于流的类一大堆,下面只演示几个基本的使用方法,实际生产中不建议使用这些东西,而是应该使用别人已经写好的经过检验的第三方库,记住不要重复造轮子,这些只作为了解即可

FileInputStream

最基础的文件读取

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Main {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("./Main.java");
        do {
            int b = fileInputStream.read();
            if (b == -1) {
                break;
            }
            System.out.print((char) b);
        } while (true);
    }
}

FileOutputStream

最基础的文件写入

1
2
3
4
5
6
public class Main {
    public static void main(String[] args) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("./Test.java");
        fileOutputStream.write("Hello World".getBytes(StandardCharsets.UTF_8));
    }
}

BufferedInputStream

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream("demo.txt"));
        do {
            int read = bufferedInputStream.read();
            if (read == -1) {
                break;
            }
            System.out.print((char) read);
        } while (true);
    }
}

BufferedOutputStream

1
2
3
4
5
6
7
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream("demo.txt"));
        bufferedOutputStream.write("Hello World!".getBytes(StandardCharsets.UTF_8));
        bufferedOutputStream.flush();
    }
}

FileReader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Main {
    public static void main(String[] args) throws IOException {
        FileReader fileReader = new FileReader("pom.xml");
        do {
            int ch = fileReader.read();
            if (ch == -1) {
                break;
            }
            System.out.print((char) ch);
        } while (true);
    }
}

FileWriter

字符写入流默认有8K的缓冲区

1
2
3
4
5
6
7
public class Main {
    public static void main(String[] args) throws IOException {
        FileWriter fileWriter = new FileWriter("demo.txt");
        fileWriter.write("Hello World!");
        fileWriter.flush();
    }
}

BufferedReader

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader bufferedReader = new BufferedReader(new FileReader("demo.txt"));
        do {
            String line = bufferedReader.readLine();
            if (line == null) {
                break;
            }
            System.out.println(line);
        } while (true);
    }
}

BufferedWriter

1
2
3
4
5
6
7
public class Main {
    public static void main(String[] args) throws IOException {
        BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("demo.txt"));
        bufferedWriter.write("Hello Java!");
        bufferedWriter.flush();
    }
}

File

老师说Java中,File类代表一个文件路径,因为它可以表示一个文件目录,甚至是一个不存在的,但我还是觉得把它想象成一个文件对象比较好理解,因为文件路径实在是想不到那去,正常思维都觉得,它应该代表一个文件对象,所以这个就这么理解算了,但你要知道它其实是代表文件路径

  • File的常用方法
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
public class Main {
    public static void main(String[] args) throws IOException {
        File file = new File("src/main");
        System.out.println(file.isDirectory()); // 判断是否是文件夹
        System.out.println(file.isFile()); // 判断是否是文件
        System.out.println(file.exists()); // 判断是否存在
        System.out.println(file.getAbsolutePath()); // 获取绝对路径
        // listFiles 列出目录下的文件
        for (File f : Objects.requireNonNull(file.listFiles())) {
            System.out.println(f.getName()); // 获取文件名
        }
        System.out.println(file.isAbsolute()); // 判断是否是绝对路径
        System.out.println(file.length()); // 获取文件长度(大小)
        System.out.println(file.getParentFile()); // 获取父级File对象
        System.out.println(file.getParent()); // 获取父级名字
        // 一个重要构造器,根据一个父目录,获取一个子文件或目录对象
        File file2 = new File("src/main/java/com/github/wjinlei", "Main.java");
    }
}

NIO

关于NIO有两种说法

  1. New IO(新IO)
  2. Non-blocking IO(非阻塞IO)

NIOJAVA 7 +引入的东西,NIO中有一个Path类,你可以把它理解为之前的File
PathFile可以相互转换,通过toPath方法和toFile方法
NIO中引入了一个Files工具类,可以了解下

  • Files.write
  • Files.walkFileTree
  • Files.readAllBytes
  • Files.readAllLines
  • Files.copy
  • Files.createDirectory
  • Files.createFile
  • Files.createLink
  • Files.createSymbolicLink
  • Files.createTempDirectory
  • Files.createTempFile
  • Files.delete
  • Files.deleteIfExists
  • Files.exists
  • Files.find
  • Files.getAttribute
  • Files.getOwner
  • Files.isDirectory
  • Files.isHidden
  • ...

NIO的引入就解决了File的不好理解问题,所以如果你想把File理解成路径,那么用Path就比较好理解了,NIO的引入主要是解决了基于流的Stream模型的问题,因为我们知道就像一个水管写入时,必须等前面一个字符写完了,才能接着写后面的字符,它没办法并发写入,因此NIO的引入将基于流的Stream模型,改成了基于块Block的模型,它提供了并发的写入数据的能力

优秀的第三方IO工具类

FileUtils

读一个文件,这他妈读文件多方便,还有很多方法可以自行研究下

1
2
3
4
5
6
7
8
9
public class Main {
    public static void main(String[] args) throws IOException {
        String result = FileUtils.readFileToString(new File("demo.txt"), StandardCharsets.UTF_8);
        System.out.println(result);

        List<String> results = FileUtils.readLines(new File("demo.txt"), StandardCharsets.UTF_8);
        results.forEach(System.out::println);
    }
}

IOUtils

将一个字节流的数据读取到字符串,还有很多方法可以自行研究下串

1
2
3
4
5
6
7
public class Main {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("demo.txt");
        String result = IOUtils.toString(fileInputStream, StandardCharsets.UTF_8);
        System.out.println(result);
    }
}

Java的异常体系

throws(声明该方法未来可能会抛出某个异常)

在Java中,异常是程序不正常的表现,一般情况下,当程序发生一个异常时,我们要么处理它要么抛出它,一个方法如果声明了throws XXXException那么这个方法的调用者也只有两种选择,即要么处理它要么继续抛出它

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
    public static void main(String[] args) {
        // 使用 try...catch 语句抓住异常并处理
        try {
            a();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 继续抛出异常
    public static void a() throws IOException {
        b();
    }

    // 抛出IOException
    public static void b() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("demo.txt");
        fileInputStream.close();
    }
}

throw(主动抛出异常)

异常还可以主动抛出,有些时候我们希望程序在某种情况下,停止运行并给出一个异常信息,我们就可以通过throw,语句来主动抛出一个异常,并终止当前方法的执行,throw语句会击穿所有的栈帧,如果它的上级没有处理这个异常,那么将会一直沿着栈向下抛出,直到遇到处理它的方法为止

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
    public static void main(String[] args) {
        a();
    }

    public static void a() {
        try {
            b();
        } catch (RuntimeException e) {
            e.printStackTrace();
        }
    }

    public static void b() {
        c();
    }

    public static void c() {
        d();
    }

    public static void d() {
        throw new RuntimeException("C");
    }
}

异常的处理

我们可以通过try...catch语句来捕捉被调用者抛出来的异常finally代码块是无论如何都会执行的,因此它通常用于做一些最后的清理工作try...catch语句不一定非要写catch,也可以try...finallyfinally也不是必须的你可以只写try...catch,也可以只写try...finally更可以三个都写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Main {
    public static void main(String[] args) {
        try {
            a();
        } catch (RuntimeException e) {
            System.out.println("RuntimeException");
        } finally {
            System.out.println("finally");
        }
    }

    public static void a() {
        throw new RuntimeException("a is run...");
    }
}

try-with-resources

Java 7+引入的语法糖,它可以简化我们的代码,并且自动帮我们完成资源的释放

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class Main {
    public static void main(String[] args) {
        /*
        bufferedWriter 会被自动close
        声明在try括号里的最后会被自动释放,但有个前提
        前提是得实现了AutoCloseable接口
         */
        try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("demo.txt"))) {
            bufferedWriter.write("Hello Java!");
            bufferedWriter.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

throw 和 throws 的区别

  • throw
    1. 抛出一个异常
  • throws
    1. 只是一个声明
    2. throws 没有必要声明无毒的异常,换句话说throws声明的都应该是有毒的异常

Java 异常的分类

  • Throwable (有毒)
    • 一旦方法抛出了该异常体系下的类,调用者要么处理要么继续抛出,具备传染性
    • Throwable 代表了一个类是否具备可抛性,换句话说就是能不能被throw语句使用
  • Exception (有毒)
    • 一旦方法抛出了该异常体系下的类,调用者要么处理要么继续抛出,具备传染性
    • Exception 代表了一种预期错误,就是我知道可能会抛出某异常,因此你需要显示的处理它
  • RuntimeException (无毒)
    • 就算方法抛出了该异常体系下的类,调用者不需要继续抛出,而且可以根据需要选择性处理,不具备传染性
    • RuntimeException 代表了运行时异常,换句话说就是只要出现了这个异常就是个bug,它不是预料之中的
      因此你没办法显示的处理它,所以它不具备传染性
  • Error (无毒)
    • 就算方法抛出了该异常体系下的类,调用者不需要继续抛出,而且可以根据需要选择性处理,不具备传染性
    • Error 代表了错误,这是非常严重的问题,是不可恢复的,它也不是预料之中的,所以也不具备传染性

异常处理的原则

  • 能用if...else处理的一定不要用try...catch
  • 尽早的抛出异常
  • 异常要准确,并带有详细信息
  • 能抛出异常一定要抛出,不能若无其事的继续运行,哪怕你抛出异常也比执行错误的逻辑好的多
  • 不归你管的异常,自己最好不要处理,而是继续向上抛出
  • 如果有资源待释放的情况,最好处理异常后再向上抛出,而不要直接抛出
  • 如非万分必要,不要忽略异常

一个反例,下面这段程序捕捉了异常,但只是打印了一下堆栈信息就若无其事的继续返回了result,这是非常错误的做法,因为你不能得到正确的结果,继续程序只会带来无法预料的后果,因此一旦遇到异常,应该想想这个方法能不能处理这个,异常,如果不能处理,就应该把这个异常继续向上抛出,无论如何不能假装若无其事的继续你的程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
    public static void main(String[] args) {
        List<String> list = readFile("demo.txt");
        System.out.println(list);
    }

    public static List<String> readFile(String filepath) {
        List<String> result = new ArrayList<>();
        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(filepath));
            while (true) {
                String line = bufferedReader.readLine();
                if (line == null) break;
                result.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }
}

这是一个正例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class Main {
    public static void main(String[] args) {
        List<String> list = readFile2("demo.txt");
        System.out.println(list);
    }

    /* 直接将异常向上抛出 */
    public static List<String> readFile1(String filepath) throws IOException {
        List<String> result = new ArrayList<>();
        BufferedReader bufferedReader = new BufferedReader(new FileReader(filepath));
        while (true) {
            String line = bufferedReader.readLine();
            if (line == null) break;
            result.add(line);
        }
        return result;
    }

    /* 包装一下再抛出 */
    public static List<String> readFile2(String filepath) {
        List<String> result = new ArrayList<>();
        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(filepath));
            while (true) {
                String line = bufferedReader.readLine();
                if (line == null) break;
                result.add(line);
            }
        } catch (IOException e) {
            throw new ReadFileException("读取文件失败", e);
        }
        return result;
    }

    static class ReadFileException extends RuntimeException {
        public ReadFileException(String message, Throwable cause) {
            super(message, cause);
        }
    }
}
updatedupdated2025-03-012025-03-01