流是一个抽象的概念,代表了数据的无结构化传递。流的本质是数据在不同设备之间的传输。在 Java 中,数据的读取和写入都是以流的方式进行的
在 Java 中,根据数据流向的不同,可以将流分为输入(Input)流和输出(Output)流。根据单位的不同,可以将流分为字节流和字符流。根据等级的不同,可以将流分为节点流和处理流
输入流用于将数据从控制台、文件、网络等外部设备输入应用程序进程中
输出流用于将应用程序进程中的数据输出到控制台、文件、显示器等中
字节流:字节流是以字节(1byte=8bit)为单位对数据进行读写操作的,也就是说,字节流进行一次读取或者写入都是以 8bit 为单位进行的,因此主要用于处理二进制据。在 Java 中使用 InputStream、OutputStream 处理字节数据,其中 InputStream 用于字节流输入,OutputStream 用于字节流输出
字符流:字符流以字符为单位对数据进行读写操作,一次读取或写入都是以 16bit 为单位进行的。Java 中的字符采用 Unicode 编码,一个字符占用 2 字节。字符流主要用于处理文本数据的读写,在处理过程中需要进行字符集的转化。在 Java 中使用 Reader、Writer 处理字符数据,其中 Reader 用于字符流输入,Writer 用于字符流输出
InputStream 字节输入流是一个抽象类,其子类包括:
InputStream 类的所有方法在遇到错误时都会抛出 IOExcepiion 异常。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 = "file_dir/";
String fileName = "File-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,直到遇到数据流结尾时
while ((n = fileInputstream.read(bytes)) != -1) {
// 4:将byte[]转化为字符串
String s = new String(bytes);
System.out.printIn(s);
}
// 5:关闭输入文件流
fileInputStream.close();
}
OutputStream 字节输出流是一个抽象类,其子类包括:
OutputStream 类的所有方法在遇到错误时都会抛出 IOException 异常。OutputStream 用于以字节形式将数据输出到目标设备,常用的方法及其作用如表所示
方法 | 作用 |
---|---|
int write() | 将指定字节的数据写入输出流 |
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 = "file_dir/";
String fileName = "File-Test.txt";
// 1:定义待写入的文件
File file = new File(path, fileName);
// 2:定义FileOutputStream
FileOutputStream fileOutputStream = new FileOutputStream(file, false);
// 3:将数据写入FileOutputStream
fileOutputStream.write("hello FileOutputStream new " .getBytes());
// 4:关闭FileOutputStream
fileOutputStream.close();
}
Reader 类是所有字符流输入类的父类,用于以字符形式将数据读取到应用程序中,其子类包括:
Reader 类常用方法如下
方法 | 作用 |
---|---|
int read() | 从输入流中读取一个字符并转化为 0-65535 的整数,当读取到流的末尾时返回-1 |
int read(char[] buf) | 从输入流中读取若干个字符并保存到参数buf指定的字符数组中,当读取到流的末尾时返回-1 |
int read(char[] buf, int off, int len) | 以输入流中的off位置为开始位置读取最大长度为len字节的数据并将其保存到buf字符数组中,当读取到流的末尾时返回-1 |
基于 BufferedReader 读取文件的一段代码如下:
public static void main(string[] args) throws Exception {
String path = "file_dir.mov";
//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:关团fileReader
fileReader.close();
//6:关闭bufferedReader
bufferedReader.close();
}
Writer 类是所有字符流输出类的父类,用于以字符形式将数据写出到外部设备,其子类包括:
Writer 类常用方法如下
方法 | 作用 |
---|---|
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) | 将字符串中的部分字符写入输出流 |
append(char c) | 将字符c追加到输出流 |
append(charSequence csq) | 将参数csq指定的字符序列追加到输出流 |
append(charSequence csq, int start, int end) | 将参数csq指定的字符序列的子序列追加到输出流 |
基于 BufferedWriter 将字符串写入文件中的一段代码如下:
public static void main(String[] args) throws Exception {
//1:定义一个FileWriter
String path = "File-Test.txt";
FileWriter writer = new FileWriter(path);
//2:基于FileWriter定义一个BufferWriter
BufferedWriter bufferedWriter = new BufferedWriter(writer)
//3:调用BufferedWriter的write方法将字符串写入BufferedWriter
bufferedWriter.write("write by str");
//4:关闭BufferedWriter
bufferedWriter.close();
//5:关闭FileWriter
writer.close();
}
节点流是低级流,直接与数据源相接,对数据源上的流进行读写。
处理流是高级流,采用修饰器模式对节点流进行了封装,不直接与数据源相连,主用于消除不同节点流的实现差异,提供更方便的方法来完成数据的输入和输出。
例如,FileInputStream、FileOutputStream、FileReader、FileWriter 属于节点流;BufferInputStream、BufferOutputStream、BufferReader、BufferWriter 属于处理流。
相对于节点流,处理流有如下特性:
操作系统可以利用虚拟内存实现将一个文件或者文件的一部分“映射”到内存中。然后,这个文件就可被当作内存数据来访问,比传统的文件要快得多,这种技术就是内存映射文件技术。
内存映射文件技术的一个关键优势是操作系统负责真正的文件读写,应用程序只需处理内存数据,就可以实现非常快速的 IO 操作。在写入过程中,即使应用程序在将数据写人内存后进程出错退出,操作系统仍然会将内存映射文件中的数据写入文件系统。另一个更突出的优势是共享内存,即内存映射文件可被多个进程同时访问,起到低时延共享内存的作用。
Java 中的 java.nio 包支持内存映射文件,具体使用方式是通过 MappedByteBuffer 读写内存,而且内存映射文件技术涉及的内存在 Java 的堆空间之外,这也是其效率高的一个原因。
在 Java 中将一个文件映射到内存并操作共分为如下三步:
从文件中获得一个通道(channel)
RandomAccessFile raf = new RandomAccessFile(filePath, "rw");
FileChannel fc= raf.getChannel();
调用 FileChannel 的 map 方法将文件映射到虚拟内存
MappedByteBuffer buffer = channel.map(mode, 0, length);
mode 参数用于指定映射模式,支持的模式有如下三种:
调用 MappedByteBuffer 的 put(byte[] src)
向内存映射文件中写入数据,调用 get(int index)
获取文件中对应索引的数据,以字节形式返回
public static void main(String[] args) throws Exception {
//1:定义文件流
String path = "file_path/File-Test.txt";
RandomAccessFile raf = new RandomAccessFile(path,"rw");
//2:获取FileChannel
FileChannel fc = raf.getChannel();
//3:定义MappedByteBuffer
int start = 0:
int len = 1024;
//调用map函数的过程其实就是磁盘文件到内存数据的映射过程
//对Filechannel调用map函数后,应用程序可以像使用内存一样使用该文件
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.PRIVATE, start, len);
//4:进行MappedByteBuffer数据的输入,分别在内存映射文件中写入如下字符串
mbb.put("12345".getBytes());
mbb.put("6789".getBytes());
mbb,put("wanglei".getBytes());
//读取第9个字符,结果为"w"
System.out.println((char)mbb.get(9));
//5:MappedByteBuffer数据的读取:读取所有数据
for (int i = start; i < mbb.position(); i++) {
System.out.println((char)mbb.get(i));
}
}