IO流
IO,即in和out,也就是输入和输出,指的是程序和外部设备之间的数据传递。常见的外部设备有文件、网络连接、管道等。
Java中通过流处理IO,流(Stream),是指一连串的数据(字节或字符),以先进先出的方式发送信息的通道。
当程序需要读取数据的时候,就会开启一个通向数据源的流,数据源可以是文件、内存、网络连接。类似的,当程序需要写入数据的时候,就会开启一个通向目的地的流。
IO流的核心为四个抽象类:InputStream、OutputStream、Reader、Writer。
InputStream类
- int read():读取文件
- int read(byte b[], int off, int len):从第off位置开始读,读取len长度的字节,然后放入数组b中。
- long skip(long n):跳过指定个数的字节。
- int available():返回可读的字节数。
- void close():关闭流,释放资源。
OutputStream类
- void write(int b): 写入一个字节。
- void write(byte b[], int off, int len):将数组b中的从off位置开始,长度为len 的字节写入。
- void flush():强制刷新,将缓冲区的数据写入。
- void close():关闭流。
Reader类
- int read():读取单个字符。
- int read(char cbuf[], int off, int len):从第off位置开始读,读取len长度的字符,然后放入b数组中。
- long skip(long n): 跳过指定个数的字符。
- int ready():是否可以读了。
- void close():关闭流,释放资源。
Writer类
void write(int c):写入一个字符
void write(char cbuf[], int off, int len):将数组从cbuf中的off位置开始,长度为冷的字符写入。
void flush():强制刷新,将缓冲区的数据写入。
void close():关闭流。
字节流和字符流的区别:
字节流一般用来处理图像、视频、音频等类型的文件。字符流一般用来处理纯文本类型的文件,不能处理图像、视频等非文本文件。
字节流本身没有缓冲流,而字符流本身就带有缓冲区,即缓冲流对字节流效率提升高,而对字符流效率提升相对低。
IO流分类
IO流按照操作对象来划分,可以分为:文件、数组、管道、基本数据类型、缓冲、打印、对象序列化/反序列化,以及转换等。
文件
文件流就是直接操作文件的流,可以分为字节流(FileInputStream 和 FileOutputStraem) 和 字节流(FileReader 和 FileWriter)
FileInputStream的例子:
//创建一个文件输入流
FileInputStream fis = new FileInputStream("demo/b.txt");
//声明一个字节数组,用来存取读到的数据
byte[] b = new byte[1024];
//声明一个int类型的变量,用于记录读取的个数
int len;
//当数组中的数据没有读完
while((len = fis.read(b)) != -1) {
/将读取到的字节转换为字符打印
System.out.println(new String(b, 0, len));
}
//关闭文件输入流,释放资源
fis.close();
FileOutputStream的例子:
FileInputStream fis = new FileInputStream("demo/b.txt");
//创建一个文件输出流
FileOutputStream fos = new FileOutputStream("demo/c.txt");
int len;
byte[] buf = new byte[1024];
while ((len = fis.read(buf)) != -1) {
//向文件中写入数据,如果是非字节数据需要转成字节
fos.write(buf, 0, len);
}
//关闭输出流
fos.close();
fis.close();
FileReader的例子:
//创建一个FileReader对象,用来读取demo/b.txt中的数据
FileReader fr = new FileReader("demo/b.txt");
//创建一个字符数组用来存储读取的输入
char[] chars = new char[100];
//定义读取到的字符长度
int len = 0;
//循环读取文件中的数据
while ((len = fr.read(chars)) != -1) {
//把读取到字符转为字符串输出
System.out.print(new String(chars, 0, len));
}
//关闭输入流对象,释放资源
fr.close();
FileWriter的例子:
FileReader fr = new FileReader("demo/b.txt");
//创建一个FileWriter对象,用来读取demo/b.txt中的数据
FileWriter fw = new FileWriter("demo/c.txt");
char[] chars = new char[100];
int len = 0;
while ((len = fr.read(chars)) != -1) {
//向文件中写入数据,写入的是chars数组中的所有字符
fw.write(chars, 0, len);
}
//关闭FileWriter流,释放资源。
fw.close();
fr.close();
FileOutputStream 和 FileWriter 构造函数的第二个参数(布尔值)可以指定是否追加数据到文件末尾。
数组(内存)
通常来说,针对文件的读写操作,使用文件流加上缓冲流就足够了,但为了提升效率,频繁读写文件并不太好,就出现了数组流,也被称为内存流。
ByteArrayInputStream 的例子:
// 创建一个 ByteArrayInputStream 对象,用于从字节数组中读取数据
InputStream is = new BufferedInputStream(new ByteArrayInputStream("Hello World".getBytes(StandardCharsets.UTF_8)));
// 定义一个字节数组用于存储读取到的数据
byte[] bytes = new byte[1024];
// 定义一个变量用于存储每次读取到的字节数
int len = 0;
// 循环读取字节数组中的数据,并输出到控制台
while ((len = is.read(bytes)) != -1) {
// 将读取到的字节转换为对应的字符串,并输出到控制台
System.out.println(new String(bytes, 0, len));
}
// 关闭输入流,释放资源
is.close();
ByteArrayOutputStream 的例子:
// 创建一个 ByteArrayOutputStream 对象,用于写入数据到内存缓冲区中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 定义一个字节数组用于存储要写入内存缓冲区中的数据
byte[] info = "Hello".getBytes();
// 向内存缓冲区中写入数据,这里写入的是 info 数组中的所有字节
bos.write(info, 0, info.length);
// 将内存缓冲区中的数据转换为字节数组
byte[] dest = bos.toByteArray();
// 关闭 ByteArrayOutputStream 对象,释放资源
bos.close();
数组流可以用于在内存中读写数据,比如将数据存储在字节数组中进行压缩、加密、序列化等操作。 它的优点是不需要创建临时文件,可以提高程序的效率。缺点是,它只能存储有限的数据量,如果存储的数据量过大,会导致内存溢出。
缓冲
CPU速度相比内存快100倍,比磁盘块百万倍。那么程序和内存交互会很快,和硬盘交互相对就较慢,这样就会导致性能问题。
为了减少程序和硬盘之间的交互,提高程序的效率,java引入了缓冲流。
缓冲流在内存中设置了一个缓冲区,只有缓冲区存储了足够多的数据后,才会和内存或者硬盘进行交互。
BufferedInputStream的例子:
// 创建一个 BufferedInputStream 对象,用于从文件中读取数据
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("demo/a.txt"));
// 创建一个字节数组,作为缓存区
byte[] buffer = new byte[1024];
// 用来判断读取的个数
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
// 对缓存区中的数据进行处理
System.out.println(new String(buffer, 0, bytesRead));
}
// 关闭 BufferedInputStream,释放资源
bis.close();
BufferedOutputStream的例子:
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("demo/b.txt"));
// 创建一个 BufferedOutputStream 对象,用于将数据写入到文件中
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("demo/c.txt"));
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
bos.write(buffer, 0, bytesRead);
}
// 刷新缓存区,将缓存区中的数据写入到文件中
bos.flush();
bos.close();
bis.close();
通过flush()方法将缓存区中的数据写入到文件中。在写入数据时,由于使用了BufferedOutputStream,数据会被先写到缓存区中,只有在缓存区被填满或者调用了flush方法时才会将缓存区中的数据写到文件中。
BufferedReader的例子:
// 创建一个 BufferedReader 对象,用于从文件中读取数据
BufferedReader br = new BufferedReader(new FileReader("demo/b.txt"));
// 读取文件中的数据,并将其存储到字符串中
String line;
while ((line = br.readLine()) != null) {
// 对读取到的数据进行处理
System.out.println(line);
}
// 关闭 BufferedReader,释放资源
br.close();
BufferWriter的例子:
BufferedReader br = new BufferedReader(new FileReader("demo/b.txt"));
// 创建一个 BufferedWriter 对象,用于将数据写入到文件中
BufferedWriter bw = new BufferedWriter(new FileWriter("demo/c.txt"));
String line;
while ((line = br.readLine()) != null) {
bw.write(line + "\n");
}
// 刷新缓存区,将缓存区中的数据写入到文件中
bw.flush();
// 关闭 BufferedWriter,释放资源
bw.close();
br.close();
使用缓冲流可以提高读写效率,减少了频繁的读写磁盘或网络的次数,从而提高了程序的性能。但是,在使用缓冲流时需要注意缓冲区的大小和清空缓冲区的时机,以避免数据丢失或不完整的问题。
对象序列化/反序列化
序列化本质上是将一个 Java 对象转成字节数组,然后可以将其保存到文件中,或者通过网络传输到远程。
// 创建一个 ByteArrayOutputStream 对象 buffer,用于存储数据
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
// 使用 try-with-resources 语句创建一个 ObjectOutputStream 对象 output,并将其与 buffer 关联
try (ObjectOutputStream output = new ObjectOutputStream(buffer)) {
// 使用 writeUTF() 方法将字符串写入到缓冲区中
output.writeUTF("字符串");
}
// 使用 toByteArray() 方法将缓冲区中的数据转换成字节数组,并输出到控制台
System.out.println(Arrays.toString(buffer.toByteArray()));
反序列化,就是再将字节数组转成Java对象的过程。
try (ObjectInputStream input = new ObjectInputStream(new FileInputStream(
new File("b.txt")))) {
String s = input.readUTF();
}