流是一个抽象的概念,代表了数据的无结构化传递。流的本质是数据在不同设备之间的传输。在 Java 中,数据的读取和写入都是以流的方式进行的。
在 Java 中,根据数据流向的不同,可以将流分为输入(Input)流和输出(Output)流;根据单位的不同,可以将流分为字节流和字符流;根据等级不同,可以将流分为节点流和处理流。
类型 | 字节流 | 字符流 |
---|---|---|
输入流 | InputStream | Reader |
输出流 | OutputStream | Writer |
InputStream 类的所有方法在遇到错误时都会抛出 IOException 。InputStream 用于以字节形式将数据读入应用程序中,常用的方法及其作用如下:
方法 | 作用 |
---|---|
int read() | 从输入流读取 8 字节数据并将其转换成一个 0~255 的整数,返回值为读取的总字节数,遇到数据流的末尾则返回 -1 |
int read(byte[] b) | 从输入流中读取最大长度为 len 字节的数据并保存到 b 字节数组中,遇到数据流的末尾则返回 -1 |
int read(byte[] b,int off,int len) | 以输入流中的 off 位置为开始位置读取最大长度为 len 字节的数据,并将其保存到 b 字节数组中 |
void close() | 关闭数据流 |
int available() | 返回可以从输入流中读取的位数 |
skip(long n) | 从输入流跳过 n 字节 |
一段基于 FileInputStream 读取文件的代码如下:
public static void main(String[] args) throws IOException {
String path = "F:\\";
String fileName = "test.txt";
//1.定义待读取的文件
File file = new File(path,fileName);
//2.从文件中读取数据到 fileInputStream
FileInputStream fileInputStream = new FileInputStream(file);
byte[] bytes = new byte[fileInputStream.available()];
int n = 0;
//3.从 fileInputStream 中不断循环读取字节数据并写入 bytes,
//直到遇到数据流结尾时,read 方法返回 -1,则退出循环
while ((n = fileInputStream.read(bytes)) != -1){
//将 byte[] 转化为字符串
String s = new String(bytes);
System.out.println(s);
}
//关闭输入文件流
fileInputStream.close();
}
打印内容如下:
你好
小明
是的
很好啊
OutputStream 类的所有方法在遇到错误时都会抛出 IOException 异常。OutputStream 用于以字节形式将数据输出到目标设备,常用的方法及其作用如下:
方法 | 作用 |
---|---|
int write(b) | 将指定字节的数据写入输出流 |
int write(byte[] b) | 将指定字节数组的数据写入输出流 |
int write(byte[] b,int off,int len) | 将指定的字节数组从 off 位置开始的 len 字节的内容写入输出流 |
close() | 关闭数据流 |
flush() | 刷新数据流,强行将缓冲区的内容写入输出流 |
基于 FileOutputStream 读取文件的一段代码如下:
public static void main(String[] args) throws IOException {
String path = "F:\\";
String fileName = "test.txt";
//1.定义待写入的文件
File file = new File(path,fileName);
//2.定义 fileOutputStream
FileOutputStream fileOutputStream = new FileOutputStream(file);
//3.将数据写入 fileOutputStream
fileOutputStream.write("biubiubiu".getBytes());
//4.关闭 fileOutputStream
fileOutputStream.close();
}
Reader 类中的常用方法有 close、mark、skip、reset 等,以下表主要介绍 Reader 类中最常用的 read 方法:
方法名及返回值类型 | 方法说明 |
---|---|
int read() | 从输入流中读取一个字符并转化为 0~65535 的整数,当读取到流的末尾时返回 -1 |
int read(char[] cbuf) | 从输入流中读取若干个字符并保存到参数 cbuf 指定的字符数组中,当读取到流的末尾时,返回 -1 |
int read(char[] cbuf,int off,int len) | 以输入流中的 off 位置为开始位置读取最大长度为 len 直接的数据并将其保存到 cbuf 字符数组中,当读取到流的末尾时返回 -1 |
基于 BufferedReader 读取文件的一段代码如下:
public static void main(String[] args) throws IOException {
String path = "F:\\test.txt";
//1.创建 fileReader
FileReader fileReader = new FileReader(path);
//2.基于 fileReader 创建 bufferedReader
BufferedReader bufferedReader = new BufferedReader(fileReader);
//3.定义一个 strLine ,表示 bufferedReader 读取的结果
String strLine = "";
//4.调用 readLine 方法将缓冲区中的数据读取为字符串,
//当 readLine 返回 -1 时,表示已经读取到文件末尾了
while ((strLine = bufferedReader.readLine()) != null){
System.out.println(strLine);
}
//5.关闭 bufferedReader
bufferedReader.close();
}
Writer 类的所有方法在执行出错时都会引发 IOException 异常。Writer 类也包含 close、flush 等方法,这些方法的功能可以参考 OutputStream 类的方法。下面主要介绍 Writer 类的 write 方法和 append 相关的方法,如下:
方法名及返回值类型 | 方法说明 |
---|---|
void write(int c) | 向输出流写入一个字符 |
void write(char[] cbuf) | 将字符数组 cbuf 中的字符写入输出流中 |
void write(char[] cbuf,int off,int len) | 将字符数组 cbuf 中从 off 位置开始获取长度为 len 的字符并写入输出流中 |
void write(String str) | 将字符串写入输出流中 |
void write(String str,int off,int len) | 将字符串中的部分字符串写入输出流中 |
Writer append(char c) | 将字符追加到输出流中 |
Writer append(charSequence esq) | 将参数 esq 指定的字符序列追加到输出流中 |
Writer append(charSequence esq,int start,int end) | 将参数 esq 指定字符序列的子序列追加到输出流中 |
基于 BufferedWriter 将字符串写入文件中的一段代码如下:
public static void main(String[] args) throws IOException {
//1.定义一个 fileWriter
String path = "F:\\test.txt";
FileWriter fileWriter = new FileWriter(path);
//2.基于 fileWriter 定义一个 bufferedWriter
BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
//3.调用 bufferedWriter 的 write 方法将字符串写入 bufferedWriter
bufferedWriter.write("小明开始输入啦,biubiubiu");
//4.关闭 bufferedWriter
bufferedWriter.close();
//5.关闭 fileWriter
fileWriter.close();
}
节点流是低级流,直接与数据源相连,对数据源上的流进行读写。
处理流是高级流,采用修饰器模式对节点流进行了封装,不直接与数据源相连,主要用于消除不同节点流的实现差异,提供更方便的方法来完成数据的输入和输出。
例如,FileInputStream、FileOutputStream、FileReader、FileWriter 属于节点流;BufferInputStream、BufferOutputStream、BufferReader、BufferWriter 属于处理流。
相对于节点流,处理流有如下特性:
操作系统可以利用虚拟内存实现将一个文件或文件的一部分“映射”到内存中。然后,这个文件就可以被当作内存数据来访问,比传统的文件要快得多,这种技术就是内存映射文件技术。
内存映射文件技术的一个关键优势就是操作系统负责真正的文件读写,应用程序只需处理内存数据,就可以实现非常快速的 I/O 操作。在写入过程中,即使应用程序在将数据写入内存后进程出错退出,操作系统仍然会将内存映射文件中的数据写入(写出)文件系统。
另一个更突出的优势是共享内存,即内存映射文件可被多个进程同时访问,起到低时延共享内存的作用。
Java 中 的 java.nio 包支持的内存映射文件,具体使用方式是通过 MappedByteBuffer 读写内存,而且内存映射文件技术涉及的内存在 Java 的堆空间之外,这也是其效率高的一个原因。
在 Java 中将一个文件映射到内存并操作共分为如下 3 步:
String path = "F:\\test.txt";
RandomAccessFile randomAccessFile = new RandomAccessFile(path,"rw");
FileChannel fileChannel = randomAccessFile.getChannel();
以上代码定义了一个可读写的 RandomAccessFile,然后调用 RandomAccessFile 的 getChannel 方法获取一个 FileChannel。
fileChannel.map(mode,0,length);
以上代码中的 mode 参数用于指定映射模式,支持的模式有如下 3 种:
一段完整的 Java 内存映射文件操作代码如下:
public static void main(String[] args) throws IOException {
//1.定义文件流
String path = "F:\\test.txt";
RandomAccessFile randomAccessFile = new RandomAccessFile(path, "rw");
//2.获取 fileChannel
FileChannel fileChannel = randomAccessFile.getChannel();
//3.定义 mappedByteBuffer
int start = 0;
int len = 1024;
//调用 map 函数的过程其实就是磁盘文件到内存数据的映射过程,
//对 fileChannel 调用 map 函数后,应用程序可以像使用内存一样使用该文件
MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.PRIVATE, start, len);
//4.进行 mappedByteBuffer 数据的输入,分别在内存映射文件中写入如下字符串
mappedByteBuffer.put("12345".getBytes());
mappedByteBuffer.put("6789".getBytes());
mappedByteBuffer.put("xiaoming".getBytes());
System.out.println((char) mappedByteBuffer.get(2));
//5.mappedByteBuffer 数据的读取:读取所有数据
for (int i = 0; i < mappedByteBuffer.position(); i++) {
System.out.println((char) mappedByteBuffer.get(i));
}
}
以上代码首先通过 “RandomAccessFile randomAccessFile = new RandomAccessFile(path, “rw”);”定义 RandomAccessFile 文件流实例 randomAccessFile,然后通过 “FileChannel fileChannel = randomAccessFile.getChannel();”获取 FileChannel,接着通过 “fileChannel.map(FileChannel.MapMode.PRIVATE, start, len);”将磁盘文件映射到内存数据,这样程序可以像使用内存一样使用该文件。
在内存文件映射好后,通过 “mappedByteBuffer.put(“xiaoming”.getBytes());” 向 mappedByteBuffer 写入数据,通过 “(char) mappedByteBuffer.get(2)”读取第 3 个字符 “a”,然后通过 for 循环对内存映射文件中的数据进行遍历。
内存映射文件到底有多快呢?
对于一个 30MB 的文件计算 CRC,使用 InputStream 耗时 21553ms,使用 RandomAccessFile 耗时 26668 ms,使用 BufferedInputStream 耗时 317ms,使用 MappedByteBuffer 耗时 75ms。可以看出, MappedByteBuffer 的效率分别是 InputStream 和 RandomAccessFile 的287、355倍;约是 BufferedInputStream 的 4.2 倍,这种优势还会随着文件大小的增加而增加,在达到 1G 以上时更加明显。
相关面试题: