1 基本概念
- Java把不同类型的输入、输出源抽象为流(stream),其中输入或输出的数据称为数据流(Data Stream),用统一的接口来表示。
- Java中的io是实现输入和输出的基础,可以方便的实现数据的输入和输出操作。
- Java把所有的流类型代码都放在java io包下,用于实现输入和输出功能。
2 分类
2.1输入流和输出流
按照流的流向划分,可以分为输入流和输出流。
注意,流向是以内存(程序)的角度来划分的,输入到内存(程序)的就是输入流,从内存(程序)输出的就是输出流,如下图:
2.1.1输入流
Java的输入流主要是InputStream和Reader作为基类。它们都是一些抽象基类,无法直接创建实例。
对于输入流来说,它们把输入设备抽象为一个“水管”,这个水管的每个“水滴”依次排列。如下图:
操作位置由隐式的记录指针决定,取出数据后记录指针会自动移动,同时也提供了方法操作记录指针。
字节流和字符流的处理方式是一样的,只是他们的输入/输出单位不同而已,字节流的单位是字节,字符流的单位是字符。
2.1.2输出流
Java的输出流主要是OutputStream和Writer作为基类。它们都是一些抽象基类,无法直接创建实例。
对于输出流来说,它们同样把输出设备抽象为一个“水管”,只是这个水管中没有“水滴”。如下图:
操作位置由隐式的记录指针决定,输出的“水滴”放置后,记录指针自动移动,同时也提供了方法操作记录指针。
字节流和字符流的处理方式是一样的,只是他们的输入/输出单位不同而已,字节流的单位是字节,字符流的单位是字符。
2.2字节流和字符流
按照操作单元划分,可以分为字节流和字符流。
字节流主要是由InputStream和OutputStream作为基类,而字符流主要是Reader和Writer作为基类。
字节流和字符流的区别:
- 读写单位不同:字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能度多个字节。
- 处理对象不同:字节流能处理所有类型的数据(如图片、avi等),而字符流只能处理字符类型的数据。
- 二进制位数:字节流一次操作的是8位二进制,而字符流一次操作的是16位二进制。
- 中文处理:字节流以字节为单位,故无法处理中文(因一个中文占两个字节)。但字符流可以处理。
- 缓冲区:字节流是直接对文件进行处理,而字符流存在缓冲区,所有操作在缓冲区完成后再对文件进行处理。
3.类图
3.1基类
红色底线,是所有输入/输出流的父类,是一个抽象类。
InputStream/Reader:所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer:所有的输出流的基类,前者是字节输出流,后者是字符输出流。
3.2节点流
橙色底线,直接与数据源相连,读入或读出。
直接使用节点流,读写不方便,为了更快的读写文件,才有了处理流。
常用的节点流:
- 文件处理:FileInputStream、FileOutputStream、FileReader、FileWriter。
- 数组处理:ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter。
- 字符串处理:StringReader、StringWriter。
- 多线程管道通信:PipedInputStream、PipedOutputStream、PipedReader、PipedWriter。
注意,FilterInputStream和FilterOutputStream使用的是设计模式中的装饰器模式。
3.3处理流
处理流和节点流一块使用,在节点流的基础上,再套接一层,套接在节点流上的就是处理流。处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
处理流可细分为:
1、缓冲流,绿色底线,增加缓冲功能,避免频繁读写硬盘。常用的有BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter。
2、转换流,紫色底线,实现字节流和字符流之间的转换。常用的有InputStreamReader、OutputStreamReader。
3、数据流,黑色底线,提供将基础数据类型写入或读取到文件中。常用的有DataInputStream、DataOutputStream。
4.示例
字节流FileInputStream和缓冲流BufferedInputStream读取文件内容代码:
/**
* 文件内容打印方法
* @param filePath 文件路径
* @return 文件内容
*/
public String filePrint(String filePath){
File targetFile = new File(filePath);
BufferedInputStream bufFis = null; //缓冲流对象
StringBuilder stringBuilder = new StringBuilder(); //结果集
try {
//基于节点流对象创建缓冲流对象
bufFis = new BufferedInputStream(new FileInputStream(targetFile));
int size = bufFis.available(); //返回输入流中可以被读的bytes字节的估计值
byte[] buf = new byte[size]; //创建一个缓存字节的容器数组
int hasRead = 0; //定义一个变量,保存实际读取的字节数
while((hasRead = bufFis.read(buf)) != -1){ //循环读取数据
String fileStr = new String(buf,0,hasRead); //转换数据为String
stringBuilder.append(fileStr);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally{
if(bufFis != null){
try {
bufFis.close(); //关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
return stringBuilder.toString(); //返回结果集
}
字节流FileOutputStream和缓冲流BufferedOutputStream写入文件内容代码:
/**
* 向文件写入指定内容
* @param filePath 文件路径
* @param fileMsg 指定内容
*/
public void fileWrite(String filePath,String fileMsg){
BufferedOutputStream buffFos = null; //缓冲流对象
try {
//基于节点流对象创建缓冲流对象
buffFos = new BufferedOutputStream(
new FileOutputStream(new File(filePath),true));
byte[] data = fileMsg.getBytes(); //把数据源转换为字节数组类型
buffFos.write(data,0,data.length); //通过流向文件写入数据
buffFos.flush(); //刷新流
System.out.println("文件写入成功!!");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(buffFos != null){
try {
buffFos.close(); //关闭流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
5.注意事项
在实际的项目中,所有的IO操作都应该放到子线程中操作,避免堵住主线程。