流的概念:
在程序的开发中IO的核心就是:输入和输出。
输入和输出是相对的,可能来自不同的环境。
对于服务器或者是客户端而言,传递的就是一种数据流的处理形式,而所谓的数据流指的就是字节数据。
这种的处理形式在java.io包里提供了两类支持:
- 字节处理流:OutputStream(输出字节流)、InputStream(输入字节流)
- 字符处理流:Writer(输出字符流)、Reader(输入字符流)
所有的流操作都应该统一步骤,以文件处理的流程为例:
- 进行文件的读写操作,通过File类找到文件路径;
- 通过字节流或字符流的子类为父类实例化;
- 利用字节流或字符流中的方法实现数据的输入与输出;
- 流的操作属于资源操作,而资源的操作必须进行关闭;
OutputStream抽象类(字节输出流):
NO | 方法名称 | 类型 | 作用 |
1 |
public abstract void write(int b) throws IOException;
|
普通 | 输出单个字节数据 |
2 |
public void write(byte[ b) throws IOException;
|
普通 | 输出一组字节数据 |
3 |
public void write(byte[] b, int off, int len) throws IOException; // 用得最多 |
普通 | 输出部分数据 |
要想使用OutputStream抽象类,就需要使用它的子类去向上转型。
关注的核心在子类FileOutputStream的构造方法上:
【覆盖】构造方法:public FileOutputStream(File file) throws FileNotFoundException; // 每次执行程序新的内容都会覆盖旧的内容
OutputStream output = new FileOutputStream(file);
【追加】构造方法:public FileOutputStream(File file, boolean append) throws FileNotFoundException; // 执行程序后在旧的内容后添加新的内容
OutputStream output = new FileOutputStream(file,true);
使用OutputStream类实现内容的输出:
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class MAIN {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "OutputStream.txt"); // 指定操作文件的路径
if (!file.getParentFile().exists()){ // 如果文件父路径不存在
file.getParentFile().mkdirs(); // 创建父路径
}
OutputStream output = new FileOutputStream(file); // 子类实例化,覆盖类型的构造方法
String str = "www.baidu.com"; // 输出的内容
output.write(str.getBytes(StandardCharsets.UTF_8)); // 将字符串转换为字节数组
output.close(); // 关闭资源
}
}



import java.io.*;
import java.nio.charset.StandardCharsets;
public class MAIN {
public static void main(String[] args) {
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "OutputStream.txt"); // 指定操作文件的路径
if (!file.getParentFile().exists()){ // 如果文件父路径不存在
file.getParentFile().mkdirs(); // 创建父路径
}
try (OutputStream output = new FileOutputStream(file,true)){ // 子类实例化,追加类型的构造方法
String str = "www.baidu.com1"; // 输出的内容
output.write(str.getBytes(StandardCharsets.UTF_8)); // 将字符串转换为字节数组
} catch (IOException e) {
e.printStackTrace();
}
}
}

程序采用了标准的形式实现了输出的操作处理,并且在整体的处理之中,只是创建了文件的父目录,但是并没有创建文件,而在执行后发现文件自动完成了创建。
由于OutputStream的子类也属于AutoCloseable接口的子类,所以对于close()方法也可以简化使用。
import java.io.*;
import java.nio.charset.StandardCharsets;
public class MAIN {
public static void main(String[] args) {
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "OutputStream.txt"); // 指定操作文件的路径
if (!file.getParentFile().exists()){ // 如果文件父路径不存在
file.getParentFile().mkdirs(); // 创建父路径
}
try (OutputStream output = new FileOutputStream(file)){ // 子类实例化
String str = "www.baidu.com1"; // 输出的内容
output.write(str.getBytes(StandardCharsets.UTF_8)); // 将字符串转换为字节数组
} catch (IOException e) {
e.printStackTrace();
}
}
}

InputStream抽象类(字节输入流):
public abstract class InputStream extends Object implements Closeable;
NO | 方法名称 | 类型 | 作用 |
1 |
public abstract int read() throws IOException;
|
普通 |
读取单个字节数据。如果已经读取到底了,返回 -1
文件的存放后是通过字节保存在硬盘,客户端读取文件通过InputStream的read()方法来进行一个字节一个字节的读取,在读取完所有字节后,下一个字节数据就为空,而空就是使用的 -1 来表示。 (好比搬砖一块一块地搬) |
2 |
public int read(byte[] b) throws IOException; // 最常用
|
普通 |
读取一组字节数据。返回值就是读取的字节数组的个数,如果读取到底没有数据了,则返回 -1
读取时是通过一组一组的数组形式进行读取以提高读取性能。 (这里是一车一车地搬) |
3 |
public int read(byte[] b,int off, int len) throws IOException;
|
普通 |
读取一组字节数据(只占数组的部分内容)。 (这里是按照存储要求地搬) |
InputStream类是抽象类,所以需要其子类FileInputStream进行向上转型实例化。
构造方法:public FileInputStream(File file) throws FileNotFoundException;
文件的读取操作:
import java.io.*; public class MAIN { public static void main(String[] args) { File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "OutputStream.txt"); try (InputStream input = new FileInputStream(file)){ // 子类实例化父类对象 byte[] data = new byte[1024]; // 开辟一个缓冲区读取数据 int len = input.read(data); System.out.println("{" + new String(data,0,len) + "}"); input.close(); } catch (IOException e) { e.printStackTrace(); } } }
读取所有字节的新方法:
import java.io.*;
public class MAIN {
public static void main(String[] args) throws Exception{
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "OutputStream.txt");
InputStream input = new FileInputStream(file); // 子类实例化父类对象
byte [] data = input.readAllBytes(); // 读取所有数据
System.out.println("{" + new String(data) + "}");
input.close();
}
}

虽然这个方法很方便,可以直接读取所有的数据,但是不建议使用,因为在文件很大很大的时候,使用这个方法可以会导致程序崩溃,所以使用这个方法的文件大小范围最好是在10kb以内。
Writer抽象类(字符输出流):
public abstract class Writer extends Object implements Appendable, Closeable,Flushable;
Writer中的输出操作方法:
输出字符数组: public void write(char[] cbuf) throws IOException;
输出字符串:public void write(String str) throws IOException;
字符输出流:
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class MAIN {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "Writer.txt");
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs(); // 如果父路径不存在则创建父路径
}
Writer writer = new FileWriter(file); // Writer的子类实例化父类,这是覆盖类型的构造方法,如果是追加类型则在第二个参数添加true
String str = "www.baidu.com";
writer.write(str); // 传入CharSequence类型的参数
writer.close(); // 关闭流
}
}


可以发现,如果文件不存在,那么Writer的write()方法同样会自动创建一个文件,然后写入数据。
此外还有一个方法append():
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class MAIN {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "Writer.txt");
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs(); // 如果父路径不存在则创建父路径
}
Writer writer = new FileWriter(file); // Writer的子类实例化父类,这是覆盖类型的构造方法,如果是追加类型则在第二个参数添加true
String str = "www.baidu.com";
writer.write(str); // 传入CharSequence类型的参数
writer.append(" 向文件末尾添加数据");
writer.close(); // 关闭流
}
}

使用Writer输出的最大优势在于可以直接利用字符串完成,Writer是字符流,字符的处理优势在中文数据上。
Reader抽象类(字符输入流):
public abstract class Reader extends Object implements Readable, Closeable.
import java.io.File;
import java.io.FileReader;
import java.io.Reader;
public class MAIN {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "OutputStream.txt");
if (file.exists()){ // 如果文件存在
Reader reader = new FileReader(file);
char []data = new char[1024];
int len = reader.read(data);
System.out.println("reader: " + new String(data,0,len));
reader.close();
}
}
}
字符流读取的时候只能够按照数组的形式来实现处理操作。
流 | 操作 | 区别 | 原因 |
Writer | 不使用close()方法 | 在程序执行后文件创建了但是内容没有输出 | Writer使用了缓冲区 |
OutputStream | 不使用close()方法 | 在程序执行后文件创建了且内容输出了 | 没有使用缓冲区 |
缓冲区就是一种堆积效果,例如丢垃圾,产生一个垃圾就先存着,等垃圾到一定程度再一起丢出去。
当使用close()方法的时候会强制刷新缓冲区,此时就会将内容输出,如果没有关闭则无法输出内容,那么如果想要在不关闭的情况下进行内容的输出就需要用到flush()方法:强制性清空。
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
public class MAIN {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "Writer.txt");
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs(); // 如果父路径不存在则创建父路径
}
Writer writer = new FileWriter(file); // Writer的子类实例化父类,这是覆盖类型的构造方法,如果是追加类型则在第二个参数添加true
String str = "www.baidu.com";
writer.write(str); // 传入CharSequence类型的参数
writer.append(" 向文件末尾添加数据");
writer.flush(); // 强制性刷新
}
}

字节流在进行处理的时候并不会使用到缓冲区,而字符流会使用到缓冲区。另外,使用缓冲区的字符流更加适合于中文数据的处理。
所以在以后的开发过程中遇到中文数据的处理都会选择字符流进行处理,但是字符流和字节流的基本处理形式都是相似的。
IO很多情况下都是进行数据的传输使用(二进制)。
转换流:
进行字节流和字符流的操作功能转换。
例如:在输出的时候OutputStream需要将内容变为字节数组后才可以输出,而Writer可以直接输出字符串,因为这样的便利性,所以需要一种转换的机制来实现不同流类型的转换操作,为此在java.io包里提供有两个类:InputStreamReader, OutputStreamWriter.
类 | OutputStreamWriter | InputStreamReader |
定义
|
public class OutputStreamWriter extends Writer.
|
public class InputStreamReaderextends Reader
|
构造方法
|
public outputStreamwriter(OutputStream out)
|
public InputStreamReader( InputStream in)
|
通过以上继承结构与构造方法可以发现,转换处理就是将接收的字节流对象通过向上转型变为字符流对象。
转换操作:
import java.io.*;
public class MAIN {
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "Demo_2_15" + File.separator + "OutputStreamWriter.txt");
if (!file.getParentFile().exists()){
file.getParentFile().mkdirs(); // 如果父路径不存在则创建父路径
}
OutputStream outputStream = new FileOutputStream(file); // 子类实例化父类
Writer writer = new OutputStreamWriter(outputStream); // 字节流变为字符流,将outputStream转换为Writer类型的writer
writer.write("Holle!Are you OK?\r\n"); // 直接输出字符串,字符流适合处理中文数据
writer.append("你好,你还好吗?");
writer.close();
}
}

总结:
最终我们可以发现:
public class FileReader extends InputStreamReader;
public class FileWriter extends OutputStreamWriter;
Writer和Reader的子类都继承于转换流.
public class OutputStreamWriter extends Writer;
public class InputStreamReader extends Reader;
而转换流又继承于Writer和Reader抽象类。
缓存就是系统的缓冲区的内容:
文件存储在磁盘上的数据都是01字节数据,系统可以通过字节流程序直接去读取文件,但是通过字符流时就需要先通过缓冲区进行处理后才能读取文件,例如 上面的流程是将磁盘中的字节数据先在缓冲区进行中文数据的处理再反馈给系统进行读取。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)