Java I/O 原理分析

2020-01-05  本文已影响0人  z白依

IO 是什么


IO 怎么用?


插管1

了解了是什么,接下来看怎么用。很简单,如图,就是插管子,也就是用流,对流进行操作。比如:往文件(外部)上插一根输出管 new FileOutputStream("文件路径") ,然后 “内部” 往管子上写数据。

try {
    FileOutputStream outputStream = new FileOutputStream("./new.txt")
    outputStream.write('a');
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

上面代码就是一个最简单的输出操作,当然这是不完整。大家都知道,文件打开了就必须要关闭,那么什么是文件打开,什么是文件关闭,为什么要关闭呢?

那么为什么要关闭就明显了,然后把关闭给加上就是下面这样了:

FileOutputStream outputStream = null;
try {
    outputStream = new FileOutputStream("./new.txt");
    outputStream.write('a');
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (outputStream != null) {
        try {
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

为什么要写在 finally 而不写在 try 里面呢?因为如果代码有问题就有可能执行不到 close() 了。

但这样好麻烦啊,而且都是固定代码,又不能减少。别着急,Java7 引入了新方式,在 try 里面就可以直接做回收。如下:

try (FileOutputStream outputStream = new FileOutputStream("./new.txt")) {
    outputStream.write('a');
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

当然,能写在 try() 里面的有个限制,必须是实现了 Closeable 接口的类。

能插输出的管子来写,肯定也能插输入的管子来读:

try (FileInputStream inputStream = new FileInputStream("./new.txt")) {
    System.out.print((char) inputStream.read());
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

接下来介绍插多根管子的情况,看图:


插管2

输出,写的操作

try (FileOutputStream outputStream = new FileOutputStream("./new.txt");
     OutputStreamWriter writer = new OutputStreamWriter(outputStream);
     BufferedWriter bufferedWriter = new BufferedWriter(writer)) {
    bufferedWriter.write("x");
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

输入,读的操作

try (FileInputStream inputStream = new FileInputStream("./new.txt");
     InputStreamReader reader = new InputStreamReader(inputStream);
     BufferedReader bufferedReader = new BufferedReader(reader)) {
    System.out.println(bufferedReader.readLine());
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

可以看到这就是一个管道插在另一个管道上,最后插在文件上,进行文件的读写操作。代码中用到了缓冲(buffer),在 BufferedReader 和 BufferedWriter 源码中都会有定义这个缓冲的大小:

...
private static int defaultCharBufferSize = 8192;
...

就上面的代码而言,BufferedReader 用到了缓冲,当然 BufferedWriter 也用了缓冲,只有当 buffer 中的数据大小达到 8192 个的时候才会往文件中写。

那如果是自动的话,是不是说每次都可以不写 flush 呢?也不是,接下来介绍一种要用到 flush 的情况:

// 模拟一个服务器,读到什么内容就写什么内容返回
try {
    ServerSocket serverSocket = new ServerSocket(8080);
    // 等待别人的请求,阻塞式的
    Socket socket = serverSocket.accept();
    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    String data;
    while (true) {
        data = reader.readLine();
        writer.write(data);
        // 冲马桶行为。这个时候就不能等关闭的时候自动冲了。
        writer.flush();
    }
} catch (IOException e) {
    e.printStackTrace();
}

最后说一下文件的复制,怎么做文件复制呢?原理就是一个字节一个字节的搬。从一个文件读数据,写到另一个文件去。

try (FileOutputStream outputStream = new FileOutputStream("./new_copy.txt");
     FileInputStream inputStream = new FileInputStream("./new.txt")) {
    byte[] bytes = new byte[1024];
    int size;
    // 每次记录读取到的数据 size
    while ((size = inputStream.read(bytes)) != -1) {
        outputStream.write(bytes, 0, size);
    }
} catch (FileNotFoundException e) {
    e.printStackTrace();
} catch (IOException e) {
    e.printStackTrace();
}

这里需要考虑一个情况,当读到最后一次,剩余数据的大小不足1024的时候,bytes 中会有残留的上一次的数据。比如:new.txt 中一共有 1025 个,第一次读了1024,第二次读的时候本应该只有一个,但是除了 0 位置上的数据变了之外,后面所有的数据还是上一次读到的数据。所以要记录读到的大小。

总结:


本文介绍了 Java I/O 是什么,怎么用,原理就是插管子(通过流进行对文件或网络的读写操作)。也可以往管子上再插管子。理解了这些,我相信以后再也不要在用到 I/O 的时候到网上复制粘贴了。

(ps:原本来准备把 NIO原理,Okio使用也做个总结,想不到,想不到。。。输出比输入难啊 - -)

上一篇下一篇

猜你喜欢

热点阅读