JDK1.4的java.nio.*包中引入了新的Java-IO类库,目的是提高速度,实际上,旧的IO包也已经使用nio重新实现过,以便充分利用这种速度提高。速度的提高在文件IO和网络IO。速度的提高来自于所使用的结构更接近于操作系统执行IO的方式:通道和缓冲器。旧IO中有三个类被修改了,分别是FileInputStream、FileOutputStream和RandomAccessFile,用于产生FileChannel。而Reader和Writer这种字符模式类不能用于产生通道,但是java.nio.channels.Channels类提供了实用方法,用以在通道中产生Reader和Writer。


NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如连接打开、数据到达
),因此单个线程可以监听多个数据通道。


NIO和IO最大的区别就是IO是面向流的,NIO面向缓冲区的。IO的各种流是阻塞的,这意味着,当一个线程调用read和writer方法时,该线程将被阻塞,直到有一些数据被读取,或者数据完全被写入,该线程在此期间不能再干任何事。NIO是非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用数据,如果目前没有数据可用,就什么都不会获取,而不是保持线程阻塞,所以在变得数据可以获取之前,线程可以先继续做其他事情,非阻塞写也是如此,一个线程请求写入一些数据到某通道,但不需要等待它完全写完,这个线程同时也可以去做其他事情,线程通常将非阻塞IO的空闲时间用于其他通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。IO是单线程的,而NIO是用选择器来模拟多线程的。

Channel的实现:

1.FileChannel:从文件中读写数据。

2.DatagramChannel:能通过UDP读写网络中的数据。

3.SocketChannel:能通过TCP读写网络中的数据。

4.ServerSocketChannel:可以监听新近来的TCP协议,像Web服务器一样。对每个新进来的连接都会创建一个SocketChannel。

示例:
public static void main(String[] args) throws IOException { File file = new
File("data.txt"); FileOutputStream outputStream = new FileOutputStream(file);
FileChannel channel = outputStream.getChannel(); ByteBuffer buffer =
ByteBuffer.allocate(1024); String string = "java nio";
buffer.put(string.getBytes()); buffer.flip(); //此处必须要调用buffer的flip方法
channel.write(buffer); channel.close(); outputStream.close(); }
e.WriteLine("具体装饰对象A的操作"); } }
Buffer:


buffer的实现包括ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer、MapperByteBuffer。

Buffer类有几个重要的属性:


1.capacity:作为一个内存快,其就代表了当前Buffer能最多暂存多少数据量,存储的数据类型则是根据上面的Buffer对象类型,一旦Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据。


2.position:代表当前数据读或者写出于哪个位置,读模式:被重置从0开始,最大值可能为capacity-1或者limit-1,写模式:被重置从0开始,最大值limit-1

3.limit:读模式下:最多能往Buffer里读多少数据,写模式下:最多能往Buffer里写多少数据,limit大小跟数量大小和capacity有关

示例:
public class ff { public static void nio() { RandomAccessFile aFile = null;
try { aFile = new RandomAccessFile("src/nio.txt", "rw"); // channel获取数据
FileChannel fileChannel = aFile.getChannel(); // 初始化Buffer,设定Buffer每次可以存储数据量 //
创建的Buffer是1024byte的,如果实际数据本身就小于1024,那么limit就是实际数据大小 ByteBuffer buf =
ByteBuffer.allocate(1024); // channel中的数据写入Buffer int bytesRead =
fileChannel.read(buf); System.out.println(bytesRead); while (bytesRead != -1) {
// Buffer切换为读取模式 buf.flip(); // 读取数据 while (buf.hasRemaining()) {
System.out.print((char) buf.get()); } // 清空Buffer区 buf.compact(); // 继续将数据写入缓存区
bytesRead = fileChannel.read(buf); } } catch (IOException e) {
e.printStackTrace(); } finally { try { if (aFile != null) { aFile.close(); } }
catch (IOException e) { e.printStackTrace(); } } } public static void
main(String[] args) { ff.nio(); } }
Buffer读写数据步骤:

1.写入数据到Buffer(fileChannel.read(buf))

2.调用flip()方法

3.从Buffer中读取数据

4.调用clear()方法或者compact方法

Buffer的方法:

flip():将Buffer写模式切换到读模式,比且将position置为0.

clear():清除整个缓冲区

compact():只会清除已经读过的数据,任何未读的数据都被转移到缓冲区起始处,新写入的数据将放到缓冲区未读数据的后面。

allocate(1024):初始化Buffer,设定的值就是capacity的大小。

rewind():position设回0,所以你可以重读Buffer中的所有数据。limit保持不变,仍然表示能从Buffer中读取多少个元素。


mark()和reset():通过调用Buffer.mark()方法,可以标记Buffer中的一个特定的position。之后可以通过调用Buffer,reset()方法恢复到这个position

equals():两个相等的Buffer,满足相同类型,剩余的元素数量相等,所剩余的元素也都相同。

Selector:

允许单线程处理多个Channel。如果你的应用打开了多个连接(通道),但是每个连接的流量很低,使用Selector就会很方便。

Selector的创建:

    通过调用Selector.open方法创建一个Selector,如下:

Selector selector = Selector.open();
向Selector注册通道:


为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现。如下:
channel.configureBlocking(false); SelectionKey key =
channel.register(selector,Selectionkey.OP_READ);

与Selector一起使用,Channel必须出于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:1.Connect,2.Accept。3.Read。4.Write。通道出发了一个事件意思是该事件已经准备就绪。所以某个channel成功连接到另一个服务器称为“连接就绪”。一个server
socket
channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道就可以说是“读就绪”。等待写数据的通道可以说“写就绪”。上面四种事件对应的SelectionKey的四个常量来表示:1.SelectionKey.OP_CONNECT。2.SelectionKey.OP_ACCEPT。3.SelectionKey.OP_READ。4.SelectionKey.OP_WRITE。还可以用“位或”操作符把多个事件连起来。如下:
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

Selector注册了Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些你感兴趣的属性:interest集合、ready集合、Channel、Selector、附加的对象(可选)

interest集合:是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合,就像这样:
int interestSet = selectionKey.interestOps(); boolean isInterestedInAccept =
(interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT; boolean
isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT; boolean
isInterestedInRead = interestSet & SelectionKey.OP_READ; boolean
isInterestedInWrite = interestSet & SelectionKey.OP_WRITE;
可以看到,用“位与”操作interest集合和给定的SelecctionKey常量,可以确定某个确定的事件是否在interest集合中。

ready集合:是通道已经准备就绪的操作的集合。在一次选择后,你会首先访问这个ready
set。Selection将在下一小节进行解释。可以这样访问ready集合:
int readySet = selectionKey.readyOps();
可以用像检测interest集合那样的方法,来检测channel中什么时间或操作已经就绪:
selectionKey.isAcceptable(); selectionKey.isConnectable();
selectionKey.isReadable(); selectionKey.isWritable();
访问channel和selector:
Channel channel = selectionKey.channel(); Selector selector =
selectionKey.selector();
附加对象:


可以将一个对象或者更多的信息附着在SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加与通道一起使用的Buffer,或者包含聚集数据的某个对象,使用方法如下:
selectionKey.attach(theObject); Object attachedObj =
selectionKey.attachment(); //还可以在用register()方法向Selector注册Channel的时候附加对象
SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

通过Selector选择通道:


一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。

下面是select方法:

select()阻塞到至少有一个通道在你注册的事件上就绪。

select(long timeout)和select()一样,除了最长会阻塞timeout毫秒

selectNow()不会阻塞,不管什么通道就绪都立刻返回。

select返回的int值表示有多少通道就绪,即自从上次调用select方法后有多少通道编程就绪状态。


一旦调用了select方法,并且返回值表明由一个或更多个通道就绪了,然后可以通过调用selector的selectedKey方法,访问“已经选择键集”中的就绪通道:
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()调用。
//Selector不会自己从已选择键集中移除SelectionKey实例。必须在处理完通道时自己移除。
//下次该通道变成就绪时,Selector会再次将其放入已选择键集中。 keyIterator.remove(); }


wakeUp():某个线程调用select方法后阻塞,即使没有通道就绪,也有办法让其select方法返回。只要放其他线程在第一个线程调用select方法的那个对象上调用Selector.wakeUp()方法即可,阻塞在select方法上的线程会立马返回。如果其他线程调用了wakeUp方法,但当前没有线程阻塞在select方法上,下个调研select方法的线程会立即“醒来”。


close():用完Selector后调用其close关闭Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身不会关闭。

以下是一个完整的示例:
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(); } }

友情链接
KaDraw流程图
API参考文档
OK工具箱
云服务器优惠
阿里云优惠券
腾讯云优惠券
华为云优惠券
站点信息
问题反馈
邮箱:[email protected]
QQ群:637538335
关注微信