Java–NIO
Java传统的IO是阻塞式的,而NIO则提供了非阻塞式的IO.
NIO即New IO, java从jdk1.4开始引入,用于解决阻塞式的处理,所以也可以说是No block IO.
NIO主要是用来处理Socket中阻塞的问题。
1、NIO组件
Java NIO 由以下几个核心部分组成:
- Channels
- Buffers
- Selectors
Channel
所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。
Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
正如你所看到的,这些通道涵盖了UDP 和 TCP 网络IO,以及文件IO。
Buffer
以下是Java NIO里关键的Buffer实现:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。
selector
Selector允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如,在一个聊天服务器中。
这是在一个单线程中使用一个Selector处理3个Channel的图示:
要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。
2、Channel、Buffer
Channel和Buffer在一起使用,channel连接一个目的地,从Buffer中进行读写。
1、FileChannel、ByteBuffer
Java NIO中的FileChannel是一个连接到文件的通道。可以通过文件通道读写文件。
FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。
Buffer可以创建对应大小的缓存区。
打开FileChannel、创建ByteBuffer
通过使用一个InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例.
RandomAccessFile file=new RandomAccessFile("./a.txt","rw");
FileChannel fChannel= file.getChannel();
Buffer没有公开的构造方法,使用allocate方法创建实例,传入一个数字作为容量。
ByteBuffer buffer=ByteBuffer.allocate(48);
Buffer中关于数据的读写起始位置,可以看做指针:
Buffer有三个属性:
- capacity:容量,即创建时指定的数值
- position:定位,即当前缓冲中指向的位置
- limit:极限:即当前Buffer中读写所处的极限位置。
操作Buffer和Channel
Channel和Buffer之间进行数据读写,和普通IO基本一样,通过write和read方法。
在对Buffer进行读写,可以使用get,put方法。
Buffer进行读写之前需要对Buffer中的三个属性进行调整,以便数据读写正确。
向Buffer中写数据,需要使用clear()与compact()方法,如果调用的是clear()方法,position将被设回0,limit被设置成 capacity的值。
如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,那么使用compact()方法。
从Buffer中读数据,需要使用flip方法。
关闭Channel
Buffer不需要关闭,jvm会自动处理。
Channel关闭使用close方法即可。
从文件中读写数据
RandomAccessFile file=new RandomAccessFile("./a.txt","rw");
FileChannel fChannel= file.getChannel();//获取通道
ByteBuffer buffer=ByteBuffer.allocate(48);//get Buffer
int len=fChannel.read(buffer);//返回长度
buffer.flip();//进入读模式
byte[] b=new byte[64];
buffer.get(b,0,len);//读到b中len个字节
System.out.println(new String(b,0,len));//输出
buffer.clear();//全部读完,进入写模式
String data="\nnow is: "+System.currentTimeMillis();
buffer.put(data.getBytes());//向通道中写入
buffer.flip();//进入读模式
fChannel.write(buffer);//从Buffer中读出
file.close();
2、SocketChannel、ServerSocketChannel
Socket才是NIO真正使用的地方。
ServerSocketChannel可以设置为非阻塞式,这种情况下的网络Io即不会阻塞。
configureBlocking(false);
打开/关闭Channel
Socket通过open打开连接,需要一个Socket地址。关闭使用close,非阻塞情况下连接可以自动关闭,无需显示调用。
SocketChannel sChannel=SocketChannel.open();
sChannel.connect(new InetSocketAddress("127.0.0.1", 8001));
操作
操作基本一样,如下:
使用客户端向服务端发送数据,服务端返回数据。
客户端
SocketChannel sChannel=SocketChannel.open();
sChannel.connect(new InetSocketAddress("127.0.0.1", 8001));
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put("hello world".getBytes());
buffer.flip();
sChannel.write(buffer);
buffer.clear();
int len=sChannel.read(buffer);
buffer.flip();
byte[] b=new byte[1024];
buffer.get(b, 0, len);
System.out.println(new String(b,0,len));
服务端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",8001));
serverSocketChannel.configureBlocking(false);
ByteBuffer buffer=ByteBuffer.allocate(1024);
while(true){
//这里不再阻塞,直接向下执行
SocketChannel socketChannel =
serverSocketChannel.accept();
if(socketChannel!=null){
int len=socketChannel.read(buffer);
buffer.flip();
byte[] b=new byte[1024];
buffer.get(b,0,len);
System.out.println(new String(b,0,len));
buffer.clear();
buffer.put("yes".getBytes());
buffer.flip();
socketChannel.write(buffer);
break;
}
3、Scatter|Gather
分散与聚集,通道可以与多个Buffer建立关联。
写入多个Buffer时,如果某个Buffer满了,则转入下个Buffer。
同样,读取时,某个Buffer被读完,则从其他Buffer读。
file=“b.txt”
abcdefghijkhello world
RandomAccessFile rFile=new RandomAccessFile(file, "rw");
ByteBuffer buffer1=ByteBuffer.allocate(10);
ByteBuffer buffer2=ByteBuffer.allocate(10);
ByteBuffer buffer3=ByteBuffer.allocate(10);
ByteBuffer[] buffers={buffer1,buffer2,buffer3};
FileChannel fc= rFile.getChannel();
long l=fc.read(buffers);//读到buffers中,buffer1满则读到buffer2
System.out.println("buffers "+l);
buffer3.flip();
buffer2.flip();
buffer1.flip();
System.out.println((char)buffer1.get());
System.out.println((char)buffer2.get());
System.out.println((char)buffer3.get());
buffer1.clear();
buffer2.clear();
buffer3.clear();
buffer1.put("scatter".getBytes());
buffer2.put("gatter".getBytes());
buffer3.flip();
buffer2.flip();
buffer1.flip();
fc.write(buffers);//从buffers中写出,buffer1写完,则从buffer2中写。