JAVA基础之I/O流
一、什么是I/O流?
1、从计算机结构的角度来解读一下 I/O。
根据冯.诺依曼结构,计算机结构分为 5 大部分:运算器、控制器、存储器、输入设备、输出设备。
IO 即 Input/Output
,输入和输出。数据输入到计算机内存的过程即输入,反之输出到外部存储(比如数据库,文件,远程主机)的过程即输出。数据传输过程类似于水流,因此称为 IO 流。IO 流在 Java 中分为输入流和输出流,而根据数据的处理方式又分为字节流和字符流。
2、字节流,字符流
Java IO 流的 40 多个类都是从如下 4 个抽象类基类中派生出来的。
InputStream
/Reader
: 所有的输入流的基类,前者是字节输入流,后者是字符输入流。OutputStream
/Writer
: 所有输出流的基类,前者是字节输出流,后者是字符输出流。- 由于以上4个都为抽象类,使用不能直接创建,使用时只能通过创建它的实现子类
3、节点流和处理流
- 节点流可以从特定数据源读取数据,如FileReader、FileWriter
- 处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。如BufferedReader.处理流的构造方法总是要带一个其他的流对象做参数。一个流对象经过其他流的多次包装,称为流的链接。
问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节,那为什么 I/O 流操作要分为字节流操作和字符流操作呢?
个人认为主要有两点原因:
- 字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是比较耗时;
- 如果我们不知道编码类型的话,使用字节流的过程中很容易出现乱码问题。
二、BIO、NIO和AIO的区别
从应用程序的视角来看的话,我们的应用程序对操作系统的内核发起 IO 调用(系统调用),操作系统负责的内核执行具体的 IO 操作。也就是说,我们的应用程序实际上只是发起了 IO 操作的调用而已,具体 IO 的执行是由操作系统的内核来完成的。
UNIX 系统下, IO 模型一共有 5 种: 同步阻塞 I/O、同步非阻塞 I/O、I/O 多路复用、信号驱动 I/O 和异步 I/O。
1、BIO(同步阻塞io)
BIO 属于同步阻塞 IO 模型 。
同步阻塞 IO 模型中,应用程序发起 read 调用后,会一直阻塞,直到内核把数据拷贝到用户空间。
在客户端连接数量不高的情况下,是没问题的。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。
2、NIO(Non-blocking/New I/O)
Java 中的 NIO 于 Java 1.4 中引入,对应 java.nio
包,提供了 Channel
, Selector
,Buffer
等抽象。NIO 中的 N 可以理解为 Non-blocking,不单纯是 New。它是支持面向缓冲的,基于通道的 I/O 操作方法。 对于高负载、高并发的(网络)应用,应使用 NIO 。
Java 中的 NIO 可以看作是 I/O 多路复用模型。也有很多人认为,Java 中的 NIO 属于同步非阻塞 IO 模型。
跟着我的思路往下看看,相信你会得到答案!
我们先来看看 同步非阻塞 IO 模型。
同步非阻塞 IO 模型中,应用程序会一直发起 read 调用,等待数据从内核空间拷贝到用户空间的这段时间里,线程依然是阻塞的,直到在内核把数据拷贝到用户空间。
相比于同步阻塞 IO 模型,同步非阻塞 IO 模型确实有了很大改进。通过轮询操作,避免了一直阻塞。
但是,这种 IO 模型同样存在问题:应用程序不断进行 I/O 系统调用轮询数据是否已经准备好的过程是十分消耗 CPU 资源的。
这个时候,I/O 多路复用模型 就上场了。
IO 多路复用模型中,线程首先发起 select 调用,询问内核数据是否准备就绪,等内核把数据准备好了,用户线程再发起 read 调用。read 调用的过程(数据从内核空间 -> 用户空间)还是阻塞的。
目前支持 IO 多路复用的系统调用,有 select,epoll 等等。select 系统调用,目前几乎在所有的操作系统上都有支持。
- select 调用 :内核提供的系统调用,它支持一次查询多个系统调用的可用状态。几乎所有的操作系统都支持。
- epoll 调用 :linux 2.6 内核,属于 select 调用的增强版本,优化了 IO 的执行效率。
IO 多路复用模型,通过减少无效的系统调用,减少了对 CPU 资源的消耗。
Java 中的 NIO ,有一个非常重要的选择器 ( Selector ) 的概念,也可以被称为 多路复用器。通过它,只需要一个线程便可以管理多个客户端连接。当客户端数据到了之后,才会为其服务。
3、AIO(异步 IO 模型)
AIO 也就是 NIO 2。Java 7 中引入了 NIO 的改进版 NIO 2,它是异步 IO 模型。
异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。
目前来说 AIO 的应用还不是很广泛。Netty 之前也尝试使用过 AIO,不过又放弃了。这是因为,Netty 使用了 AIO 之后,在 Linux 系统上的性能并没有多少提升。
最后,来一张图,简单总结一下 Java 中的 BIO、NIO、AIO。
三、I/O流的使用(节点流)
1、文件专属:
java.io.FileInputStream(掌握)
java.io.FileOutputStream(掌握)
java.io.FileReader
java.io.FileWriter
2、转换流:(将字节流转换成字符流)
java.io.InputStreamReader
java.io.OutputStreamWriter
3、缓冲流专属:
java.io.BufferedReader
java.io.BufferedWriter
java.io.BufferedInputStream
java.io.BufferedOutputStream
4、数据流专属:
java.io.DataInputStream
java.io.DataOutputStream
5、标准输出流:
java.io.PrintWriter
java.io.PrintStream(掌握)
6、对象专属流:
java.io.ObjectInputStream(掌握)
java.io.ObjectOutputStream(掌握)
7、File文件类:
java.io.File
1、File类
File的构造方法
构造方法摘要 |
---|
File(File parent, String child) 根据parent抽象路径名和child路径名字符串创建一个新File实例 |
File(String pathname) 通过通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。 |
File(File parent, String child) 根据 parent 路径名字符串和 child 路径名字符串创建一个新 File 实例。 |
File(URI uri) 通过将给定的 file: URI 转换为一个抽象路径名来创建一个新的 File 实例。 |
v
@Test
public void FileCreate01(){
String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\a.txt";
File file = new File(filePath);
try {
file.createNewFile();
System.out.println("文件创建成功");
} catch (IOException e) {
e.printStackTrace();
}
}
@Test
public void FileCreate02(){
String parentPath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\";
String childPath = "b.txt";
File file = new File(parentPath, childPath);
try {
file.createNewFile();
System.out.println("文件创建成功2");
} catch (IOException e) {
e.printStackTrace();
}
}
2、字节流Inpustream/Outputsteam

2.1、FileInputStream类
// 测试read方法
@Test
public void fist() throws IOException {
String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\a.txt";
File file = new File(filePath);
FileInputStream fileInputStream = new FileInputStream(file);
int readDate = 0;
while ((readDate = fileInputStream.read())!= -1){
System.out.print((char) readDate);
}
fileInputStream.close();
}
// 测试read(byte[])方法
@Test
public void fist_read() throws IOException {
String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\a.txt";
File file = new File(filePath);
byte[] br = new byte[8];
int len;
FileInputStream fileInputStream = new FileInputStream(file);
int readDate = 0;
// 返回-1表示读取完毕,正常读取返回读取的个数
while ((len = fileInputStream.read(br))!= -1){
System.out.print(new String(br,0, len));
}
fileInputStream.close();
}
2.2、FileOutputStream类
1、这种方式写入的是直接覆盖原来文件的内容
new FileOutputStream(file);
2、不覆盖原来的内容用
new FileOutputStream(file,ture);
@Test
//字符输入
public void fos1() throws IOException {
String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\b.txt";
File file = new File(filePath);
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(file);
fileOutputStream.write('h');//char->int 事实上传入的是int
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
fileOutputStream.close();
}
}
@Test
//数组输入
public void fos2() throws IOException {
String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\b.txt";
File file = new File(filePath);
FileOutputStream fileOutputStream = null;
char[] b = {'a', 'b', 'c', 'd'};
String a = "Is't fanny!";
try {
fileOutputStream = new FileOutputStream(file);
//写入一个字节
// fileOutputStream.write('h');//char->int 事实上传入的是int
fileOutputStream.write(a.getBytes());//通过getBytes(),将String转为char数组
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
fileOutputStream.close();
}
}
2.3、利用字节流进行文件复制
@Test
public void testcopy() throws IOException {
String filePath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\becopy.jpg";
String copyPath = "D:\\java\\savegit\\javaIOTest\\theTestBag\\myphoto.jpg";
File photo = new File(filePath);
File copy = new File(copyPath);
// 利用字节数组提升效率
byte[] br = new byte[1024];
int len;
FileOutputStream fileOutputStream = new FileOutputStream(copy);
FileInputStream fileInputStream = new FileInputStream(photo);
while ((len=fileInputStream.read(br))!=-1){
fileOutputStream.write(br,0,len);
}
fileInputStream.close();
fileOutputStream.close();
}
3、字符流Reader/Writer
字符流操作的对象唯char或char[]

3.1、FileReader
FileReader相关方法:
- new FileReader(File/String)
- read:每次读取单个字符,返回该字符,如果到文件末尾返回-1
- read(char[]):批量读取多个字符到数组,返回读取到的字符数,如果到文件末尾返回-1.
相关API:
- new String(char[]): 将char[]转换成String
- new String(char[],off,len): 将char[]指定的部分转换成String
3.2、FileWriter类

FileWriter常用方法
- new FileWriter(File/String):覆盖模式,相当于流的指针在首端
- new FileWriter(File/String,true):追加模式,相当于流的指针在尾端
- write(int):写入单个字符
- write(char[]):写入指定数组
- write(char[],off,len):写入指定数组的指定部分
- write (string) :写入整个字符串
- write(string,off,len):写入字符串的指定部分
相关APl:
String类: toCharArray:将String转换成char[]
注意:
FieWriter使用后,必须要关闭(close)或刷新(flush),否则写入不到指定的文件!
3.3、字符流实现文件复制
@Test
public void cp() throws IOException {
String path = "D:\\java\\savegit\\javaIOTest\\theTestBag\\teseReaderAndWriter.txt";
File file = new File(path);
File becp = new File("D:\\java\\savegit\\javaIOTest\\theTestBag\\copyReaderAndWriter.txt");
char[] br = new char[8];
int len;
FileWriter fileWriter = new FileWriter(becp);
FileReader fileReader = new FileReader(file);
//返回-1表示读取完毕,正常读取返回读取的个数
while ((len=fileReader.read(br))!=-1){
fileWriter.write(br,0,len);
}
fileReader.close();
fileWriter.close();
}
四、I/O流的使用(处理流)

通过装饰器模式(底层实现有点像多态),得到属性的子类对象,进而实现封装。

1、Bufferreader
@Test
public void BR() throws IOException {
File file = new File("D:\\java\\savegit\\javaIOTest\\theTestBag\\copyReaderAndWriter.txt");
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
String Line;
// 按行读取
while ((Line=bufferedReader.readLine())!=null){
System.out.println(Line);
}
bufferedReader.close();
}
2、Buffer进行复制文件
//处理流进行复制粘贴
@Test
public void BW() throws IOException{
File file = new File("D:\\java\\savegit\\javaIOTest\\theTestBag\\copyReaderAndWriter.txt");
BufferedReader bufferedReader = new BufferedReader(new FileReader(file));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("D:\\java\\savegit\\javaIOTest\\theTestBag\\cpBW.txt"));
String Line;
// 读取一行数据时没有带换行符,所以,写入时要插入一个换行符
while ((Line=bufferedReader.readLine())!=null){
bufferedWriter.write(Line);
bufferedWriter.newLine();
}
bufferedReader.close();
bufferedWriter.close();
}