IO流顺序读写数据,单向流动,称IO流。IO流以byte(字节)为最小单位,因此也称为字节流。
快速引入
InputStream / OutputStream
在Java中,InputStream代表输入字节流,OuputStream代表输出字节流,这是最基本的两种IO流。
IO流以byte(字节)为最小单位,因此也称为字节流。
Reader / Writer
Java提供了Reader和Writer表示字符流,字符流传输的最小数据单位是char。
Reader和Writer本质上是一个能自动编解码的InputStream和OutputStream。
究竟使用Reader还是InputStream,要取决于具体的使用场景。如果数据源不是文本,就只能使用InputStream,如果数据源是文本,使用Reader更方便一些。Writer和OutputStream是类似的。
同步和异步
同步IO是,读写IO时代码必须等待数据返回后才继续执行后续代码,优点是代码编写简单,缺点是CPU执行效率低。
异步IO是,读写IO时仅发出请求,然后立刻执行后续代码,优点是CPU执行效率高,缺点是代码编写复杂。
Java标准库的包java.io提供了同步IO,而java.nio则是异步IO。
InputStream、OutputStream、Reader和Writer都是同步IO的抽象类,对应的具体实现类,以文件为例,有FileInputStream、FileOutputStream、FileReader和FileWriter。
File
Java的标准库java.io提供了File对象来操作文件和目录。
1 | import java.io.*; |
构造File对象时,既可以传入绝对路径,也可以传入相对路径。
Windows平台使用\作为路径分隔符,在Java字符串中需要用\\表示一个\,前一个\表示转义。
Linux平台使用/作为路径分隔符
1 | File f = new File("/usr/bin/javac"); |
File对象有3种形式表示的路径,一种是getPath(),返回构造方法传入的路径,一种是getAbsolutePath(),返回绝对路径,一种是getCanonicalPath,它和绝对路径类似,但是返回的是规范路径。
规范路径就是把.和..转换成标准路径
因为Windows和Linux的路径分隔符不同,File对象有一个静态变量用于表示当前平台的系统分隔符:
1 | System.out.println(File.separator); // 根据当前平台打印"\"或"/" |
文件和目录
调用isFile(),判断该File对象是否是一个已存在的文件,调用isDirectory(),判断该File对象是否是一个已存在的目录。
用File对象获取到一个文件时,还可以进一步判断文件的权限和大小:
boolean canRead():是否可读;boolean canWrite():是否可写;boolean canExecute():是否可执行;long length():文件字节大小。
对目录而言,是否可执行表示能否列出它包含的文件和子目录。
创建和删除文件
createNewFile()创建新文件,delete()删除文件
1 | File file = new File("/path/file"); |
File对象提供了createTempFile()来创建一个临时文件,以及deleteOnExit()在JVM退出时自动删除该文件。
1 | import java.io.*; |
遍历文件和目录
File对象表示一个目录时,可以使用list()和listFiles()列出目录下的文件和子目录名。
1 | import java.io.*; |
File对象如果表示一个目录,可以通过以下方法创建和删除目录:
boolean mkdir():创建当前File对象表示的目录;boolean mkdirs():创建当前File对象表示的目录,并在必要时将不存在的父目录也创建出来;boolean delete():删除当前File对象表示的目录,当前目录必须为空才能删除成功。
Path
位于java.nio.file包
1 | import java.io.*; |
使用path适用于目录复杂的拼接、遍历等。
inputStream
java.io包提供了所有同步IO的功能。InputStream就是Java标准库提供的最基本的输入流。
FileInputStream就是从文件流中读取数据。
1 | public void readFile() throws IOException { |
InputStream和OutputStream都是通过close()方法来关闭流。
所有与IO操作相关的代码都必须正确处理IOException。
需要用try ... finally来保证InputStream在无论是否发生IO错误的时候都能够正确地关闭:
1 | public void readFile() throws IOException { |
利用Java 7引入的新的try(resource)的语法,只需要编写try语句,让编译器自动为我们关闭资源。推荐的写法如下:
1 | public void readFile() throws IOException { |
try(resource = ...)中的对象是否实现了java.lang.AutoCloseable接口,如果实现了,就自动加上finally语句并调用close()方法。InputStream和OutputStream都实现了这个接口。
InputStream实现类
1 | import java.io.*; |
OutputStream
OutputStream还提供了一个flush()方法,它的目的是将缓冲区的内容真正输出到目的地,能强制把缓冲区内容输出。
OutputStream实现类
1 | import java.io.*; |
操作zip文件
ZipInputStream是一种FilterInputStream,它可以直接读取zip包的内容,JarInputStream是从ZipInputStream派生,它增加的主要功能是直接读取jar文件里面的MANIFEST.MF文件。
读取zip包
我们要创建一个ZipInputStream,通常是传入一个FileInputStream作为数据源,然后,循环调用getNextEntry(),直到返回null,表示zip流结束。
一个ZipEntry表示一个压缩文件或目录,如果是压缩文件,我们就用read()方法不断读取,直到返回-1:
1 | try (ZipInputStream zip = new ZipInputStream(new FileInputStream(...))) { |
写入zip包
ZipOutputStream是一种FilterOutputStream,它可以直接写入内容到zip包。我们要先创建一个ZipOutputStream,通常是包装一个FileOutputStream,然后,每写入一个文件前,先调用putNextEntry(),然后用write()写入byte[]数据,写入完毕后调用closeEntry()结束这个文件的打包。
1 | try (ZipOutputStream zip = new ZipOutputStream(new FileOutputStream(...))) { |
读取classpath资源
1 | try (InputStream input = getClass().getResourceAsStream("/default.properties")) { |
调用getResourceAsStream()就可以直接从classpath读取任意的资源文件。
序列化
1 | 序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。 |
一个Java对象要能序列化,必须实现一个特殊的java.io.Serializable接口,它的定义如下:
1 | public interface Serializable { |
Serializable接口没有定义任何方法,它是一个空接口。
序列化
1 | import java.io.*; |
反序列化
1 | try (ObjectInputStream input = new ObjectInputStream(...)) { |
Java的序列化允许class定义一个特殊的serialVersionUID静态变量,用于标识Java类的序列化“版本”,通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID的值,这样就能自动阻止不匹配的class版本:
1 | public class Person implements Serializable { |
要特别注意反序列化的几个重要特点:
反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。
通用的序列化方法,例如JSON。
发布时间: 2019-09-07
最后更新: 2019-09-15
本文链接: https://juoyo.github.io/posts/9d0114be.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!