流
在 Java API 中,可以从中读取一个字节序列的对象叫做 输入流,可以向其中写入一个字节序列的对象叫做 输出流。这些字节序列的来源地和目的地可以是文件、网络连接、内存块等。
抽象类 InputStream
和 OutputStream
是构成 输入/输出(I/O)类层次结构的基础。
由于面向字节的流不便处理 Unicode 字符,于是衍生出了字符流,抽象类 Reader
和 Writer
是构成字符流的基础。
分类
- 按流的来源地与目的地(读还是写)划分
输入流:从来源地读取数据;有 InputStream
和 Reader
。
输出流:将数据写入目的地;有 OutputStream
和 Writer
。
- 按流的操作粒度划分
字节流:以字节为单元;有 InputStream
和 OutputStream
。
字符串:以字符为单位;有 Reader
和 Writer
。
- | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
Java I/O 流家族:
Java IO 的基本用法
字节流
字节流对应的超类是 IntputStream
和 OutputStream
,这两个类不能创建实例,平时使用是选用具体相应的子类来处理的,最常用的类是 FileIntputStream
和 FileOutputStream
。
1. IntputStream
类详细说明:
ObjectInputStream
:对象输入流,用来提供对 基本数据类型 或 对象 的持久存储。
FileInputStream
:文件输入流,用于对文件进行读取操作,很常用。
FilterInputStream
:装饰者类,具体的装饰者继承该类,这些类都是处理类,用来对节点类进行封装,实现一些特殊功能。
BufferedInputStream
:缓存流,内部会有一个缓冲区,用来存放字节,每次将缓存区存满再发送,而不是一个字节或多个字节这样发送,效率更高。
DataInputStream
:数据输入流,用来装饰其他输入流。
ByteArrayInputStream
:字节数组输入流,从字节数组(byte[])中进行以字节为单位的读取,将资源文件以字节的形式读取到该类中的字节数组中。
PipedInputStream
:管道字节输入流,能实现多线程间的管道通信。
常用方法:
// 从数据中读入一个字节,并返回该字节
abstract int read()
// 读入一个字节数组,并返回实际读入的字节数,最多读入 b.length 个字节
int read(byte[] b)
// 读入一个字节数组,返回实际读入的字节数
int read(byte[] b, int off, int len)
// 在输入流中跳过 n 个字节
long skip(long n)
// 关闭这个输入流
void close()
2. OutputStream
各个类的说明请参考 InputStream
,一个是输入,另一个是输出。
常用方法:
// 将指定的字节写入输出流
abstract void write(int b)
// 将字节数组写入输出流
write(byte[] b)
// 将某个范围的字节写入输出流
write(byte b[], int off, int len)
// 冲刷输出流,也就是将所有缓存的数据发送到目的地
void flush()
// 关闭这个输出流
void close()
由于
InputStream
、OutputStream
、Reader
、Writer
都实现了Closeable
接口,我们可以使用try-with-resource
语句,无需手动关闭流,流使用结束后会自动关闭。因此,以下示例代码均使用
try-with-resource
语句。
(1)使用字节流(FileInputStream)读文件
public static void main(String[] args) {
File file = new File("R:\\lanweihong.txt");
try (InputStream inputStream = new FileInputStream(file)) {
byte[] bytes = new byte[(int)file.length()];
// 读取数据
int size = inputStream.read(bytes);
System.out.println("Size:" + size);
String text = new String(bytes);
System.out.println("Text:" + text);
} catch (Exception e) {
e.printStackTrace();
}
}
(2)使用字节流(OutputStream)写文件
public static void main(String[] args) {
File file = new File("R:\\lanweihong.txt");
try (OutputStream outputStream = new FileOutputStream(file)) {
String text = "blog.lanweihong.com";
// 输出到文件
outputStream.write(text.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
e.printStackTrace();
}
}
(3)使用字节流拷贝文件
public static void main(String[] args) {
File sourceFile = new File("R:\\lanweihong.txt");
File targetFile = new File("R:\\www.txt");
try (InputStream inputStream = new FileInputStream(sourceFile);
OutputStream outputStream = new FileOutputStream(targetFile)) {
byte[] bytes = new byte[1024];
int len;
while ((len = inputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, len);
}
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
(4)使用 BufferedInputStream 和 BufferedOutputStream
缓冲字节流是为高效率而设计的,真正的读写操作还是靠 FileOutputStream
和 FileInputStream
。
public static void main(String[] args) {
File sourceFile = new File("R:\\lanweihong.txt");
File targetFile = new File("R:\\www.txt");
try (InputStream inputStream = new FileInputStream(sourceFile);
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
OutputStream outputStream = new FileOutputStream(targetFile);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
// 一次读取的字节数
byte[] bytes = new byte[1024];
int len = 0;
// 读取
while ((len = bufferedInputStream.read(bytes)) != -1) {
// 写入
bufferedOutputStream.write(bytes, 0, len);
}
bufferedOutputStream.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
字符流
字符流对应的超类是 Reader
和 Writer
,常用的子类有 FileReader
、FileWriter
。
1. Reader
类详细说明:
InputStreamReader
:从字节流到字符流的桥梁(InputStreamReader
构造器参数是 FileInputStream
的实例对象),它读取字节并使用指定的字符集将其编码为字符。
BufferedReader
:从字符输入流中读取文本,设置一个缓冲区来提高效率。
FileReader
:用于读取字符文件的便利类。
PipedReader
:管道字符输入流,实现多线程间的管道通信。
CharArrayReader
:从 Char
数组中读取数据的介质流。
StringReader
:从String
中读取数据的介质流。
常用方法:
// 读取单个字符
void read()
// 将字符读入数组
int read(char[] cbuf)
// 将字符读入数组某个范围
abstract int read(char[] cbuf, int off, int len)
// 跳过字符
long skip(long n)
// 关闭输入流
abstract void close()
2. Writer
各个类的说明请参考 Reader
,方向相反而已。
常用方法:
// 写入单个字符
void write(int c)
// 写入字符数组
void write(char[] cbuf)
// 写入字符数组的某一范围
abstract void write(char[] cbuf, int off, int len)
// 写入字符串
void write(String str)
// 写入字符串的某一范围
void write(String str, int off, int len)
// 将字符追加到 Writer
Writer append(CharSequence csq)
// 将字符的某一范围追加到 Writer
Writer append(CharSequence csq, int start, int end)
// 将字符追加到 Writer
Writer append(char c)
// 冲刷输出流,也就是将所有缓存的数据发送到目的地
abstract void flush()
// 关闭这个输出流
abstract void close()
(1)以下是 FileReader 和 FileWriter 读取和写入的例子:
public static void main(String[] args) {
try (FileReader reader = new FileReader("R:\\reader.txt");
FileWriter writer = new FileWriter("R:\\writer.txt")) {
int read;
// 读取
while ((read = reader.read()) != -1) {
// 写入
writer.write(read);
}
} catch (Exception e) {
e.printStackTrace();
}
}
(2)使用 BufferedReader 与 BufferedWriter 读取和写入的例子:
public static void main(String[] args) {
File sourceFile = new File("R:\\lanweihong.txt");
File targetFile = new File("R:\\www.txt");
try(BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile))) {
String line;
// 按行读取字符(不包括换行符)
while ((line = reader.readLine()) != null) {
// 写入字符
writer.write(line);
// 写入换行符
writer.newLine();
}
writer.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
转换流
转换流是什么?简单理解就是字节流转为字符流或字符流转为字节流,详细的可看 史上最骚最全最详细的IO流教程,没有之一!。
1. InputStreamReader
InputStreamReader
是字节流到字符流的桥梁,它是 Reader
的子类。它读取字节,并使用指定的字符集将其解码为字符。字符集可由名称指定,也可接受平台默认字符集。
使用方法:
InputStreamReader isr = new InputStreamReader(new FileInputStream("R:\\lanweihong.txt"));
// 指定字符集
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("R:\\lanweihong.txt"), StandardCharsets.UTF_8);
2. OutputStreamWriter
OutputStreamWriter
是字符流到字节流的桥梁,是 Writer
的子类。它使用指定的字符集将字符编码为字节。字符集可由名称指定,也可接受平台默认字符集。
使用方法:
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("R:\\lanweihong.txt"));
// 指定字符集
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("R:\\lanweihong.txt"), StandardCharsets.UTF_8);
序列化
Java 提供了一种对象序列化的机制。可以用一个字节序列表示一个对象,该字节序列包含该对象的数据、类型、属性等信息。字节序列写到文件后,相当于在文件中持久保存了一个对象信息。
反之,该字节序列可以从文件中读取并转换为对象,对它进行反序列化。
如果想要类能够序列化和反序列化,则类必须实现 java.io.Serializable
接口。
1. ObjectInputStream
ObjectInputStream
反序列化流,将之前使用 ObjectOutputStream
序列化的原始数据恢复为对象。
新建 User
对象,其中 password
属性使用 transient
声明,表示该属性不可序列化,序列化时不会输出此属性信息。
public class User implements Serializable {
private static final long serialVersionUID = 1948088770867387338L;
private String username;
/**
* 使用 transient 声明表示该属性不可序列化,如平时开发开为确保密码安全,不进行序列化
*/
private transient String password;
private String email;
// getter and setter...
}
对象序列化:
public static void main(String[] args) {
User user = new User();
user.setUsername("lanweihong");
user.setPassword("123456");
user.setEmail("986310747@qq.com");
try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("R:\\user.txt"))) {
// 写出对象
objectOutputStream.writeObject(user);
} catch (Exception e) {
e.printStackTrace();
}
}
2. ObjectOutputStream
对象反序列化:
public static void main(String[] args) {
try (ObjectInputStream objectInputStream = new java.io.ObjectInputStream(new FileInputStream("R:\\user.txt"))) {
User user2 = (User)objectInputStream.readObject();
System.out.println("username:" + user2.getUsername());
System.out.println("password:" + user2.getPassword());
System.out.println("email:" + user2.getEmail());
} catch (Exception e) {
e.printStackTrace();
}
}
输出如下,由于 password
属性没有序列化,所以反序列化后获取为 null。
username:lanweihong
password:null
email:986310747@qq.com
参考文献
[1]. 高级Java工程师必备 —– 深入分析 Java IO (三)
[3]. 【Java基础-3】吃透Java IO:字节流、字符流、缓冲流
[4]. 《Java 核心技术 卷‖》