学习阿里云大学零基础学Java系列 Java高级编程
1. 简介
在Java.io包里面File类是唯一一个与文件本身有关的程序处理,但是File只能够操作文件本身而不能够操作文件内容,或者说,在实际的开发之中IO操作的核心意义在于:输入与输出操作。而对于程序而言,输入与输出可能来自于不同的环境,例如:通过电脑连接服务器进行浏览的时候,实际上此时客户端发出了一个信息,而后服务器接收到此信息之后进行回应处理。
对于服务器或者客户端而言实质上传递的就是一种数据流的处理形式,而所谓的数据流指的就是字节数据,而对于这种流的处理形式在java.io包里面提供有两类支持:
- 字节处理流:OutputStream(输出字节流,抽象类)、InputStream(输入字节流,抽象类)
- 字符处理流:Writer(输出字符流,抽象类)、Reader(输入字符流,抽象类)
所有的流操作都应该采用如下统一的步骤进行,下面以文件处理的流程为例:
- 如果现在要进行的是文件的读写操作,则一定要通过File类找到一个文件路径
- 通过字节流或字符流的子类为父类对象实例化
- 利用字节流或字符流中的方法实现数据的输入与输出操作
- 流的操作属于资源操作,资源操作必须进行关闭处理
2. 字节输出流OutputStream
字节的数据是以byte类型为主实现的操作,在进行字节内容输出的时候可以使用OutputStream类完成,这个类的基本定义如下:
public abstract class OutputStream extends Object implements Closeable, Flushable
首先可以发现这个类实现了两个个接口,于是基本的对应关系如下:
public interface Closeable extends AutoCloseable {
public void close() throws IOException;
}
public interface Flushable {
void flush() throws IOException;
}
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子类;
因为最终都需要发生向上转型的处理关系,所以对于此时的FileOutputStream子类的核心的关注点就可以放在构造方法上,提供有两类构造方法:
- 【覆盖】构造方法:public FileOutputStream(File file) throws FileNotFoundException
- 【追加】构造方法:public FileOutputStream(File file, boolean append) throws FileNotFoundException
范例:使用OutputStream类实现内容的输出
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" + File.separator + "mldn.txt");// 指定要操作文件的路径
if (!file.getParentFile().exists()) {// 文件不存在
file.getParentFile().mkdirs();// 创建父目录
}
OutputStream output = new FileOutputStream(file);// 通过子类实例化
String str = "www.sunlh.vip";// 要输出的文件内容
output.write(str.getBytes());// 将字符串变为字节数组
output.close();// 关闭资源
}
本程序是采用了最为标准的形式实现了输出的操作处理,并且在整体的处理之中只是创建了文件的父目录,但是并没有创建文件,而在执行后会发现文件可以自动帮助用户创建。由于OutputStream子类也属于AutoCloseable接口子类,所以对于close()方法也可以简化使用。
范例:利用try…catch自动关闭处理
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" + File.separator + "mldn.txt");// 指定要操作文件的路径
if (!file.getParentFile().exists()) {// 文件不存在
file.getParentFile().mkdirs();// 创建父目录
}
try(OutputStream output = new FileOutputStream(file, true)) {
String str = "www.sunlh.vip\r\n";// 要输出的文件内容,\r\n表示标准换行
output.write(str.getBytes());// 将字符串变为字节数组
} catch (IOException e) {
e.printStackTrace();
}
}
是否使用自动的关闭取决于项目的整体结构,另外需要提醒大家的是,整个的程序里面,最终是输出了一组的字节数据,但是千万不要忘记了,OutputStream类之中定义的输出方法一共有3个。
3. 字节输入流InputStream
与我们OutputStream类对应的一个流就是字节输入流,InputStream类主要实现的就是字节数据读取,该类定义如下:
public abstract class InputStream extends Object implements Closeable
在InputStream类里面定义有如下的几个核心方法
No | 方法名称 | 类型 | 说明 |
---|---|---|---|
1 | public abstract int read() throws IOException | 普通 | 读取单个字节数据,如果现在已经读取到底,返回 |
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
范例:读取数据
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" + File.separator + "mldn.txt");// 指定要操作文件的路径
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();
}
对于字节输入流里面最为麻烦的问题在于:使用read()方法读取的时候只能够以字节数组为主进行接收。
特别需要注意的是从jdk1.9开始增加了一个新的方法:public byte[] readAllBytes(byte[] b) throws IOException
如果你现在要读取的内容很大很大的时候,那么这种读取直接搞死你的程序
4. 字符输出流Writer
使用OutputStream字节输出流进行数据输出的时候使用的都是字节类型的数据,而很多的情况下字符串的输出是比较方便的,所以对于java.io包而言,在jdk1.1的时候又推出了字符输出流: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
范例:使用Writer输出
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" + File.separator + "mldn.txt");// 指定要操作文件的路径
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();// 父目录一定要存在
}
Writer out = new FileWriter(file);// new FileWriter(file, false)//第二个参数是否追加
String str = "www.sunlh.vip\r\n";
out.write(str);
out.append("zhongguo");// 追加输出内容
// write和append的区别,看源码可以发现,append调用的就是write,只是中间加了一层对null的处理
out.close();
}
使用Writer输出的最大优势在于可以直接利用字符串完成。Writer是字符流,字符处理的优势在于中文数据上
5. 字符输入流Reader
Reader是实现字符输入流的一种类型,其本身属于一个抽象类,这个类的定义如下:
public abstract class Reader extends Object implements Readable, Closeable
Reader类里面并没有像Writer类一样提供有整个字符串的输入操作处理,只能够利用字符数组来实现接收
- 接收数据:
public int read(char[] cbuf) throws IOException
范例:实现数据读取
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" + File.separator + "mldn.txt");// 指定要操作文件的路径
if (file.exists()) {// 文件存在则读取
Reader in = new FileReader(file);
char [] data = new char[1024];
int len = in.read(data);
System.out.println(new String(data, 0, len));
in.close();
}
}
字符流读取的时候只能够按照数组的形式来实现处理操作
6. 字节流与字符流的区别
现在通过一系列的分析已经可以清楚字节流与字符流的基本操作了,但是对于这两类流依然存在有区别的,重点来分析一下输出的处理操作。在使用OutputStream和Writer输出的最后发现都使用了close()方法进行了关闭处理。
在使用OutputStream类输出的时候如果现在没有使用close()方法关闭输出流发现内容依然可以实现正常的输出,但是如果在使用Writer的时候没有使用close()方法关闭输出流,那么这个时候内容将无法进行输出,因为Writer使用到了缓冲区,当使用close()方法的时候,实际上会出现有强制刷新缓冲区的情况,所以这个时候会将内容进行输出,如果没有关闭,那么将无法进行输出操作,所以此时如果在不关闭的情况下要想将全部的内容输出可以使用flush()方法强制性清空。
范例:使用Writer并强制性清空
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" + File.separator + "mldn.txt");// 指定要操作文件的路径
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();// 父目录一定要存在
}
Writer out = new FileWriter(file);// new FileWriter(file, false)//第二个参数是否追加
String str = "www.sunlh.vip\r\n";
out.write(str);
out.append("zhongguo");// 追加输出内容
out.flush();// 强制性刷新
//out.close();
}
字节流再进行处理的时候并不会使用到缓冲区,而字符流会使用到缓冲区。另外使用缓冲区的字符流更加适合中文的处理,所以在日后的程序开发之中,如果要涉及到包含有中文信息的输出一般都会使用字符流处理,但是从另外一方面字节流和字符流的基本处理形式是相似的,由于IO很多情况下都是进行数据的传输使用(二进制),所以本次的讲解将以字节流为主。
7. 转换流
所谓的转换流指的是可以实现字节流与字符流操作的功能转换,例如:进行输出的时候OutputStream需要将内容变为字节数组后才可以输出,而Writer可以直接输出字符串,这一点是很方便的,所以很多人就认为需要提供有一种转换的机制,来实现不同流类型的转换操作,为此在java.io包里面提供有两个类:InputStreamReader、OutputStreamWriter。
类 | 定义 | 构造方法 |
---|---|---|
OutputStreamWriter | public class OutputStreamWriter extends Writer | public OutputStreamWriter(OutputStream out) |
InputStreamReader | public class InputStreamReader extends Reader | public InputStreamReader(InputStream in) |
通过类的继承结构与构造方法可以发现,所谓的转换处理就是将要接收到的字节流对象通过向上转型变为字符流对象。
范例:观察转换
public static void main(String[] args) throws Exception {
File file = new File("D:" + File.separator + "hello" + File.separator + "mldn.txt");// 指定要操作文件的路径
if(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();// 父目录一定要存在
}
OutputStream output = new FileOutputStream(file);
Writer out = new OutputStreamWriter(output);// 字节流变为字符流
out.write("www.sunlh.vip");// 直接输出字符串,字符流适合处理中文
out.close();
}
讲解转换流的主要目的基本上不是为了让开发者记住它,而是知道有这样一种功能,但同时更多的是需要结构的分析处理。通过之前的字节流与字符流的一系列分析之后你会发现OutputStream类有FileOutputStream直接子类、InputStream类有FileInputStream直接子类,但是来观察一下FileWriter、FileReader类的继承关系。
FileWriter | FileReader |
---|---|
public class FileWriter extends OutputStreamWriter | public class FileReader extends InputStreamReader |
FileWriter继承结构
FileReader继承结构
实际上所谓的缓存都是指的是程序中间的一道处理缓冲区。