Java I/O
在整个的java.io包中提供了5个重要的I/O类和1个接口类。5个类分别是File、OutputStream、InputStream、Writer、Reader ,1个接口是指Serializable序列化接口。具体的使用方式可以查看JDK的参考文档。
Java NIO 实现
Java NIO的实现内容主要有如下的三个核心内容
- Selector(选择器)
- Channel(通道)
- Buffer(缓冲区)
Selector 用于监听多个Channel的操作事件,例如连接的打开、数据的连接处理等,所以说一个线程可以实现对于多个Channel的管理操作。
传统的I/O是基于数据流的方式进行I/O的读写操作;NIO是基于Channel和Buffer进行的I/O操作,数据从Channel中读取到了Buffer中,或者操作从Buffer数据写入到Channel中。
JavaNIO与传统I/O操作的区别
- I/O操作是面对流对象的,而NIO则是面向缓冲区的;在面向流的操作中,数据只能在输入流或者输出流中进行连续的读写操作,数据没有缓冲区这个概念,所以字节流无法进行前后移动。在NIO的操作中数据是从Channel中读取到Buffer中,然后再从Buffer中读取的Channel中,既然出现了Channel就可以很容易的实现数据的前后操作。也就出现了NIO中常见的拆包、粘包问题。
- 传统的I/O方式是阻塞的模式,NIO是非阻塞模式,在传统的I/O模式下,当用户调用了read()或者是write()进行读写操作的时候,线程一直处于阻塞状态等待数据的写入写出操作。NIO通过Selector机制监听每个Channel的事件的变化,当Channel上有数据发生变化的时候通知对应的线程进行读写操作。对于读请求,在Channel有可用数据的时候,线程现将数据写入到Buffer上,在没有数据的时候,线程执行其他的业务逻辑操作。对于写请求,在一个线程进行写操作的时候数据写入到某个Channel中的时候,只需要Channel上的数据通过异步的方式写入到Buffer就可以了。Buffer上的数据会通过异步的方式写入到目标机器的Channel上,用户线程不需要等待数据完全被写入,就可以执行其他的业务逻辑操作。
非阻塞IO模型中Selector线程工作如下图所示。
Channel
Channel和I/O流Stream类似,只不过Stream是单向的,Channel是双向的,也就是说流式只能是输入流或者输出流,但是Channel即可用来进行读操作,也可以进行写操作。
NIO中Channel主要的实现类有如下几种:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
分别对应的是文件I/O操作、UDP、TCP IO 、Socket Client和Socket Server操作。
Buffer
Buffer实际上通过上面的理解我们可以看出,它是一个容器,其内部通过一个连续的字节数组存储I/O上的数据。在NIO中,Channel在文件、网络上对数据的读取或写入都必须经过Buffer。
如图所示,客户端向着服务端发送数据的时候,先将数据写入到Buffer中,然后将Buffer中的数据写入到服务端对应的Channel中,服务端在接受到数据的时候通过Channel将数据读入到Buffer中,然后从Buffer中读取数据并进行对应的处理。
Java NIO包中Buffer是一个抽象类主要有如下的一些实现类
- ByteBuffer
- IntBuffer
- CharBuffer
- LongBuffer
- DoubleBuffer
- FloatBuffer
- ShortBuffer
Selector
Selector选择器,用来检测在多个Channel上是否有I/O操作事件发生,并且对检测到的I/O事件进行相应的处理。所以Selector通过一个线程就可以实现对于多个Channel的管理。这样就不需要为每个连接都创建线程,避免了线程资源和在多线程上下文切换导致的开销。
Selector只有在Channel有读写事件发生的时候,才能调用I/O函数进行读写操作。极大地减少了系统开销,提高系统的并发量。
服务端示例代码
package nioDemo;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Random;
import java.util.Set;
/*服务器端,:接收客户端发送过来的数据并显示,
*服务器把上接收到的数据加上"echo from service:"再发送回去*/
public class ServiceSocketChannelDemo {
public static class TCPEchoServer implements Runnable{
/*服务器地址*/
private InetSocketAddress localAddress;
public TCPEchoServer(int port) throws IOException{
this.localAddress = new InetSocketAddress(port);
}
@Override
public void run(){
Charset utf8 = Charset.forName("UTF-8");
ServerSocketChannel ssc = null;
Selector selector = null;
Random rnd = new Random();
try {
/*创建选择器*/
selector = Selector.open();
/*创建服务器通道*/
ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
/*设置监听服务器的端口,设置最大连接缓冲数为100*/
ssc.bind(localAddress, 100);
/*服务器通道只能对tcp链接事件感兴趣*/
ssc.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e1) {
System.out.println("server start failed");
return;
}
System.out.println("server start with address : " + localAddress);
/*服务器线程被中断后会退出*/
try{
while(!Thread.currentThread().