加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

Java NIO

发布时间:2020-12-15 06:53:03 所属栏目:Java 来源:网络整理
导读:1. 概述 Java NIO (New IO) 由以下三个核心组件组成: Channels (通道) Buffers (缓冲区) Selectors (选择器) 通常,在NIO中,IO从一个Channel开始。数据可以从Channel中读到Buffer,也可以从Buffer中写道Channel。而Selector允许单个线程处理多个Chan

1. 概述

Java NIO (New IO) 由以下三个核心组件组成:

  • Channels (通道)
  • Buffers (缓冲区)
  • Selectors (选择器)

通常,在NIO中,IO从一个Channel开始。数据可以从Channel中读到Buffer,也可以从Buffer中写道Channel。而Selector允许单个线程处理多个Channel。

2. Channel

Channels和Streams很像,但还是有一些不同的:

  • 在一个Channels上既可以读又可以写,而Streams只能读或写;
  • Channels可以异步读写;
  • Channels总是从Buffer中读,或写到Buffer中;

如上所述,数据从通道读取到缓冲区中,从缓冲区写入通道中,如下图所示:

Channel最重要的四种实现:

  • FileChannel : 从文件中读数据
  • DatagramChannel : 可以通过UDP在网络上读写数据
  • SocketChannel : 可以通过TCP在网络上读写数据
  • ServerSocketChannel : 监听TCP连接

3. Buffer

在Java NIO中Buffer用来和Channel交互,数据从channel读到buffer中,从buffer写到channel中。

Buffer本质上是一个内存块,可以在其中写入数据,然后在以后再次读取。该内存块包装在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用该内存块。

使用Buffer读写数据,典型地分为四步:

  1. 写数据到Buffer
  2. 调用buffer.flip()
  3. 从Buffer中读数据
  4. 调用 buffer.clear() 或者 buffer.compact()

当你向一个buffer中写数据时,buffer会跟踪你已经写了多少数据了。一旦你需要读取数据,你需要调用flip()方法将buffer从写入模式切换为读取模式。在读取模式下,buffer使你可以读取写入缓冲区的所有数据。

一旦你已经读取了所有数据,你需要清除buffer,以使得它可以再次被写入数据。有两个方法可以达到这个效果:clear()或者compact()。clear()方法会清理整个buffer,compact()方法只清理你已经读过的数据。任何未读的数据都将移至缓冲区的开头,并且将来写入buffer的数据在现在未读的数据之后。

 1 RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
 2 FileChannel inChannel = aFile.getChannel();
 3 
 4 // create buffer with capacity of 48 bytes
 5 ByteBuffer buf ByteBufferallocate(48 6 
 7 int bytesRead inChannelread(buf); //read into buffer.
 8 while != -1) {
 9 
10   flip();  //make buffer ready for read
11 
12   whilehasRemaining()){
13       Systemoutprint((char) get()); // read 1 byte at a time
14   }
15 
16   clear(); //make buffer ready for writing
17   18 19 close();

Buffer有三个属性:

  • capacity
  • position
  • limit?

position和limit的含义取决于Buffer是处于读模式还是写模式,无论哪种模式下capacity的含义总是不变的。

Capacity?

作为一个内存块,Buffer有一个固定大小,也被称作“capacity”。一旦Buffer满了,就需要清空它(读取数据或清除数据),然后才能将更多数据写入其中。

Position?

当你将数据写入Buffer时,你需要在一个明确的位置写入。初始位置是0,当有数据被写入后,position会向前移动以指向下一个可写入的位置。position的最大值是capacity-1。

当你从Buffer中读取数据时,也需要从一个给定的位置处开始读取。当你将Buffer从写模式切换为读模式时,position会被重置为0。

Limit

在写模式下,limit表示你可以写多少数据到Buffer。在写模式下,limit的值等于capacity。

在读模式下,limit表示你可以从Buffer中读多少数据。因此,当从写模式切换为读模式时,limit被设置为在写模式是的position。换言之,写了多少就能读多少。

3.1. 分配一个缓冲区

为了获得一个Buffer,首先必须先给它分配空间。每种类型的Buffer都有一个allocate()方法来做这件事。?

1 // 分配字节缓冲区,容量为48字节 2 3 // 分配字符缓冲区,容量为1024个字符 4 CharBuffer CharBuffer1024);

3.2. 写数据到Buffer

有两种方式向Buffer中写数据:

  • 从Channel中向Buffer写数据
  • 从Buffer本身向自己写数据,通过put()方法?
//read into buffer 2 ); 3 put127);

flip()

flip()方法将Buffer从写模式切换为读模式,调用flip()将设置position为0,limit不变还是在刚才的位置。?

3.3. 从Buffer中读数据

有两种方式从Buffer中读数据:

  • 从Buffer中读数据到Channel
  • 从Buffer自身读取,通过get()方法?
//read from buffer into channel bytesWritten write4 byte aByte ();

rewind()

Buffer.rewind()设置position为0,以至于你可以从头再读一遍Buffer中的所有数据。

clear()

clear()方法将position置为0,并且limit与capacity相等。换句话说,Buffer被清除了。其实,Buffer上的数据并没有被真正清除,只是告诉你你可以将数据写到哪里。

compact()

compact()方法将所有未读的数据复制到Buffer的开头,然后它将position设置在最后一个未读元素的右侧,limit仍然等于capacity。现在,Buffer可以写了,只不过你不能覆盖之前那些未读的数据。?

mark() 和 reset()?

通过调用Buffer.mark()你可以标记一个给定的位置。你可以在随后调用Buffer.reset()返回到刚才标记的位置那里。

?

4. Selector

Selector是一个组件,它可以检查一个或多个Channel实例,并决定哪些Channel已经准备好读或写。通过这种方式,一个线程可以管理多个通道,从而实现管理多个网络连接(PS:Selelctor可以确定哪些Channel可读或可写,这样只需要一个线程就能管理多个网络连接)

4.1. 为什么要用Selector

使用单个线程来处理多个通道的优点是,处理通道所需的线程更少。事实上,你可以使用一个线程来处理所有的通道。对于操作系统来说,线程之间的切换非常昂贵,而且每个线程也会占用操作系统中的一些资源(内存)。因此,使用的线程越少越好。(PS:但是请记住,现代操作系统和CPU在多任务处理方面变得越来越好,因此,随着时间的推移,多线程的开销会越来越小。)

4.2. 创建Selector

// 创建一个Selector Selector selector Selectoropen4 // 注册Channel到Selector 5 channelconfigureBlocking(false6 SelectionKey key registerselectorSelectionKeyOP_READ);

Channel必须是非阻塞模式才能和Selector一起使用。这就意味着FileChannel不能和Selector一起用,因为FileChannel不能切换成非阻塞模式。

register()方法的第二个参数表示你希望通过Selector在Channel中监听的事件。有四种不同的事件可以被监听:

  1. Connect
  2. Accept
  3. Read
  4. Write

这四种事件用SelectionKey的四个常量来表示:

  1. SelectionKey.OP_CONNECT?
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

如果你对多个事件都感兴趣,可以这样写:

1 interestSet OP_READ | OP_WRITE;

4.3. 通过Selector选择Channel

在调用任意一个select()方法以后,会返回你感兴趣的并且相应事件已经准备好的channel给你。简单地来说就是,如果你对已经为读取做好准备的channel感兴趣,那么你将从select()方法中接收到这样的channel。

  • select() : 阻塞,直到至少有一个你注册的事件准备好的channel
  • select(long timeout) : 跟select()很像,多了一个超时时间
  • selectNow() : 不阻塞,无论有没有已经准备好的channel都立刻返回

select()方法的返回值是一个int值,表示有多少个准备好的channel。也就是说,在上一次调用select()以后有多少个channel变成已准备好。

完整的示例:

2 3 4 true 8 9 readyChannels selectNow10 11 if == 0) continue; 12 13 14 Set<> selectedKeys selectedKeys16 IteratorkeyIterator iterator17 18 keyIteratorhasNext()) 19 20 next21 22 keyisAcceptable23 // a connection was accepted by a ServerSocketChannel. 24 25 } else isConnectable26 // a connection was established with a remote server. 27 28 isReadable29 // a channel is ready for reading 30 31 isWritable32 // a channel is ready for writing 33 34 35 remove36 37 }

5. SocketChannel

Java NIO SocketChannel是连接到TCP网络Socket的通道。

创建SocketChannel有两种方式:

  • 打开一个SocketChannel并连接到服务器上
  • 当一个输入连接到达ServerSocketChannel时,也会创建一个SocketChannel?
// Opening a SocketChannel SocketChannel socketChannel SocketChannel3 socketChannelconnectInetSocketAddress"localhost"9000)); 4 5 // Closing a SocketChannel ();?

5.1. 从SocketChannel中读取

1 );

SocketChannel.read()方法将数据从SocketChannel读到Buffer中,其返回值表示有多少字节被写道Buffer中。如果返回-1,则表示到达流的末尾。

5.2. 写数据到SocketChannel

String newData = "hahaha" 4 newDatagetBytes()); 7 9 10 11 }

注意,SocketChannel.write()是放在while循环体中的。由于无法保证write()方法将多少字节写入SocketChannel,因此,要重复调用write()方法,直到缓冲区没有字节可写为止。

5.3. 非阻塞模式

当一个SocketChannel被设置为非阻塞模式时,你就可以异步地调用connect(),read(),write()方法了。

connect()?

如果SocketChannel是非阻塞模式,那么当你调用connect()方法时,该方法可能在建立连接之前返回。为了确定连接是否已经成功建立,可以调用finishConnect()方法。

(! finishConnect() ){ 5 //wait,or do something else... 6 }

write() 和 read()

在非阻塞模式下,write()方法可能会在未写入任何内容的情况下返回,因此需要在循环中调用write()。同样的,在非阻塞模式下,read()方法可能在没有读取任何数据的情况下就返回了,因此,需要注意返回的int,它告诉我们读取了多少字节。?

6. ServerSocketChannel

Java NIO ServerSocketChannel是一个可以监听输入TCP连接的通道,就像标准Java网络中的ServerSocket一样。?

ServerSocketChannel serverSocketChannel ServerSocketChannel2 serverSocketChannelsocket().bind99995 6 accept7 //do something with socketChannel... 8 }

6.1. Listening for Incoming Connections

通过调用ServerSocketChannel.accept()方法可以监听输入的连接。当accept()方法有返回的时候,它返回一个带有输入连接的SocketChannel。因此,accept()会阻塞直到有输入连接到来为止。通常的做法是这样的:

1 2 3 4 6.2. 非阻塞模式

ServerSocketChannel可以设置为非阻塞模式。在非阻塞模式下,调用accept()方法会立即返回,因此如果没有输入连接到达,它返回的可能是null。因此,必须检查返回的SocketChannel是否为null,下面是一个例子:

4 ); 5 6 7 9 if!= null){ 10 11 } 12 7. Java NIO vs. IO

?

7.1. Stream Oriented vs. Buffer Oriented

Java NIO和IO之间的第一个大区别是IO是面向流的,而NIO是面向缓冲区的。 那是什么意思呢?

面向流的Java IO意味着你一次从流中读取一个或多个字节。如何处理读取的字节由你自己决定。它们不会被缓存到任何地方。此外,你不能在流中的数据中来回移动。如果需要来回移动从流中读取的数据,需要首先将其缓存到缓冲区中。

Java NIO的面向缓冲区的方法略有不同。数据被读入缓冲区,以后再从缓冲区中进行处理。你可以根据需要在缓冲区中来回移动。这使得在处理过程中更具灵活性。但是,你还需要检查缓冲区是否包含你需要的所有数据,以便对其进行完全处理。并且,你需要确保在将更多数据读入缓冲区时,不会覆盖缓冲区中尚未处理的数据。

7.2. Blocking vs. Non-blocking IO

Java IO的各种流被阻塞。这意味着,当线程调用read()或write()时,该线程将被阻塞,直到有一些数据需要读取,或者数据被完全写入。 在此期间,线程无法执行其他任何操作。

Java NIO的非阻塞模式允许线程请求从通道读取数据,并且只获取当前可用的数据,如果当前没有可用的数据,则什么也得不到。在数据可以读取之前,线程不会一直处于阻塞状态,而是可以继续执行其他操作。

非阻塞写入也是如此。线程可以请求将某些数据写入通道,但不等待将其完全写入。然后线程可以继续运行,同时执行其他操作。

当线程在IO调用中没有被阻塞时,它们的空闲时间通常在其他通道上执行IO。也就是说,单个线程现在可以管理输入和输出的多个通道。

7.3. Selectors

Java NIO的Selector允许单个线程监视多个输入通道。可以使用Selector注册多个通道,然后使用一个线程“select”具有可用于处理输入的通道,或者选择准备好进行写入的通道。这种选择器机制使单个线程可以轻松管理多个通道。

7.4. 不同的数据读取方式

Java IO: Reading data from a blocking stream?

Java NIO: Reading data from a channel until all needed data is in buffer

8. 示例

ChatServer.java

1 package comcjs 3 import java.io.IOException 4 java.net.InetSocketAddress 5 java.nio.ByteBuffer 6 java.nio.channels.SelectionKey 7 java.nio.channels.Selector 8 java.nio.channels.ServerSocketChannel 9 java.nio.channels.SocketChannel10 java.nio.charset.Charsetjava.util.Iteratorjava.util.Set14 public class ChatServer 16 private 17 18 19 readBuffer 20 writeBuffer 22 private static final Charset CHARSET CharsetforName"UTF-8"23 24 public ChatServerport25 try 26 27 28 29 30 31 OP_ACCEPT32 catch IOException e34 printStackTrace35 37 38 /** 39 * 监听客户端连接 40 */ 41 void listen() throws IOException 42 println"服务器启动成功"43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 CHARSETencode"来自服务器的问候: 你好!"65 66 67 68 69 = 70 msg ""71 readBuffer> 72 73 += decode).toString74 75 msg76 77 78 // 给客户端回复消息 79 writeBuffer"服务器对你说: 收到"80 81 82 83 84 85 86 87 88 89 90 91 92 93 mainString[] args94 ChatServer chatServer 95 chatServer96 97 }

ChatClient.java

1 2 3 4 5 6 7 8 9 10 11 java.util.Scanner 12 13 ChatClient 14 15 16 17 18 19 20 21 22 ChatClienthost 23 24 25 26 27 28 (! 29 "正在等待连接" 30 31 32 "连接成功" 33 34 35 36 37 new ThreadHandler)).start 38 39 40 41 42 43 44 45 * 给服务器发消息 46 47 48 Scanner scanner Scannerin 49 scanner 50 line nextLine 51 null != && !equalslinetrim())) 52 53 54 55 56 57 58 * 接收来自服务器的消息 59 60 Handler implements Runnable 61 62 63 64 65 this 66 67 68 @Override 69 run 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 // 从写模式切换为读模式 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 ChatClient chatClient "127.0.0.1"106 chatClient107 108 }?

控制台

9. 参考

http://tutorials.jenkov.com/java-nio/index.html

http://tutorials.jenkov.com/java-nio/socketchannel.html?

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读