流的基本概念
在 java.io 包里面 File 类是唯一一个与文件本身有关的程序处理类,但是 File 类只能够操作文件本身而不能够操作文件内容,或者说在实际的开发之中。
IO 操作的核心意义在于:输入与输出操作。
对于程序而言,输入与输出可能来自于不同的环境
1、例如:通过电脑连接服务器上进行浏览的时候,实际上此时客户端发出了一个信息,而后服务器接受到此信息之后进行回应处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6qjYI4Bc-1690075992347)(https://ucc.alicdn.com/pic/developer-ecology/6070fccc285740a19949411fb7f37e3d.png “图片17.png”)]
2、对于服务器或者是客户端而言实质上传递的就是一种数据流的处理形式,而所谓的数据流指的就是字节数据。
而对于这种流的处理形式在 java.io 包里面提供有两种支持:
(1)字节处理流:OutputStream(输出字节流)、InputStream(输入字节流)
(2)字符处理流:Writer(输出字符流)、Reduce(输入字符流)
3、所有的流操作都应该采用如下统的步骤进行,下面以文件处理的流程为例:
(1)如果现在要进行的是文件的读写操作,则一定要通过 File 类找到一个文件路径;
(2)通过字节流或字符流的子类为父类对象实例化;
(3)利用字节流或字符流中的方法实现数据的输入与输出操作;
(4)流的操作属于资源操作,资源操作必须进行关闭处理。
OutputStream字节输出流
一、OutputStream字节输出流
1、字节的数据是以 byte 类型为主实现的操作,在进行字节内容输出的时候可以使用 OutputStream 类完成,这个类的基本定义如下:
Public abstract class OutputStream extends Object implements Closeable,Flushable.
2、首先可以发现这个类实现了两个接口,于是基本的对应关系如下:
(1)Closeable
Public interface Closeable extends AutoCloseable{public void close() throws IOException;}
(2)Flushable
Public interface Flushable{Public void flush() throws IOException;}
二、OutputStream 类的三个内容输出的方法
1、OutputStream 类定义的是一个公共的输出操作标准,而在这个操作标准里面,一共定义有三个内容输出的方法:
No | 方法名称 | 类型 | |
---|---|---|---|
01 | public abstract void write(int b) throws IOException ; | 普通 | 输出单个字节数据 |
02 | public void write(byte[] b) throws IOException | 普通 | 输出一组字节数据 |
03 | public void write(byte[] b, int off, int len) throws IOException | 普通 | 输出部分字节数据 |
2、注意:
(1)但是需要注意的一个核心问题在于: OutputStream 类毕竟是一个抽象类,而这个抽象类如果想要获得实例化对象,按照认识应该通过子类实例的向上转型完成。
(2)如果说现在要进行的文件处理操作,则可以使用 FileOutputStream 子类。
三、FileOutputStream子类的构造方法
1、因为最终都需要发生向上转型的处理关系,所以对于此时的 FileOutputStream子类核心的关注点就可以放在构造方法上:
(1)【覆盖】构造方法:public FileOutputStream(File file) throws FileNotFoundException;
(2)【追加】构造方法:public FileOutputStream(File file, boolean append) throws FileNotFoundException
四、使用 OutputStream 类实现内容输出
public class FileTest {
public static void main(String[] args) throws IOException {
File file = new File("E:" + File.separator + "hello" + File.separator + "byf.txt");
if (!file.getParentFile().exists()) { // 父级目录不存在
file.getParentFile().mkdirs(); // 创建父级目录
}
try(OutputStream out = new FileOutputStream(file)) { // 如果不使用这种格式就需要把输出流关闭,out.close();
String str = "巴运飞真帅";
out.write(str.getBytes());
}
}
}
注:
(1)本程序是采用了最为标准的形式实现了输出的操作处理,并且在整体的处理之中,只是创建了文件的父目录,但是并没有创建文件,而在执行后会发现文件可以自动帮助用户创建。
(2)由于 OutputStream 子类也属于 AutoCloseable 接口子类,所以对于 close()方法也可以简化使用。
InputStream 字节输入流
一、InputStream 类的定义
与 OutputStream 类对应的一个流就是字节输入流,InputStream 类主要实现的就字节数据读取,该类定义如下:
Public abstract class InputStream Extends Objetct implements Closeable
二、InputStream类的核心方法:
在 InputStream 类里面定义了如下的几个核心方法:
No | 方法名称 | 类型 | |
---|---|---|---|
01 | public abstract int read() throws IOException | 普通 | 读取单个字节数据,如果现在已经读取到底了,返回-1 |
02 | public int read(byte[] b) throws IOException | 普通 | 的个数,如果没有数据读取,已经读取到底了,则返回-1 |
03 | public int read(byte[] b, int off, int len) throws | 普通 | 读取一组字节数据,只占数组的部分 |
三、读取方式
1、01类方式
2、02类方式
3、03类方式
(1)InputStream 类属于一个抽象类,这时应该依靠它的子类来实例化对象,如果要从文件读取,则一定要使用 FileInputStream 子类,对于子类而言,只关心父类对象实例化,构造方法:
Public File inputStream(File file) throws FileNotFoundException.
读取数据:
public class FileTest {
public static void main(String[] args) throws IOException {
File file = new File("E:" + File.separator + "hello" + File.separator + "byf.txt");
InputStream is = new FileInputStream(file);
byte[] bytes = new byte[1024];
int len = is.read(bytes); // 返回的是读取的长度
System.out.println("【" + new String(bytes, 0, len) + "】");// 读取的长度是从0开始到len
}
}
四、字节输入流里面最为麻烦的问题
对于字节输入流里面最为麻烦的问题就在于:使用 read() 方法读取的时候只能够以字节数组为主进行接收。
Writer字符输出流
一、Writer 字符输出流
使用 OutputStream 字节输出流进行数据输出的时候使用的都是字节类型的数据,而很多情况下字符串的输出是比较方便的,所以对于 java.io 而言,在 JDK1.1的时候又推出了字符输出流:Writer,这个类的定义如下:
Public abstract class Writer Extends Object Implements Appendable,Closeable,Flushable
二、输出程序
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hhAN46rt-1690075992358)(https://ucc.alicdn.com/pic/developer-ecology/927fbfb9386a4f5da8654d1666e5f650.png “图片71.png”)]
三、在 Writer 类里提供的输出操作方法
(1)输出字符数组: public void write(char[] cbuf) throws IOException;
(2)输出字符串: public void write( String str) throws IOException;
File file = new File("E:" + File.separator + "hello" + File.separator + "byf.txt");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
Writer writer = new FileWriter(file);
writer.write("虫儿飞太帅了\n\t");
writer.append("中华人民共和国万岁");
writer.close();
注意:使用 Writer 输出的最大优势在于可以直接利用字符串完成。
Writer 是字符流,字符处理的优势在于中文数据。
Reader 字符输入流
1、Reader 是实现字符输入流的一种类型,其本身属于一个抽象类,这个类的定义如下:
Public abstract class Reader extends Object impleents Readable,Closeable
2、结构图
1、Reader 类里面并没有像 Writer 类一样提供有整个字符串的输入处理操作,只能够利用字符数组来接收:
(1)接收数据:
Public int read(char[]cbuf)throws IOException;
范例:
File file = new File("E:" + File.separator + "hello" + File.separator + "byf.txt");
Reader reader = new FileReader(file);
char[] chars = new char[1024];
int len = reader.read(chars);
System.out.println("【" + new String(chars, 0, len) + "】");
注:字符流读取的时候只能够按照数组的形式来实现处理操作。
字节流与字符流的区别
1、现在通过一系列的分析已经可以清楚字节流与字符流的基本操作了,但是对于这两类流依然是存在有区别的,重点分析一下输出的处理操作。
OutputStream 和 Writer 输出的最后发现都使用了 close (方法进行了关闭处理)。
2、在使用 OutputStream 类输出的时候如果发现没有使用 close() 方法关闭输出流,内容依然可以实现正常的输出。
在使用 Writer 类输出的时候,如果没有使用 close() 方法关闭输出流,那么这时内容无法进行输出,因为 Writer 使用到了缓冲区。
3、当使用了 close() 方法的时候,实际上会出现有强制刷新缓中区的情况,所以这个时候会将内容进行输出。
如果没有关闭,那么将无法进行输出操作。
所以此时如果在不关闭的情况下,要想将全部的内容输出可以使用 flush() 方法强制性清空。
(1)使用 Writer 并强制性清空
File file = new File("E:" + File.separator + "hello" + File.separator + "byf.txt");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
Writer writer = new FileWriter(file);
writer.write("巴云飞太帅了\n\t");
writer.append("中华人民共和国万岁");
writer.flush();
(2)字节流在进行处理的时候并不会使用到缓冲区,而字符流会使用到缓冲区。
另外,使用缓冲区的字符流更加于进行中文数据的处理。所以在日后的程序开发之中,如果要涉及到包含有中文信息的输出一般都会使用字符流处理。
(3)但是从另一方面来说,字节流和字符流的基本处理形式是相似的。
由于IO很多情况下都是进行数据的传输使用(二进制),所以本次讲解将以字节流为主。
转换流
一、转换流
所谓的转换流指的是可以实现字节流与字符流操作的功能转换。
例如:进行输出的时候,output stream 需要将内容变为字节数组后才可以输出,而 Writer 可以直接输出字符串,这一点是方便的,所以很多人就认为需要提供有一种转换的机制来实现不同流类型的转换操作。
为此在 java.io 包里面提供有两个类:
InputStreamReader、OutputStreanmwriter。
以下为两个类的结构操作方式。
在代码中找到类 OutputStreamWriter和InputStreamReader,将其打开。
出现以下界面
Public class
OutputStreamWriter
Extends Writer
Public class
InputStreamReader
Extends Reader
以上为 OutputStreamWriter和InputStreamReader 的定义。
public OutputStreamWriter(OutputStream out)
public InputStreamReader(InputStream in)
以上为 OutputStreamWriter 和 InputStreamReader 的构造方法。
以下为转换流:
使用OutputStreamWriter将字节流转为字符流
File file = new File("E:" + File.separator + "hello" + File.separator + "byf.txt");
if (!file.getParentFile().exists()) { // 父级目录不存在
file.getParentFile().mkdirs(); // 创建父级目录
}
OutputStream out = new FileOutputStream(file);
Writer writer = new OutputStreamWriter(out);
writer.write("虫儿飞真帅\n中华人民共和国万岁");
writer.close();
out.close();
在整体代码中,FileOutputStream 和 File 位有关。
程序在此,output 只能操作字节。如果加入字节流变为字符流的代码,可直接输出字符串。字符流适合处理中文。
此转换在是中文的情况下,处理较为方便,在不是中文的情况下,处理不够方便。
但讲解转换流的主要目的基本上不是为了让开发者去记住它,而是知道有这样一种功能,但同时更多的是需要进行结构的分析处理。
通过之前的字节流和字符流的一系列分析之后,你会发现 OutputStream 类有 FireOutputStream,直接子类 InputStream 类有 FileInputStream 直接子类,但是观察 FileWriter,FileReader 类的继承关系。
FileWriter | FileReader |
---|---|
public class FileWriter extends OutputStreamWriter | public class FileReader extends InputStreamReader |
在整体代码中,file output stream 和 File 位有关。
程序在此,output 只能操作字节。如果加入字节流变为字符流的代码,可直接输出字符串。字符流适合处理中文。
此转换在是中文的情况下,处理较为方便,在不是中文的情况下,处理不够方便。
但讲解转换流的主要目的基本上不是为了让开发者去记住它,而是知道有这样一种功能,但同时更多的是需要进行结构的分析处理。
通过之前的字节流和字符流的一系列分析之后,你会发现OutputStream 类有 FireOutputStream,直接子类 InputStream 类有 FileInputStream 直接子类,但是观察 FileWriter,FileReader 类的继承关系。
打开 file reader 和 file writer,观察其继承结构。
public class FileWriter extends OutputStreamWriter
打开 FileWriter,发现 OutputStreamWriter,打开 FileReader,发现 InputStreamReader。
在类结构定义时,已经准确的描述出转换流程。
而缓存就是如果用字节流读取,并不表示整个过程中没有内存。在严格意义上来讲,内存将一直存在。
只要程序需要读取数据,那么内存就会一直存在。如果用字节流读取,流程如下。
如果用字符流读取,还是向 cpu 请求。但是当读数据的时候,会准备缓冲区。适合传输的数据都是字节数据。
Cpu 请求到的数据是字节数据,而整个流程中磁盘传的是字节数据,而有了缓存之后相当于所有的数据已经经过处理。
意味着这些数据要读取到缓存区之中,读取到缓存区中直接过程就在于会对数据进行先期处理。在处理过程中就极为方便处理中文数据。
在电脑中,文件都是二进制数据的集合,缓冲区会进行处理。转换流的处理在定义结构上更加清楚描述出所有读取到的字节数据,并要求进行转换处理。
这就是转换流存在的意义所在。
实现拷贝功能
以下为需求分析:
需要实现文件的拷贝操作,那么这种拷贝就有可能拷贝各种类型的文件,所以此时选择使用字节流。
在进行拷贝的时候,有可能需要考虑到大文件的拷贝问题。
实现方案如下:
方案一:使用 input stream 将全部要拷贝的内容直接读取到程序里面,而后一次性的输出到目标文件;此方案的缺点就在于,如果现在拷贝的文件很大,基本上程序就死了;
方案二:采用部分拷贝,读取一部分输出一部分数据,如果现在要采用第二种做法,核心的操作方法为 Input stream 和 output stream。
主要用的是 input stream 中的以下方法:
Public int read(byte[]b) Throws IOException
Output stream 中的是以下方法:
Public void write(byte[]b), Int off, Int len) Throws IOException
class FileUtils {
private File srcFile;
private File desFile;
public FileUtils(String srcPath, String desPath) {
srcFile = new File(srcPath);
desFile = new File(desPath);
}
public FileUtils(File srcFile, File desFile) {
this.srcFile = srcFile;
this.desFile = desFile;
}
public boolean copy() throws Exception {
if (!srcFile.exists()) return false;
if (!desFile.getParentFile().exists())
desFile.getParentFile().mkdirs();
byte[] data = new byte[1024];
InputStream is = null;
OutputStream out = null;
try {
is = new FileInputStream(srcFile);
out = new FileOutputStream(desFile);
int len;
while ((len = is.read(data)) != -1) {
out.write(data, 0, len);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (is != null)
is.close();
out.close();
}
return true;
}
}
但是需要注意的是,以上的做法是属于文件拷贝的最原始实现,而从 jdk1.9 开始,inputstream 和 reader 类中都追加有数据转存的处理操作方法:
Reader:public long transferTo(Writer out)throws IOException
InputStream:Public long transferTo(OutputStream out)throws IOExceptiom
文件夹拷贝
class FileUtils {
private File srcFile;
private File desFile;
public FileUtils(String srcPath, String desPath) {
srcFile = new File(srcPath);
desFile = new File(desPath);
}
public FileUtils(File srcFile, File desFile) {
this.srcFile = srcFile;
this.desFile = desFile;
}
public boolean copy() throws Exception {
if (!srcFile.exists()) return false;
return copy(this.srcFile, this.desFile);
}
private boolean copy(File srcFile, File desFile) throws IOException {
if (!desFile.getParentFile().exists())
desFile.getParentFile().mkdirs();
byte[] data = new byte[1024];
InputStream is = null;
OutputStream out = null;
try {
is = new FileInputStream(srcFile);
out = new FileOutputStream(desFile);
int len;
while ((len = is.read(data)) != -1) {
out.write(data, 0, len);
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (is != null)
is.close();
out.close();
}
return true;
}
public boolean copyDir() throws IOException {
File[] files = this.srcFile.listFiles();
for (int i = 0; i < files.length; i++) {
copyDirImpl(files[i]);
}
return true;
}
public boolean copyDirImpl(File file) throws IOException {
if (file.isDirectory()) {
File[] files = file.listFiles();
for (int i = 0; i < file.length(); i++) {
copyDirImpl(files[i]);
}
} else {
String path = file.getAbsolutePath().replace(this.srcFile.getAbsolutePath()+File.separator, "");
File outFile = new File(this.desFile, path);
this.copy(file, outFile);
}
return true;
}
}