缓冲区基础
缓冲区是包在一个对象内的基本数据元素数组。Buffer类相比一个简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。Buffer类以及它专有的子类定义了一个用于处理数据缓冲区的API。
属性:
容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
上界(Limit):缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。
位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的get()和put()函数更新。
标记(Mark):一个备忘位置。调用mark()来设定mark=position。调用reset()设定position=mark。标记在设定前是未定义的。
字节缓冲区
字节是操作系统及其I/O设备使用的基本数据类型。当JVM和操作系统间传递数据时,将其它的数据类型拆分成构成它们的字节是十分必要的。
非字节类型的基本类型,除了布尔型都是由组合在一起的几个字节组成的。这些数据类型及其大小为:
Byte:1,Char:2,Short:2,Int:4,Long:8,Float:4,Double:8
每个基本数据类型都是以连续字节序列的形式存储在内存中。
直接缓冲区
字节缓冲区跟其他缓冲区类型最明显的不同在于,它们可以成为通道所执行的I/O的源头或目标,通道只接收ByteBuffer作为参数。
直接缓冲区被用于与通道和固有I/O例程交互。它们通过使用固有代码来告知操作系统直接释放或填充内存区域,对由于通道直接或原始存取的内存区域中的字节元素的存储尽了最大的努力。直接字节缓冲区通常是I/O操作最好的选择。
通道
通道是访问I/O服务的导管。有两种类型的通道:文件(file)通道和套接字(socket)通道。对应的有一个FileChannel类和三个socket通道类:SocketChannel、ServerSocketChannel和DatagramChannel。
通道可以以多种方式创建。Socket通道有可以直接创建新socket通道的工厂方法。但是一个FileChannel对象却只能通过在一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel()方法来获取。
通道将数据传输给ByteBuffer对象或者从ByteBuffer对象获取数据进行传输。
通道可以是单向或者双向的。一个channel类可能实现定义read()方法的ReadableByteChannel接口,而另一个channel类也许实现WritableByteChannel接口以提供write()方法。实现这两种接口其中之一的类都是单向的,只能在一个方向上传输数据。如果一个类同时实现这两个接口,那么它是双向的,可以双向传输数据。
通道会连接一个特点I/O服务且通道实例的性能受它所连接的I/O服务的特征限制。一个连接到只读文件的Channel实例不能进行写操作,即使该实例所属的类可能有write()方法。
通道可以以阻塞(blocking)或非阻塞(noblocking)模式运行。
Scatter/Gather
它是指在多个缓冲区上实现一个简单的I/O操作。对于一个write操作而言,数据是从几个缓冲区按顺序抽取(gather)并沿着通道发送的。对于read操作而言,从通道读取的数据会按顺序被散布(scatter)到多个缓冲区,将每个缓冲区填满直至通道中的数据或者缓冲区的最大空间被消耗完。
文件通道
文件通道总是阻塞式的,因此不能被置于非阻塞模式。对于文件I/O,最强大之处在于异步I/O,它允许一个进程可以从操作系统请求一个或多个I/O操作而不必等待这些操作的完成。发起请求的进程之后会收到它请求的I/O操作已完成的通知。
一个FileChannel实例只能通过在一个打开的file对象(RandomAcessFile、FileInputStream、FileOutputStream)上调用getChannel()方法获取。该方法会返回一个连接到相同文件的FileChannel对象且该FileChannel对象具有与file对象相同的访问权限,然后就可以使用该通道对象来利用强大的FileChannel API
Socket通道
全部socket通道类包括DatagramChannel、SocketChannel和ServerSocketChannel。
DatagramChannel和SocketChannel实现定义读和写功能的接口而ServerSocketChannel不实现。ServerSocketChannel负责监听传入的连接和创新新的SocketChannel对象,它本身从不传输数据。
socket和socket通道之间的关系:
通道是一个连接I/O服务导管并提供交互的方法。就某个socket而言,它不会再次实现与之对应的socket通道类中的socket协议API,而java.net中已经存在的socket通道都可以被大多数协议操作重复使用。全部socket通道类在被实例化时都会创建一个对等socket对象。相对应的为DatagramSocket、Socket、ServerSocket,它们已经被更新以识别通道。对等socket可以通过socket()方法从一个通道上获取。每个socket通道都有一个关联的socket对象,但并非所有的socket都有一个关联的通道。
非阻塞模式
Socket通道可以在非阻塞模式下运行。要把一个socket通道置于非阻塞模式,要依靠所有socket通道类的公有超级类:SelectableChannel。
public abstract class SelectableChannel
extends AbstractInterruptibleChannel
implements Channel
{
protected SelectableChannel() { }
public abstract SelectorProvider provider();
public abstract int validOps();
public abstract SelectionKey keyFor(Selector sel);
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
public final SelectionKey register(Selector sel, int ops)
throws ClosedChannelException
{
return register(sel, ops, null);
}
public abstract SelectableChannel configureBlocking(boolean block)
throws IOException;
public abstract boolean isBlocking();
public abstract Object blockingLock();
}
设置或重新设置一个通道的阻塞模式是很简单的,只要调用configureBlocking()方法即可,传入参数值为true为阻塞模式,参数值为false为非阻塞模式。
ServerSocketChannel
ServerSocketChannel是一个基于通道的socket监听器。它同java.net.ServerSocket执行相同的基本任务,不过它增加了通道语义,因此能够在非阻塞模式下运行。
用静态的open()工厂方法创建一个新的ServerSocketChannel对象,将会返回同一个未绑定的java.net.ServerSocket关联的通道。该对等ServerSocket可以通过在返回的ServerSocketChannel调用socket()方法来获取。作为ServerSocketChannel的对等体被创建的ServerSocket对象依赖通道实现。由于ServerSocketChannel没有bind()方法,因此有必要取出对等的socket并使用它来绑定到一个端口以开始监听连接, serverSocket.bind(new InetSocketAddress(port))。
同它的对等体java.net.ServerSocket一样,ServerSocketChannel也有accept()方法。一旦您创建了一个ServerSocketChannel并用对等socket绑定了它,然后就可以在其中一个上调用accpet()。在ServerSocket上调用accept(),会同任何其他的ServerSocket表现一样的行为;总是阻塞并返回一个Socket对象。如果在ServerSocketChannel上调用accept()方法则会返回SocketChannel类型的对象,返回的对象能够在非阻塞模式下运行。
选择器
选择器(Selector):管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起呗注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以讲激发的线程挂起,直到有就绪的通道。
可选择通道(SelectableChannel):这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。FileChannel对象不是可选择的,因为它们没有继承SelectableChannel.所有socket通道都是可选择的,包括从管道(Pipe)对象中获得的通道。SelectableChannel可以被注册到Selector对象上,同时可以指定对那个选择器而言,那种操作是感兴趣的。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。
选择键(SelectinKey):选择键封装了特定的通道与特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回并提供一个表示这个注册关系的标记。选择键包含两个比特集,指示了该注册关系所关心的通道操作,以及通道以及准备好的操作。
Selector对象是通过调用静态工厂方法open()来实例化的。选择器不是像通道或流那样的基本I/O对象:数据从了没有通过它们进行传递。
register()方法位于SelectableChannel类,尽管通道实际上是被注册到选择器上的。register()方法接受一个Selector对象作为第一个参数,第二个参数表示所关心的通道操作。这是一个选择器在检查通道就绪状态时需要关心的操作的比特掩码。特定的操作比特值在SelectionKey类中被定义为pubic static字段。可选择的操作有以下四种:读(read)、写(write)、连接(connect)和接受(accept)。
实际应用
- ServerSocketChannel: ServerSocket 的替代类,支持阻塞通信与非阻塞通信.
- SocketChannel: Socket 的替代类, 支持阻塞通信与非阻塞通信.
- Selector: 为ServerSocketChannel 监控接收客户端连接就绪事件, 为 SocketChannel监控连接服务器就绪,读就绪和写就绪事件.
- SelectionKey: 代表 ServerSocketChannel 及 SocketChannel 向Selector 注册事件的句柄. 当一个 SelectionKey 对象位于Selector 对象的 selected-keys集合中时, 就表示与这个
SelectionKey 对象相关的事件发生了.在SelectionKey类中有几个静态常量 - SelectionKey.OP_ACCEPT
->客户端连接就绪事件 等于监听serversocket.accept()返回一个socket - SelectionKey.OP_CONNECT
->准备连接服务器就绪 跟上面类似,只不过是对于socket的相当于监听了 socket.connect() - SelectionKey.OP_READ
->读就绪事件, 表示输入流中已经有了可读数据, 可以执行读操作了 - SelectionKey.OP_WRITE
->写就绪事件