IO
一.BIO
二.NIO
NIO
Channels通道Buffers缓冲区Selectors选择器
2.1 Channel
2.1.1 通道基础
类别
FileChannel: 无法设置为非阻塞模式,总是运行在阻塞模式下DatagramChannel: 收发UDP数据包的通道SocketChannel: TCP连接客户端通道,可以设置为非阻塞ServerSocketChannel: TCP连接服务端通道,可以设置为非阻塞
特点
- 既可以从通道中读数据到缓冲区,也可以从缓冲区中写数据到通道;通道支持异步的读写
- 支持Buffer的Scatter和Gather操作
2.1.2 文件锁定
注意事项: 锁的对象是文件而不是通道或线程,这意味着文件锁不适用于判优同一台Java虚拟机上的多个线程发起的访问.
如果一个线程在某个文件上获得了一个独占锁,然后第二个线程利用一个单独打开的通道来请求改文件的独占锁,那么第二个线程的请求会被批准.但如果这两个线程运行在不同的Java虚拟机上,那么第二个线程会阻塞,因为所最终是由操作系统或文件系统来判优的并且几乎总是在进程级而非线程级上判优.锁都是与一个文件关联的,而不是与单个的文件句柄或通道关联.
public abstract class FileChannel
extends AbstractInterruptibleChannel
implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel{
// -- Locks --
/**
* Acquires a lock on the given region of this channel's file.
*
* <p> An invocation of this method will block until the region can be
* locked, this channel is closed, or the invoking thread is interrupted,
* whichever comes first.
*
* <p> If this channel is closed by another thread during an invocation of
* this method then an {@link AsynchronousCloseException} will be thrown.
*
* <p> If the invoking thread is interrupted while waiting to acquire the
* lock then its interrupt status will be set and a {@link
* FileLockInterruptionException} will be thrown. If the invoker's
* interrupt status is set when this method is invoked then that exception
* will be thrown immediately; the thread's interrupt status will not be
* changed.
*
* <p> The region specified by the <tt>position</tt> and <tt>size</tt>
* parameters need not be contained within, or even overlap, the actual
* underlying file. Lock regions are fixed in size; if a locked region
* initially contains the end of the file and the file grows beyond the
* region then the new portion of the file will not be covered by the lock.
* If a file is expected to grow in size and a lock on the entire file is
* required then a region starting at zero, and no smaller than the
* expected maximum size of the file, should be locked. The zero-argument
* {@link #lock()} method simply locks a region of size {@link
* Long#MAX_VALUE}.
*
* <p> Some operating systems do not support shared locks, in which case a
* request for a shared lock is automatically converted into a request for
* an exclusive lock. Whether the newly-acquired lock is shared or
* exclusive may be tested by invoking the resulting lock object's {@link
* FileLock#isShared() isShared} method.
*
* <p> File locks are held on behalf of the entire Java virtual machine.
* They are not suitable for controlling access to a file by multiple
* threads within the same virtual machine. </p>
*
* @param position
* The position at which the locked region is to start; must be
* non-negative
*
* @param size
* The size of the locked region; must be non-negative, and the sum
* <tt>position</tt> + <tt>size</tt> must be non-negative
*
* @param shared
* <tt>true</tt> to request a shared lock, in which case this
* channel must be open for reading (and possibly writing);
* <tt>false</tt> to request an exclusive lock, in which case this
* channel must be open for writing (and possibly reading)
*
* @return A lock object representing the newly-acquired lock
*
* @see #lock()
* @see #tryLock()
* @see #tryLock(long,long,boolean)
*/
public abstract FileLock lock(long position, long size, boolean shared)
throws IOException;
/**
* Acquires an exclusive lock on this channel's file.
*/
public final FileLock lock() throws IOException {
return lock(0L, Long.MAX_VALUE, false);
}
/**
* Attempts to acquire a lock on the given region of this channel's file.
*
* <p> This method does not block. An invocation always returns
* immediately, either having acquired a lock on the requested region or
* having failed to do so. If it fails to acquire a lock because an
* overlapping lock is held by another program then it returns
* <tt>null</tt>. If it fails to acquire a lock for any other reason then
* an appropriate exception is thrown.
*
* <p> The region specified by the <tt>position</tt> and <tt>size</tt>
* parameters need not be contained within, or even overlap, the actual
* underlying file. Lock regions are fixed in size; if a locked region
* initially contains the end of the file and the file grows beyond the
* region then the new portion of the file will not be covered by the lock.
* If a file is expected to grow in size and a lock on the entire file is
* required then a region starting at zero, and no smaller than the
* expected maximum size of the file, should be locked. The zero-argument
* {@link #tryLock()} method simply locks a region of size {@link
* Long#MAX_VALUE}.
*
* <p> Some operating systems do not support shared locks, in which case a
* request for a shared lock is automatically converted into a request for
* an exclusive lock. Whether the newly-acquired lock is shared or
* exclusive may be tested by invoking the resulting lock object's {@link
* FileLock#isShared() isShared} method.
*
* <p> File locks are held on behalf of the entire Java virtual machine.
* They are not suitable for controlling access to a file by multiple
* threads within the same virtual machine. </p>
*
* @param position
* The position at which the locked region is to start; must be
* non-negative
*
* @param size
* The size of the locked region; must be non-negative, and the sum
* <tt>position</tt> + <tt>size</tt> must be non-negative
*
* @param shared
* <tt>true</tt> to request a shared lock,
* <tt>false</tt> to request an exclusive lock
*
* @return A lock object representing the newly-acquired lock,
* or <tt>null</tt> if the lock could not be acquired
* because another program holds an overlapping lock
*
* @see #lock()
* @see #lock(long,long,boolean)
* @see #tryLock()
*/
public abstract FileLock tryLock(long position, long size, boolean shared)
throws IOException;
/**
* Attempts to acquire an exclusive lock on this channel's file.
*/
public final FileLock tryLock() throws IOException {
return tryLock(0L, Long.MAX_VALUE, false);
}
}
2.1.3 管道
广义讲, 管道(Pipe)就是一个用来在两个实体之间单向传输数据的导管.Pipe类实现一个管道范例, 不过它所创建的管道是在进程内(Java虚拟机进程内部)而非线程间使用的.
public abstract class Pipe {
/**
* A channel representing the readable end of a {@link Pipe}.
*
* @since 1.4
*/
public static abstract class SourceChannel
extends AbstractSelectableChannel
implements ReadableByteChannel, ScatteringByteChannel
{
/**
* Constructs a new instance of this class.
*
* @param provider
* The selector provider
*/
protected SourceChannel(SelectorProvider provider) {
super(provider);
}
/**
* Returns an operation set identifying this channel's supported
* operations.
*
* <p> Pipe-source channels only support reading, so this method
* returns {@link SelectionKey#OP_READ}. </p>
*
* @return The valid-operation set
*/
public final int validOps() {
return SelectionKey.OP_READ;
}
}
/**
* A channel representing the writable end of a {@link Pipe}.
*
* @since 1.4
*/
public static abstract class SinkChannel
extends AbstractSelectableChannel
implements WritableByteChannel, GatheringByteChannel
{
/**
* Initializes a new instance of this class.
*
* @param provider
* The selector provider
*/
protected SinkChannel(SelectorProvider provider) {
super(provider);
}
/**
* Returns an operation set identifying this channel's supported
* operations.
*
* <p> Pipe-sink channels only support writing, so this method returns
* {@link SelectionKey#OP_WRITE}. </p>
*
* @return The valid-operation set
*/
public final int validOps() {
return SelectionKey.OP_WRITE;
}
}
/**
* Initializes a new instance of this class.
*/
protected Pipe() { }
/**
* Returns this pipe's source channel.
*
* @return This pipe's source channel
*/
public abstract SourceChannel source();
/**
* Returns this pipe's sink channel.
*
* @return This pipe's sink channel
*/
public abstract SinkChannel sink();
/**
* Opens a pipe.
*
* <p> The new pipe is created by invoking the {@link
* java.nio.channels.spi.SelectorProvider#openPipe openPipe} method of the
* system-wide default {@link java.nio.channels.spi.SelectorProvider}
* object. </p>
*
* @return A new pipe
*
* @throws IOException
* If an I/O error occurs
*/
public static Pipe open() throws IOException {
return SelectorProvider.provider().openPipe();
}
}
2.2 Buffer
概念上, 缓冲区是报在一个对象内的基本数据元素数组. 一个内存区域或者byte数组, 数据需要包装成Buffer的形式才能和Channel交互(写入或者读取)
2.2.1 缓冲区的属性
capacity: 缓冲区能够容纳的数据元素的最大数量,分配之后不会变化limit: 表示还有多少数据需要读取的下标或者还有多少数据需要写入的下标position: 表示下一个要放置字节的位置(缓冲区就是数组),位置会自动由相应的get()和put()函数更新Mark: 标记,一个备忘位置,调用mark()来设定mark=position. 调用reset()设定position=mark.标记在设定前是未定义的(undefined)
四个属性直接总是遵循以下关系: 0 <= mark <= position <= limit <= capacity
2.2.2 Buffer类的方法签名
public abstract class Buffer {
public final int capacity()
public final int position()
public final Buffer position(int newPosition)
public final int limit()
public final Buffer limit(int newLimit)
public final Buffer mark()
public final Buffer reset()
public final Buffer clear()
public final Buffer flip()
public final Buffer rewind()
public final int remaining()
public final boolean hasRemaining()
public abstract boolean isReadOnly();
// @since 1.6
public abstract boolean hasArray();
public abstract Object array();
public abstract int arrayOffset();
public abstract boolean isDirect();
}
注意: 像clear()这类函数, 通常应返回void,而不是Buffer引用. 这些函数将引用返回到他们在(this)上被引用的对象,这个一个允许级联调用的类设计方法.
缓冲区方法
flip(): 将position置为0,limit置为原position位置,可以读取limit之前的所有数据,为了读出数据做准备clear(): 将position置为0,limit置为capacity位置,清空缓冲区,以便读取更多字符
2.2.3 缓冲区存取
public abstract class ByteBuffer
extends Buffer
implements Comparable<ByteBuffer>{
public abstract ByteBuffer slice();
public abstract ByteBuffer duplicate();
public abstract ByteBuffer asReadOnlyBuffer();
// get()被调用时指出下一个元素应从何处检索
public abstract byte get();
public abstract byte get(int index);
// put()被调用时指出下一个数据元素应该被插入的位置
public abstract ByteBuffer put(byte b);
}
get()和put(),他们所采用的参数类型,以及它们返回的数据类型,对于每个子类来说都是唯一的,所以不能在顶层Buffer类中被抽象声明.
2.2.4 缓冲区的填充
ByteBuffer buffer = ByteBuffer.allocate(1024);
byte array[] = new byte1024;
ByteBuffer buffer = ByteBuffer.wrap(array);
2.3 Selectors
2.3.1 选择器基础
选择器可以决定多个通道中的哪一个准备好运行,有两种方式选择
- 被激发的线程可以处于休眠状态, 知道一个或者多个注册到选择器的通道就绪
- 周期性地轮询选择器,看看从上次检查之后,是否有通道处于就绪状态
java.nio.channels包中处理就绪选择的类
- 选择器(Selector): 选择器类管理着一个被注册的通道集合的信息和他们的就绪状态
- 可选择通道(SelectableChannel): 提供了实现通道的可选择性所需要的公共方法, 它是所有支持就绪检查的通道类的父类.所有的socket通道都是可选择的, 包括从管道(Pipe)对象中获得的通道.SelectableChannel可以被注册到多个选择器上,但对每个选择器而言只能被注册一次.
- 选择键(SelectionKey): 选择键封装了特定的通道与特定的选择器的注册关系.选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作.
Selector相关API
public abstract class Selector implements Closeable {
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();
}
public abstract boolean isOpen();
public abstract SelectorProvider provider();
public abstract Set<SelectionKey> keys();
public abstract Set<SelectionKey> selectedKeys();
public abstract int selectNow() throws IOException;
public abstract int select(long timeout)
throws IOException;
public abstract int select() throws IOException;
public abstract Selector wakeup();
public abstract void close() throws IOException;
}
Selector对象内部维护的是三个键的集合
- 已注册的键的集合(Registered key set)
- 已选择的键的集合(Selected key set)
- 已取消的键的集合(Cancelled key set)
SelectableChannel相关API
public abstract class SelectableChannel
extends AbstractInterruptibleChannel
implements Channel{
/**
* Returns the provider that created this channel.
*
* @return The provider that created this channel
*/
public abstract SelectorProvider provider();
public abstract boolean isRegistered();
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();
}
NOTE: 尽管SelectableChannel定义了registre()方法,还是应该将通道注册到选择器上,而不是另一种方式. 选择器维护了一个需要监控的通道的集合.一个给定的通道可以被注册到多于一个的选择器上,而且不需要知道它被注册了哪个Selector对象上.
SelectionKey相关API
public abstract class SelectionKey {
// -- Channel and selector operations --
public abstract SelectableChannel channel();
public abstract Selector selector();
public abstract boolean isValid();
public abstract void cancel();
public abstract int interestOps();
public abstract SelectionKey interestOps(int ops);
public abstract int readyOps();
// -- Operation bits and bit-testing convenience methods --
/**
* Operation-set bit for read operations.
*
* <p> Suppose that a selection key's interest set contains
* <tt>OP_READ</tt> at the start of a <a
* href="Selector.html#selop">selection operation</a>. If the selector
* detects that the corresponding channel is ready for reading, has reached
* end-of-stream, has been remotely shut down for further reading, or has
* an error pending, then it will add <tt>OP_READ</tt> to the key's
* ready-operation set and add the key to its selected-key set. </p>
*/
public static final int OP_READ = 1 << 0;
/**
* Operation-set bit for write operations.
*
* <p> Suppose that a selection key's interest set contains
* <tt>OP_WRITE</tt> at the start of a <a
* href="Selector.html#selop">selection operation</a>. If the selector
* detects that the corresponding channel is ready for writing, has been
* remotely shut down for further writing, or has an error pending, then it
* will add <tt>OP_WRITE</tt> to the key's ready set and add the key to its
* selected-key set. </p>
*/
public static final int OP_WRITE = 1 << 2;
/**
* Operation-set bit for socket-connect operations.
*
* <p> Suppose that a selection key's interest set contains
* <tt>OP_CONNECT</tt> at the start of a <a
* href="Selector.html#selop">selection operation</a>. If the selector
* detects that the corresponding socket channel is ready to complete its
* connection sequence, or has an error pending, then it will add
* <tt>OP_CONNECT</tt> to the key's ready set and add the key to its
* selected-key set. </p>
*/
public static final int OP_CONNECT = 1 << 3;
/**
* Operation-set bit for socket-accept operations.
*
* <p> Suppose that a selection key's interest set contains
* <tt>OP_ACCEPT</tt> at the start of a <a
* href="Selector.html#selop">selection operation</a>. If the selector
* detects that the corresponding server-socket channel is ready to accept
* another connection, or has an error pending, then it will add
* <tt>OP_ACCEPT</tt> to the key's ready set and add the key to its
* selected-key set. </p>
*/
public static final int OP_ACCEPT = 1 << 4;
public final boolean isReadable() { return (readyOps() & OP_READ) != 0; }
public final boolean isWritable() { return (readyOps() & OP_WRITE) != 0; }
public final boolean isConnectable() { return (readyOps() & OP_CONNECT) != 0;}
public final boolean isAcceptable() { return (readyOps() & OP_ACCEPT) != 0;}
// -- Attachments --
public final Object attach(Object ob)
public final Object attachment()
}
NOTE: 选择器才是提供管理功能的对象,而不是可选择通道对象. 选择器对象对注册到它之上的通道执行就绪选择,并管理选择键.
2.3.2 选择器建立和使用
- 一个Selector能够检测多个Channel, 任何一个
SelectableChannel都可以将自己注册到一个Selector中 - Channel需要在
Selector上注册自己感兴趣的事件,事件主要有:Connect,Accept,Read,Write - 调用
Selector的select方法阻塞等待Channel上的特定事件就绪 - select()返回准备就绪的通道个数,调用selector.selectedKeys()返回已经准备就绪的事件
- 在
Selector的open方法中,会根据不同的操作系统提供不同的内部实现,如果是Linux操作系统,则使用Poll(kernel < 2.6)、epoll(kernel > 2.6);如果是window操作系统,则是select,如果是Unix,则是kqueue - 当不在使用Selector时, 需要调用
close()方法来释放它可能占用的资源并将所有相关的选择键置为无效
并发性 选择器对象是线程安全的,但它们包含的键集合不是.通过keys()和selectKey()返回的键的集合是Selector对象内部的私有的Set对象集合的直接引用.这些集合可能在任意时间被改变. 已注册的键的集合是只读的. 多线程并发访问的时候,需要采取合理的方式同步访问.
Selector的唤醒机制
Selector在open()的时候,会建立一个Pipe,通过Pipe实现唤醒
- Window下不支持使用Pipe,所以通过两个连接的SocketChannel实现了Pipe,通过本地地址127.0.0.1自己连接自己
- Linux下则直接使用系统的Pipe进行通信.
Java NIO的缺点 需要自己处理拆包和粘包问题.当从通道中读数据到缓冲区的时候,我们并不知道缓冲区中的内容是否是一个完整的包,需要自己手动的进行处理.