Java IO的分类
字节流读取单个字节,字符流读取单个字符。字节流用来处理二进制文件,字符流用来处理文本文件。简而言之,字节是个计算机看的,字符才是给人看的。
Java NIO
NIO简介
NIO(Non-blocking I/O,在Java领域,也称为New I/O),是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。
NIO与IO的区别
- IO是面向流的,NIO是面向缓冲的
- IO是阻塞的,NIO是不阻塞的
- NIO有选择器,IO没有
读写数据方式
- 从通道进行数据读取 :创建一个缓冲区,然后请求通道读取数据。
- 从通道进行数据写入 :创建一个缓冲区,填充数据,并要求通道写入数据。
核心组件介绍
- Channel
- Buffer
- Selector
NIO之Channel
Channel介绍
通道(channel)是一种用于磁盘文件的抽象,它使我们可以访问诸如内存映射、文件加锁机制以及文件间快速数据传递等操作系统特性。
FileChannel的使用
1
2
3FileChannel channel = FileChannel.open(path, options);
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0,
length);通过FileChannel的map方法可以获得一个ByteBuffer,支持三种映射模式:
- READ_ONLY: 产生的缓冲区只读
- READ_WRITE: 产生的缓冲区可写,且对缓冲的修改会写回文件中
- PRIVATE: 缓冲区可写,对缓冲区的修改不写回文件中
有了缓冲区就可以通过缓冲区进行读写操作了。
SocketChannel 和 ServerSocketChannel 的使用
对于 SocketChannel 如果直接在默认模式(阻塞模式)下使用 connect() 方法直接连接,和使用 Socket 对象直接连接一样会造成阻塞;如果在非阻塞模式下使用 connect() 方法连接时采用并发连接,它发起对请求地址的连接并且立即返回值。如果返回值是true,说明连接立即建立了(这可能是本地环回连接);如果连接不能立即建立,connect( )方法会返回false且并发地继续连接建立过程。
而在服务端的 ServerSocketChannel 对象负责监听服务器上的端口,使用之前需要通过 ServerSocketChannel.socket() 方法获取 ServerSocket 对象并绑定端口。在传统的基于流的Socket网络编程中,服务端为每一个请求创建一个线程用于读写数据,而使用ServerSocketChannel在服务端编程,我们往往配合Selector选择器使用,在服务端我们将特定的Accpet、Read、Write事件注册到选择器上,由选择器帮我检查操作系统内核是否可读和可写,如有对应的事件满足要求,选择器会通知调用者线程。
相对于传统Socket编程,使用基于缓冲区的NIO编程在如下几点不会阻塞调用者线程:
客户端connect( )方法不会阻塞
服务端accept()方法不会阻塞
Socket的读read()方法不会阻塞
NIO之Buffer
- 缓冲区介绍
- Java NIO Buffers用于和NIO Channel交互。 我们从Channel中读取数据到buffers里,从Buffer把数据写入到Channels;
- Buffer本质上就是一块内存区;
- 一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit界限。
- Buffer常见的方法
- clear():将位置复位到0,并将界限设置到容量,为写出做好准备;
- flip():将界限设置到位置,并将位置复位到0,为读入做好准备;
- rewind():将读写位置复位到0,并保持界限不变,使这个缓冲区为重新读入相同的值做好准备;
- position():返回这个缓冲区的位置
Buffer的使用方式
分配缓冲区:
1
ByteBuffer buf = ByteBuffer.allocate(28);
写入数据:
1
2
3
4
5//从Channel中写入数据
int bytesRead = inChannel.read(buf);
//通过put()写入数据
buf.put(dataArray);
NIO之Selector
选择器介绍
Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。
使用Selector的好处在于: 使用更少的线程来就可以来处理通道了,相比使用多个线程,避免了线程上下文切换带来的开销。
Selector的使用方法
Selector的创建
1
Selector selector = Selector.open();
注册Channel到Selector
1
2channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);与 Selector 一起使用时,channel 必须处于非阻塞模式下。
register()方法的第二个参数是一个“interest集合”,对应可以监听的四种事件:
- Connect
- Accept
- Read
- Write
这四种事件用SelectionKey的四个常量来表示:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
如果需要监听多个事件可以用 | 组合。
SelectionKey介绍
当向 Selector 注册 Channel 之后返回一个SelectionKey对象,这个对象包含:
- interest集合:同上
- ready集合:通道已经准备就绪的操作的集合。
- Selector
- Channel
通过Selector选择通道
调用 select() 方法会返回已经就绪的通道个数,通过 key.channel()可以返回通道。wakeUp()
某个线程调用select()方法后阻塞了,即使没有通道已经就绪,也有办法让其从select()方法返回。只要让其它线程在第一个线程调用select()方法的那个对象上调用Selector.wakeup()方法即可。阻塞在select()方法上的线程会立马返回。- close()
用完Selector后调用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效。通道本身并不会关闭。