IO流回顾解析详解
概述:
-
什么是流?
在计算机的世界中计算机只有0和1,我们所看到的所有东西(图片,视频,文字…)都是由这两个数字所组成的,经过二进制转换成的一个文字,一张图片,一条视频。而我们需要将一个文件(文本,图片,视频…)copy到一个文件夹中,而文件copy的过程我们称之为流,过程就是将一个文件转化成二进制的数据集,把数据一点点地传递到文件夹中,类似于水流一般,所以这种数据集就为一个数据流。
-
IO流特点:
1. 顺序读写:
读写数据时,一般都是顺序执行的,从文件的第一个字节到最后一个字节,写出时也是也如此(RandomAccessFile 可以实现随机读写)。
2. 字节数组:
读写数据本质都是对字节做读取和写出操作,字节流就是在流的基础上转化为一个个字符,所以字节数组是IO流读写数据的本质。
-
流的种类:
1. 输入流(InputStream)- Reader:从磁盘或者设备数据输入到应用进程中。
2. 输出流(outputStream)- Writer:将应用进程中的数据输入到磁盘或者其他的设备上保存。
-
数据的基础单位:字节流 与 字符流
1. 字节流:以字节(8bit)为单位做数据传输。
2. 字符流:以字符为单位(1-3字符)做数据传输。
注:
(1). Java中的字符采用Unicode 标准,在读取与输出的过程,以字符为单位,查找对应的码表将字节转换为对应的字符。
(2). 文本格式的数据优先采用字符流,除此之外其它类型的数据文件(图片,音频…)使用字节流。
(3). Stream 就知道是字节流,看到 Reader / Writer 就知道是字符流。
public static void byteStreamIn() {
InputStream inputStream = null;
try {
inputStream = new FileInputStream("D:\\桌面\\静电式图片\\img\\io.txt");
byte[] bytes = new byte[1024];
int read = inputStream.read(bytes);
String str = new String(bytes);
System.out.println(str);
System.out.println(read);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) inputStream.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
public static void byteStreamOut() {
OutputStream outputStream = null;
InputStream inputStream = null;
try {
outputStream = new FileOutputStream("D:\\桌面\\静电式图片\\img\\妹2.jpg");
inputStream = new FileInputStream("D:\\桌面\\静电式图片\\img\\妹1.jpg");
byte[] bytes = new byte[1024];
int temp;
while ((temp = inputStream.read(bytes)) != -1) {
System.out.println(temp);
String str = new String(bytes);
System.out.println(str);
outputStream.write(bytes);
}
System.out.println("OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (outputStream != null)
outputStream.close();
if (inputStream != null)
inputStream.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
public static void charStreamIn() {
try {
Reader reader = new FileReader("D:\\桌面\\静电式图片\\img\\io1.txt");
char[] chars = new char[1024];
int temp;
while ((temp = reader.read(chars)) != -1) {
System.out.println(temp);
String str = new String(chars);
System.out.println(str);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void charStreamOut() {
Reader reader = null;
Writer writer = null;
try {
writer = new FileWriter("D:\\桌面\\静电式图片\\img\\io2.txt");
reader = new FileReader("D:\\桌面\\静电式图片\\img\\io1.txt");
char[] chars = new char[1024];
int temp;
while ((temp = reader.read(chars)) != -1) {
System.out.println(temp);
String str = new String(chars);
System.out.println(str);
writer.append(str);
}
System.out.println("OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader != null) reader.close();
if (writer != null) writer.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
public static void BufferedIntPutStreamByte() {
InputStream inputStream = null;
try {
inputStream = new FileInputStream("D:\\桌面\\静电式图片\\img\\io1.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
byte[] bytes = new byte[1024];
int temp;
while ((temp = bufferedInputStream.read(bytes)) != -1) {
System.out.println(temp);
String string = new String(bytes);
System.out.println(string);
}
bufferedInputStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) inputStream.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
public static void BufferedOutPutStreamByte() {
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream("D:\\桌面\\静电式图片\\img\\io1.txt");
outputStream = new FileOutputStream("D:\\桌面\\静电式图片\\img\\io99.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream);
byte[] bytes = new byte[1024];
int temp;
while ((temp = bufferedInputStream.read(bytes)) != -1) {
System.out.println(temp);
String string = new String(bytes);
System.out.println(string);
bufferedOutputStream.write(bytes);
}
bufferedOutputStream.close();
bufferedInputStream.close();
System.out.println("ok");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
public static void BufferedIntPutStreamChar() {
Reader reader = null;
try {
reader = new FileReader("D:\\桌面\\静电式图片\\img\\io1.txt");
BufferedReader bufferedReader = new BufferedReader(reader);
char[] chars = new char[1024];
int temp;
int tot_count = 0;
while ((temp = bufferedReader.read(chars)) != -1) {
System.out.println(temp);
tot_count += temp;
String str = new String(chars);
System.out.println(str);
}
System.out.println("总数:"+tot_count);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void BufferedOutPutStreamChar() {
Reader reader = null;
Writer writer = null;
try {
reader = new FileReader("D:\\桌面\\静电式图片\\img\\222.txt");
writer = new FileWriter("D:\\桌面\\静电式图片\\img\\333.txt");
BufferedReader bufferedReader = new BufferedReader(reader);
BufferedWriter bufferedWriter = new BufferedWriter(writer);
char[] chars = new char[1024];
int temp;
int tot_count = 0;
while ((temp = bufferedReader.read(chars)) != -1) {
System.out.println(temp);
tot_count += temp;
String str = new String(chars);
System.out.println(str);
bufferedWriter.append(str);
}
System.out.println("总数:" + tot_count);
bufferedReader.close();
bufferedWriter.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (reader != null) reader.close();
if (writer != null) writer.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
public static void BufferedIntPutStreamCharLine() {
Reader reader = null;
try {
reader = new FileReader("D:\\桌面\\静电式图片\\img\\222.txt");
BufferedReader bufferedReader = new BufferedReader(reader);
int tot_count = 0;
String line ;
while ((line = bufferedReader.readLine()) != null) {
if (StringUtils.isNotBlank(line)) {
if (line.contains(" ")) {
String replace = line.replace(" ", "");
System.out.println(replace);
} else {
System.out.println(line);
}
tot_count += 1;
}
}
System.out.println("行数:"+tot_count);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void BufferedOutPutStreamCharLine() {
Reader reader = null;
Writer writer = null;
try {
reader = new FileReader("D:\\桌面\\静电式图片\\img\\222.txt");
writer = new FileWriter("D:\\桌面\\静电式图片\\img\\555.txt");
BufferedReader bufferedReader = new BufferedReader(reader);
BufferedWriter bufferedWriter = new BufferedWriter(writer);
int tot_count = 0;
String line ;
while ((line = bufferedReader.readLine()) != null) {
if (StringUtils.isNotBlank(line)) {
if (line.contains(" ")) {
String replace = line.replace(" ", "");
System.out.println(replace);
bufferedWriter.append(replace);
bufferedWriter.newLine();
} else {
System.out.println(line);
bufferedWriter.append(line);
bufferedWriter.newLine();
}
tot_count += 1;
}
}
bufferedReader.close();
bufferedWriter.close();
System.out.println("行数:"+tot_count);
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (reader != null) reader.close();
if (writer != null) writer.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
-
转换流
转换流 / 数据类型 | 字节流与字符流之间的转换 |
---|
(输入)字节流 => 字符流 | InputStreamReader |
(输出)字符流 => 字节流 | OutputStreamWriter |
1. InputStreamReader:从字节流转换为字符流,将字节数据转换为字符数据读入到内存。
2. OutputStreamWriter:从字符流转换为字节流,将字符数据转换为字节数据写出到指定位置。
public static void InputStreamReadeByte(){
InputStream inputStream = null;
try {
inputStream = new FileInputStream("D:\\桌面\\静电式图片\\img\\555.txt");
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
char[] chars = new char[1024];
int temp;
while ((temp = inputStreamReader.read(chars)) != -1) {
System.out.println(temp);
String str = new String(chars);
System.out.println(str);
}
inputStreamReader.close();
System.out.println("OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) inputStream.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
public static void OutputStreamReadeByte(){
InputStream inputStream = null;
OutputStream outputStream = null;
try {
inputStream = new FileInputStream("D:\\桌面\\静电式图片\\img\\555.txt");
outputStream = new FileOutputStream("D:\\桌面\\静电式图片\\img\\666.txt");
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
char[] chars = new char[1024];
int temp;
while ((temp = inputStreamReader.read(chars)) != -1) {
System.out.println(temp);
String str = new String(chars);
System.out.println(str);
outputStreamWriter.write(str);
}
inputStreamReader.close();
outputStreamWriter.close();
System.out.println("OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
} catch (IOException io) {
io.printStackTrace();
}
}
}
类型 | 烧开水 |
---|
BIO (同步阻塞 I/O) | 一直监测着某个水壶,该水壶烧开水后再监测下一个水壶 |
NIO(同步非阻塞 I/O) | 每隔一段时间就看看所有水壶的状态,哪个水壶烧开水就去处理哪个水壶 |
AIO(异步非阻塞 I/O) | 不用监测水壶,每个水壶烧开水后都会主动通知线程说:“我的水烧开了,来处理我吧” |
1. BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
2. NIO:同步非阻塞式IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。
3. AIO(NIO.2):异步非阻塞式IO,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由OS先完成了再通知服务器应用去启动线程进行处理。
BIO
1. 同步阻塞式IO,相信每一个学习过操作系统网络编程或者任何语言的网络编程的人都很熟悉,在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。
2. 如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理,大概原理图就像这样:
3. 虽然此时服务器具备了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃,NIO可以一定程度解决这个问题。
NIO
BIO 与 NIO 的区别,BIO 是面向流的 IO,它建立的通道都是单向的,所以输入和输出流的通道不相同,必须建立2个通道,通道内的都是传输==0101001···==的字节数据。而在 NIO 中,不再是面向流的 IO 了,而是面向缓冲区,它会建立一个通道(Channel),该通道我们可以理解为铁路,该铁路上可以运输各种货物,而通道上会有一个缓冲区(Buffer)用于存储真正的数据,缓冲区我们可以理解为一辆火车。通道(铁路)只是作为运输数据的一个连接资源,而真正存储数据的是缓冲区(火车)。即通道负责传输,缓冲区负责存储。
BIO | NIO |
---|
面向流(Stream) | 面向缓冲区(Buffer) |
单向通道 | 双向通道 |
阻塞 | 非阻塞 |
| 选择器(Selectors) |
-
同步非阻塞式IO,关键是采用了事件驱动的思想来实现了一个多路转换器。
-
NIO与BIO最大的区别就是只需要开启一个线程就可以处理来自多个客户端的IO事件,这是怎么做到的呢?
-
就是多路复用器,可以监听来自多个客户端的IO事件:
A. 若服务端监听到客户端连接请求,便为其建立通信套接字(java中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字。
B. 若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。
C. 监听多个客户端的连接请求和接收数据请求同时还能监听自己时候有数据要发送。
总之就是在一个线程中就可以调用多路复用接口(java中是select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应函数处理。
1. 打开ServerSocketChannel,监听客户端连接
2. 绑定监听端口,设置连接为非阻塞模式
3. 创建Reactor线程,创建多路复用器并启动线程
4. 将ServerSocketChannel注册到Reactor线程中的Selector上,监听ACCEPT事件
5. Selector轮询准备就绪的key
6. Selector监听到新的客户端接入,处理新的接入请求,完成TCP三次握手,简历物理链路
7. 设置客户端链路为非阻塞模式
8. 将新接入的客户端连接注册到Reactor线程的Selector上,监听读操作,读取客户端发送的网络消息
9. 异步读取客户端消息到缓冲区
10. 对Buffer编解码,处理半包消息,将解码成功的消息封装成Task
11. 将应答消息编码为Buffer,调用SocketChannel的write将消息异步发送给客户端
-
所以不能保证一次能吧需要发送的数据发送完,此时就会出现写半包的问题。我们需要注册写操作,不断轮询Selector将没有发送完的消息发送完毕,然后通过Buffer的hasRemain()方法判断消息是否发送完成。
AIO(NIO.2)
-
NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。
-
异步的套接字通道时真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动I/O(AIO)。他不需要过多的Selector对注册的通道进行轮询即可实现异步读写,从而简化了NIO的编程模型。API比NIO的使用起来真的简单多了,主要就是监听、读、写等各种CompletionHandler。此处本应有一个WriteHandler的,确实,我们在ReadHandler中,以一个匿名内部类实现了它。
-
AIO是真正的异步非阻塞的,所以,在面对超级大量的客户端,更能得心应手