------NIO简介(1)--------
NIO组件
channel,buffer,selector,pip,filelock。
Channel分为:
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
UDP 和 TCP 网络IO,以及文件IO
Buffer分为:
ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
byte, short, int, long, float, double 和 char。
结构图
thread
|
selector
| | |
channel channel channel
使用Selector,得向Selector注册Channel,然后调用它的select()方法。
这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回
线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等
--------channel(2)---------
channel 可以理解为pip
channel <=======> buffer
FileChannel 从文件中读写数据。
DatagramChannel 能通过UDP读写网络中的数据。
SocketChannel 能通过TCP读写网络中的数据。
ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。
对每一个新进来的连接都会创建一个SocketChannel。
例子:
NIO
File - channel - buffer =============== application
buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据
--------NIO buffer(3)------------
四个步骤:
写入数据到Buffer
调用flip()方法
从Buffer中读取数据
调用clear()方法或者compact()方法
flip()方法
反转IO 写=========》读
capacity
position
capacity /limit -
要想获得一个Buffer对象首先要进行分配。
allocate方法。
ByteBuffer buf = ByteBuffer.allocate(48);
--------向Buffer中写数据-----
从Channel写到Buffer。
通过Buffer的put()方法写到Buffer里。
int bytesRead = inChannel.read(buf); //read into buffer.
buf.put(127);
调用flip()方法会将position设回0,并将limit设置成之前position的值。
----从Buffer中读取数据----
从Buffer读取数据到Channel。
使用get()方法从Buffer中读取数据。
int bytesWritten = inChannel.write(buf);
byte aByte = buf.get();
rewind()方法
Buffer.rewind()将position设回0,所以你可以重读Buffer中的所有数据。limit保持不变
如果调用的是clear()方法.
position将被设回0,limit被设置成 capacity的值,Buffer 被清空了
compact()方法
如果Buffer中仍有未读的数据,且后续还需要这些数据,但是此时想要先先写些数据,
compact()方法将所有未读的数据拷贝到Buffer起始处。然后将position设到最后一个未读元素正后面
mark()与reset()方法
通过调用Buffer.mark()方法,可以标记Buffer中的一个特定position。
之后可以通过调用Buffer.reset()方法恢复到这个position。
equals()方法
有相同的类型(byte、char、int等)。
Buffer中剩余的byte、char等的个数相等。
Buffer中所有剩余的byte、char等都相同。
compareTo()方法
ompareTo()方法比较两个Buffer的剩余元素
第一个不相等的元素小于另一个Buffer中对应的元素 。
第一个Buffer的元素个数比另一个少)
--------------Scatter/Gather(4)-----------------
scatter/gather用于描述从Channel
scatter 读操作时将读取的数据写入多个buffer中
gather 写操作时将多个buffer的数据写入同一个Channel
scatter
经常用于需要将传输的数据分开处理的场合,例如传输一个由消息头和消息体组成的消息,
你可能会将消息体和消息头分散到不同的buffer中,这样你可以方便的处理消息头和消息体。
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,
当一个buffer被写满后,channel紧接着向另一个buffer中写。
这也意味着它不适用于动态消息(译者注:消息大小不固定)
----Gathering---------
Gathering Writes是指数据从多个buffer写入到同一个channel。
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
注意只有position和limit之间的数据才会被写入。
因此,如果一个buffer的容量为128byte,但是仅仅包含58byte的数据
------------(五) 通道之间的数据传输---------------------
直接将数据从一个channel传输到另外一个channel。
transferFrom()
FileChannel ===========> FileChannel
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(position, count, fromChannel);
transferTo()
FileChannel ===========> Channel
-----------------------(六) Selector--------------
Selector(选择器)是Java NIO中能够检测一到多个NIO通道
一个单独的线程可以管理多个channel
Selector的创建
Selector selector = Selector.open();
向Selector注册通道
SelectableChannel.register()
channel.configureBlocking(false);
SelectionKey key = channel.register(selector,
Selectionkey.OP_READ);
Connect
Accept
Read
Write
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey
interest集合
ready集合
Channel
Selector
附加的对象(可选)
Channel channel = selectionKey.channel();
Selector selector = selectionKey.selector();
通过Selector选择通道
下面是select()方法:
int select()
int select(long timeout)
int selectNow()
完整的示例
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.select();
if(readyChannels == 0) continue;
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isAcceptable()) {
// a connection was accepted by a ServerSocketChannel.
} else if (key.isConnectable()) {
// a connection was established with a remote server.
} else if (key.isReadable()) {
// a channel is ready for reading
} else if (key.isWritable()) {
// a channel is ready for writing
}
keyIterator.remove();
}
}
-------(七) FileChannel--------------
连接到文件的通道
FileChannel无法设置为非阻塞模式,它总是运行在阻塞模式下。
打开FileChannel
InputStream、OutputStream或RandomAccessFile来获取一个FileChannel实例
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
从FileChannel读取数据
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
向FileChannel写数据
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
channel.write(buf);
}
关闭FileChannel
channel.close();
FileChannel的某个特定位置进行数据的读/写操作
position方法
long pos = channel.position();
channel.position(pos +123);
FileChannel的size方法
long fileSize = channel.size();
FileChannel的truncate方法
截取文件,后面的部分将被删除
channel.truncate(1024);
FileChannel的force方法
FileChannel.force()方法将通道里尚未写入磁盘的数据强制写到磁盘上。
channel.force(true);
---------(八) SocketChannel------------
连接到TCP网络套接字的通道
创建SocketChannel
打开一个SocketChannel并连接到互联网上的某台服务器。
一个新连接到达ServerSocketChannel时,会创建一个SocketChannel
打开 SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
关闭 SocketChannel
socketChannel.close();
从 SocketChannel 读取数据
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
分配一个Buffer。从SocketChannel读取到的数据将会放到这个Buffer中
返回的int值表示读了多少字节进Buffer里
写入 SocketChannel
new string - new buf - buf clear - buf put - buf flip - buf remain - channel write
非阻塞模式
设置 SocketChannel 为非阻塞模式(non-blocking mode)
异步模式下调用connect(), read() 和write()了。
connect()
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
while(! socketChannel.finishConnect() ){
//wait, or do something else...
}
非阻塞模式与选择器
非阻塞模式与选择器搭配会工作的更好,通过将一或多个SocketChannel注册到Selector,
可以询问选择器哪个通道已经准备好了读取,写入等
--------(九) ServerSocketChannel---------
ServerSocketChannel 是一个可以监听新进来的TCP连接的通道
例子:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true){
SocketChannel socketChannel =
serverSocketChannel.accept();
//do something with socketChannel...
}
打开 ServerSocketChannel
关闭 ServerSocketChannel
监听新进来的连接
ServerSocketChannel.accept() 方法监听新进来的连接
返回一个包含新进来的连接的 SocketChannel
非阻塞模式
非阻塞模式下,accept() 方法会立刻返回
如果还没有新进来的连接,返回的将是null。
需要检查返回的SocketChannel是否是null
if(socketChannel != null){
//do something with socketChannel...
}
--------(十) Java NIO DatagramChannel-------
DatagramChannel是一个能收发UDP包的通道
UDP是无连接的网络协议,所以不能像其它通道那样读取和写入。它发送和接收的是数据包。
打开 DatagramChannel
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
接收数据
receive()
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);
发送数据
send()方法
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));
连接到特定的地址
channel.connect(new InetSocketAddress("jenkov.com", 80));
当连接后,也可以使用read()和write()方法,就像在用传统的通道一样。
只是在数据传送方面没有任何保证。这里有几个例子:
----------(十一) Pipe--------------
管道是2个线程之间的单向数据连接
Pipe有一个source通道和一个sink通道
数据会被写到sink通道,从source通道读取。
ThreadA - sink - source - ThreadB
创建管道
Pipe pipe = Pipe.open();
向管道写数据
Pipe.SinkChannel sinkChannel = pipe.sink();
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
sinkChannel.write(buf);
}
从管道读取数据
Pipe.SourceChannel sourceChannel = pipe.source();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);
------------(十二) Java NIO与IO-----------
IO NIO
面向流 面向缓冲
阻塞IO 非阻塞IO
无 选择器
面向流与面向缓冲