新io:nio的学习
OIO是面向流(Stream Oriented)的,NIO是面向缓冲区(Buffer Oriented)的。
与 Java IO 不同,Java NIO 是一个面向缓冲区的包。这意味着数据是从通道读取到缓冲区中,然后进一步处理。例如,一个线程请求通道将数据读取到缓冲区中,而在通道同时将数据读取到缓冲区的过程中,线程可以去做其他工作。一旦数据被读取到缓冲区,线程就可以继续处理在读取操作期间留下的工作。因此,NIO 是一种双向数据传输。
OIO的操作是阻塞的,而NIO的操作是非阻塞的。
与 Java IO 不同,Java NIO 是一种非阻塞 IO。这意味着如果一个线程正在调用 read() 或 write() 操作,该线程不会被阻塞,直到有数据可读或数据完全写入,而是可以继续执行其他任务。这就是它被称为异步 IO 或非阻塞 IO 的原因。
基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点像流。
数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。
Channel的实现
这些是Java NIO中最重要的通道的实现:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
FileChannel 从文件中读写数据。
DatagramChannel 能通过UDP读写网络中的数据。
SocketChannel 能通过TCP读写网络中的数据。
ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
|
|
从file对象获取filechannel通道对象,FileChannel是javanio中用于文件读写操作的通道,提供比传统文件流读写更高效灵活
buffer的实现
以下是Java NIO里关键的Buffer实现:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。
Buffer的分配
要想获得一个Buffer对象首先要进行分配。 每一个Buffer类都有一个allocate方法。下面是一个分配48字节capacity的ByteBuffer的例子。
|
|
这是分配一个可存储1024个字符的CharBuffer:
|
|
NIO使用示例
基本的 Channel 示例
下面是一个使用FileChannel读取数据到Buffer中的示例:
|
|
注意 buf.flip() 的调用,首先读取数据到Buffer,然后反转Buffer,接着再从Buffer中读取数据。
- 初始化:创建
RandomAccessFile
对象用于读写文件,然后通过该对象获取FileChannel
。- 分配缓冲区:创建一个指定大小的
ByteBuffer
用于存储从文件中读取的数据。- 读取数据:从
FileChannel
中读取数据到ByteBuffer
中,并检查是否读取到文件末尾。- 处理数据:如果读取到数据,将
ByteBuffer
翻转,将其中的数据输出,然后清空ByteBuffer
准备下一次读取。- 关闭文件:当读取完所有数据后,关闭
RandomAccessFile
。
flip()
用 flip() 方法将 Buffer buf从写入模式切换到读取模式。调用 flip() 将 position 设置回0,并将 limit 置为刚才的位置。
换句话说,position 现在标记了读取位置,limit 标记了写入缓冲区的字节、字符数等——可以读取的字节数、字符数等的限制。
Nio实现高效Socket通信,以下是实现逻辑和步骤:
- Channel(通道)
ServerSocketChannel
:服务端监听TCP连接SocketChannel
:客户端与服务端的数据传输通道- 支持非阻塞模式(
configureBlocking(false)
)
- Buffer(缓冲区)
- 数据读写通过
ByteBuffer
完成,需调用flip()
切换读写模式 - 常用方法:
put()
、get()
、clear()
、compact()
- 数据读写通过
- Selector(选择器)
- 单线程管理多个通道的事件(如连接、读、写)
- 监听事件类型:
OP_ACCEPT
、OP_READ
、OP_WRITE
服务器端
初始化服务器
注册到Selector
事件循环处理
|
|
客户端
连接服务器
发送和接受数据
关键注意事项
- 资源管理
- 需手动关闭通道和选择器(
close()
) - 避免内存泄漏:及时释放不再使用的Buffer
- 需手动关闭通道和选择器(
- 粘包与拆包问题
- 需自定义协议(如消息头声明长度)
- 使用
ByteBuffer
的compact()
合并不完整数据
- 性能优化
- 使用直接缓冲区(
ByteBuffer.allocateDirect()
)减少内存拷贝 - 避免在事件循环中执行阻塞操作
- 使用直接缓冲区(
- NIO与BIO对比
- 优势:单线程处理高并发连接,减少线程上下文切换
- 劣势:编程复杂度高,需处理异步逻辑和边界条件
适用场景
- 高并发短连接(如即时通讯、实时推送)
- 需要低延迟和大吞吐量的网络服务(如游戏服务器)
通过合理利用NIO的非阻塞特性,可在单线程中高效管理数千个并发连接,但需注意代码健壮性和异常处理。