• Java I/O(二)BIO, NIO, AIO


    BIO, NIO, AIO

    1 Java BIO

    BIO全称是Blocking IO,同步阻塞式IO,是JDK1.4之前的传统IO模型。

    • 在JDK1.4之前网络通信使用的都是BIO模型,BIO模型中accept()、read()、write()、Connection()都会阻塞,BIO要同时支处理多个客户端的连接,就必须使用多线程,即每次accept阻塞等待客户端连接对象socket,为每一个socket创建一个线程。
    • BIO提供的是面向流的I/O操作接口
    • 采用多线程范式使得BIO具备了高并发能力,即同时处理多客户端连接请求,但带来新问题,随着开启线程数量增多,会消耗过多的内存资源,导致服务器变慢甚至崩溃。
    • 服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理。虽然此时服务器具备了高并发能力,即能够同时处理多个客户端请求了,但是却带来了一个问题,随着开启的线程数目增多,将会消耗过多的内存资源,导致服务器变慢甚至崩溃,NIO可以一定程度解决这个问题。
    • BIO方式适用于连接数量少且固定的场景,这种方式对服务器资源要求比较高, JDK1.4之前唯一的选择,程序直观简单易理解。

    1.1示例

    Java BIO 基于TCP协议的服务端,客户端通信(单线程)

    2 Java NIO

    Java New IO,N还可以理解为 Non-blocking,是解决高并发、I/O高性能的有效方式。Java NIO是Java1.4之后推出来的一套IO接口,NIO提供了一种完全不同的操作方式。

    • NIO提供面向缓冲区的、基于通道的IO操做接口
    • NIO有三大核心部分:Channel(通道),Buffer(缓冲区), Selector(选择器)
    • 新增了许多用于处理输入输出的类,这些类都被放在java.nio包及子包下,并且对原java.io包中的很多类进行改写,新增了满足NIO的功能。
    • 支持多路复用。服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。
      一个线程中就可以调用多路复用接口(select)阻塞同时监听来自多个客户端的IO请求,一旦有收到IO请求就调用对应函数处理,NIO擅长1个线程管理多条连接,节约系统资源。
      适用于连接数目多且业务比较轻,比如:聊天服务器。

    2.1 核心组件

    NIO 包含3个核心的组件:

    2.1.1Channel(通道)

    Channel 是 NIO 的核心概念,它表示一个打开的连接,这个连接可以连接到 I/O 设备(例如:磁盘文件,Socket)或者一个支持 I/O 访问的应用程序,Java NIO 使用缓冲区和通道来进行数据传输。

    «interface»
    Channel
    isOpen() : boolean
    close()
    «interface»
    NetworkChannel
    bind(SocketAddress local) : NetworkChannel
    SocketOption
    SocketAddress
    InterruptibleChannel
    AbstractInterruptibleChannel
    SelectableChannel
    AbstractSelectableChannel
    «abstract»
    ServerSocketChannel
    «abstract»
    SocketChannel
    «abstract»
    DatagramChannel
    «abstract»
    SctpChannel
    «abstract»
    SctpMultiChannel
    «abstract»
    SctpServerChannel
    «abstract»
    FileChannel
    «abstract»
    Channels
    2.1.1.1 Channel

    代表I/O操作的连接。
    通道表示与实体(例如硬件设备、文件、网络套接字或能够执行一个或多个不同I/O操作(例如读取或写入)的程序组件)的开放连接。
    通道打开或关闭。通道在创建时打开,一旦关闭,它将保持关闭状态。一旦通道关闭,任何对其调用I/O操作的尝试都将引发ClosedChannelException。可以通过调用其isOpen方法来测试通道是否打开。
    一般来说,通道对于多线程访问是安全的,如扩展和实现该接口的接口和类的规范中所述。

    修饰符和返回值方法描述
    voidclose()关闭这个通道
    booleanisOpen()指示此通道是否打开
    2.1.1.2 NetworkChannel

    网络套接字的通道。
    实现此接口的通道是到网络套接字的通道。bind方法用于将套接字绑定到本地地址,getLocalAddress方法返回套接字所绑定的地址,setOption和getOption方法用于设置和查询套接字选项。这个接口的实现应该指定它支持的套接字选项。
    没有返回值的bind和setOption方法被指定为返回调用它们的网络通道。这允许链接方法调用。此接口的实现应专门化返回类型,以便可以链接实现类上的方法调用。

    修饰符和返回值方法描述
    NetworkChannelbind(SocketAddress local)将通道的套接字绑定到本地地址。
    SocketAddressgetLocalAddress()返回此通道套接字绑定到的套接字地址。
    TgetOption(SocketOption name)返回套接字选项的值。
    NetworkChannelsetOption(SocketOption name, T value)设置套接字选项的值。
    Set>supportedOptions()返回此通道支持的套接字选项集。
    2.1.1.3 ServerSocketChannel

    面向流的侦听套接字的多路复用通道。
    通过调用该类的open方法创建服务器套接字通道。无法为任意预先存在的ServerSocket创建通道。新创建的服务器套接字通道已打开,但尚未绑定。尝试调用未绑定服务器套接字通道的accept方法将引发NotYetBoundException。可以通过调用该类定义的绑定方法之一来绑定服务器套接字通道。
    套接字选项是使用setOption方法配置的。选项列表见此类:StandardSocketOptions。
    多个并发线程可以安全使用服务器套接字通道。

    修饰符和返回值方法描述
    abstract SocketChannelaccept()接受一个建到此通道的套接字的连接。
    ServerSocketChannelbind(SocketAddress local)将通道的套接字绑定到本地地址,并将套接字配置为侦听连接。
    abstract ServerSocketChannelbind(SocketAddress local, int backlog)将通道的套接字绑定到本地地址,并将套接字配置为侦听连接。backlog参数是套接字上挂起连接的最大数量。
    abstract SocketAddressgetLocalAddress()返回此通道套接字绑定到的套接字地址。
    static ServerSocketChannelopen()打开服务器套接字通道。
    abstract ServerSocketChannelsetOption(SocketOption name, T value)设置套接字选项的值。
    abstract ServerSocketsocket()检索与此通道关联的服务器套接字。
    intvalidOps()返回标识此通道支持的操作的操作集(多路复用)。具体值参见SelectionKey的静态成员变量。
    2.1.1.4 SelectableChannel

    可以通过一个Seloecor多路复用的信道。
    为了与选择器一起使用,必须首先通过register方法注册该类的实例。此方法返回一个新的SelectionKey对象,该对象表示通道在选择器中的注册。
    一旦向选择器注册,通道将保持注册状态,直到取消注册。这涉及解除分配选择器分配给通道的任何资源。
    通道不能直接注销;相反,代表其注册的密钥必须被取消。取消键要求在选择器的下一次选择操作期间取消通道注册。可以通过调用键的cancel方法显式取消键。当通道关闭时,无论是通过调用其关闭方法还是通过中断通道上I/O操作中阻塞的线程,通道的所有键都会被隐式取消。
    如果选择器本身关闭,则通道将被取消注册,并且表示其注册的密钥将无效,不再延迟。
    一个通道最多可以用任何特定的选择器注册一次。
    可以通过调用isRegistered方法来确定是否向一个或多个选择器注册了通道。
    可选择的通道对于多个并发线程来说是安全的。

    修饰符和返回值方法描述
    abstract ObjectblockingLock()查找用于同步configureBlocking和register方法的对象。
    abstract SelectableChannelconfigureBlocking(boolean block)调整此通道的阻塞模式。
    abstract booleanisBlocking()指示此通道上的每个I/O操作在完成之前是否都会阻塞。
    abstract booleanisRegistered()指示此通道当前是否已向任何选择器注册。
    abstract SelectionKeykeyFor(Selector sel)检索表示此通道用给定的Selector注册后的密钥。
    abstract SelectorProviderprovider()返回创建此通道的Provider。
    SelectionKeyregister(Selector sel, int ops)使用给定的Selector注册此通道,并返回SelectionKey。
    abstract SelectionKeyregister(Selector sel, int ops, Object att)使用给定的选择器注册此通道,并返回选择键。
    intvalidOps()返回标识此通道支持的操作的操作集(多路复用)。具体值参见SelectionKey的静态成员变量。生成的Key的附件,可以为null。
    2.1.1.5 SocketChannel

    面向流的连接套接字的多路复用通道。
    套接字通道是通过调用该类的一个打开方法创建的。不可能为任意预先存在的套接字创建通道。新创建的套接字通道已打开,但尚未连接。试图在未连接的通道上调用I/O操作将导致引发NotYetConnectedException。套接字通道可以通过调用其连接方法进行连接;一旦连接,套接字通道将保持连接,直到关闭。套接字通道是否连接可以通过调用其isConnected方法来确定。
    套接字通道支持非阻塞连接:可以创建套接字信道,并且可以通过连接方法启动建立到远程套接字的链接的过程,以便稍后通过finishConnect方法完成。连接操作是否正在进行可以通过调用isConnectionPending方法来确定。
    套接字通道支持异步关闭,这类似于Channel类中指定的异步关闭操作。如果一个套接字的输入端被一个线程关闭,而另一个线程在套接字通道上的读操作中被阻塞,那么被阻塞线程中的读操作将在不读取任何字节的情况下完成,并返回-1,则被阻止的线程将接收AsynchronousCloseException。
    套接字选项是使用setOption方法配置的。套接字通道支持以下选项:

    选项名称描述
    SO_SNDBUF套接字发送缓冲区的大小
    SO_RCVBUF套接字接收缓冲区的大小
    SO_KEEPALIVE保持连接有效
    SO_REUSEADDR重新使用地址
    SO_LINGER如果存在数据,关闭时将等待(仅在阻塞模式下配置时)
    TCP_NODELAY禁用Nagle算法

    还可以支持其他(特定于实现的)选项。
    套接字通道对于多个并发线程来说是安全的。它们支持并发读写,尽管在任何给定的时间最多只能有一个线程在读,最多只能有个线程在写。connect和finishConnect方法相互同步,在调用其中一个方法时尝试启动读或写操作将被阻止,直到调用完成。

    修饰符和返回值方法描述
    abstract SocketChannelbind(SocketAddress local)将通道的套接字绑定到本地地址。
    abstract booleanconnect(SocketAddress remote)连接此通道的套接字。
    abstract booleanfinishConnect()完成连接套接字通道的过程。
    abstract SocketAddressgetLocalAddress()返回此通道套接字绑定到的套接字地址。
    abstract SocketAddressgetRemoteAddress()返回此通道套接字连接到的远程地址。
    abstract booleanisConnected()指示此通道的网络套接字是否已连接。
    abstract booleanisConnectionPending()指示此通道上是否正在进行连接操作。
    static SocketChannelopen()静态SocketChannel打开()
    static SocketChannelopen(SocketAddress remote)打开套接字通道并将其连接到远程地址。
    abstract intread(ByteBuffer dst)将此通道中的字节序列读取到给定的缓冲区中。
    longread(ByteBuffer[] dsts)将此通道中的字节序列读取到给定的缓冲区中。
    abstract longread(ByteBuffer[] dsts, int offset, int length)将此通道中的字节序列读取为给定缓冲区的子序列。
    abstract SocketChannelsetOption(SocketOption name, T value)设置套接字选项的值。
    abstract SocketChannelshutdownInput()在不关闭通道的情况下关闭读取连接。
    abstract SocketChannelshutdownOutput()在不关闭通道的情况下关闭连接进行写入。
    abstract Socketsocket()检索与此通道关联的套接字。
    intvalidOps()返回标识此通道支持的操作的操作集。
    abstract intwrite(ByteBuffer src)将字节序列从给定缓冲区写入此通道。
    longwrite(ByteBuffer[] srcs)从给定的缓冲区将字节序列写入此通道。
    abstract longwrite(ByteBuffer[] srcs, int offset, int length)从给定缓冲区的子序列将字节序列写入此通道。
    2.1.1.6 FileChannel
    OpenOption
    StandardOpenOption
    APPEND
    CREATE
    CREATE_NEW
    DELETE_ON_CLOSE
    DSYNC
    READ
    SPARSE
    SYNC
    TRUNCATE_EXISTING
    WRITE
    LinkOption
    NOFOLLOW_LINKS
    «abstract»
    FileChannel
    FileLock
    FileAttribute
    MapMode
    READ_ONLY
    READ_WRITE
    PRIVATE
    PosixFilePermission
    GROUP_EXECUTE
    GROUP_READ
    GROUP_WRITE
    OTHERS_EXECUTE
    OTHERS_READ
    OTHERS_WRITE
    OWNER_EXECUTE
    OWNER_READ
    OWNER_WRITE
    PosixFilePermissions

    用于读取、写入、映射和操作文件的通道。
    文件通道是连接到文件的SeekableByteChannel。它在其文件中有一个当前位置,可以查询和修改。文件本身包含一个可变长度的字节序列,可以读取和写入,并且可以查询其当前大小。当写入的字节超过其当前大小时,文件的大小会增加;文件被截断时,其大小会减小。文件还可以具有一些相关联的元数据,例如访问权限、内容类型和上次修改时间;此类不定义元数据访问的方法。

    修饰符和返回值方法描述
    abstract voidforce(boolean metaData)强制将此通道的文件的任何更新写入包含该文件的存储设备。
    FileLocklock()获取此通道的文件的独占锁。
    abstract FileLocklock(long position, long size, boolean shared)获取此通道的文件的给定区域的锁定。
    abstract MappedByteBuffermap(FileChannel.MapMode mode, long position, long size)获取此通道的文件的给定区域的锁定。
    abstract MappedByteBuffermap(FileChannel.MapMode mode, long position, long size)将此频通道的文件的一个区域直接映射到内存中。
    static FileChannelopen(Path path, OpenOption… options)打开或创建文件,返回访问文件的文件通道。
    static FileChannelopen(Path path, Set options, FileAttribute… attrs)打开或创建文件,返回访问文件的文件通道。
    abstract longposition()返回此通道的文件位置。
    abstract FileChannelposition(long newPosition)设置此通道的文件位置。
    abstract intread(ByteBuffer dst)将此通道中的字节序列读取到给定的缓冲区中。
    longread(ByteBuffer[] dsts)将此通道中的字节序列读取到给定的缓冲区中。
    abstract longread(ByteBuffer[] dsts, int offset, int length)将此通道中的字节序列读取为给定缓冲区的子序列。
    abstract intread(ByteBuffer dst, long position)从给定的文件位置开始,将此通道中的字节序列读取到给定的缓冲区中。
    abstract longsize()返回此通道的文件的当前大小。
    abstract longtransferFrom(ReadableByteChannel src, long position, long count)将字节从给定的可读字节通道传输到此通道的文件中。
    abstract longtransferTo(long position, long count, WritableByteChannel target)将字节从此通道的文件传输到给定的可写字节通道。
    abstract FileChanneltruncate(long size)将此通道的文件截断为给定大小。
    FileLocktryLock()试图获取此通道的文件的独占锁。
    abstract FileLocktryLock(long position, long size, boolean shared)尝试获取此通道的文件的给定区域的锁定。
    abstract intwrite(ByteBuffer src)将字节序列从给定缓冲区写入此通道。
    longwrite(ByteBuffer[] srcs)从给定的缓冲区将字节序列写入此通道。
    abstract longwrite(ByteBuffer[] srcs, int offset, int length)从给定缓冲区的子序列将字节序列写入此通道。
    abstract intwrite(ByteBuffer src, long position)从给定的文件位置开始,从给定的缓冲区将字节序列写入此通道。

    enum OpenOption
    配置如何打开或创建文件的对象。
    这种类型的对象在以下打开或创建文件的方法中被用到:newOutputStream, newByteChannel, FileChannel.open, 和AsynchronousFileChannel.open

    enum StandardOpenOption
    定义了标准的打开选项,如下表:

    • APPEND
    • CREATE
    • CREATE_NEW
    • DELETE_ON_CLOSE
    • DSYNC
    • READ
    • SPARSE
    • SYNC
    • TRUNCATE_EXISTING
    • WRITE

    enum LinkOption
    定义如何处理符号链接的选项。目前只有一个选项:

    • NOFOLLOW_LINKS

    相关知识:链接文件
    相关知识:FollowSymLinks

    class FileLock
    表示文件区域上的锁的标记。
    每次通过FileChannel类的lock或tryLock方法之一或AsynchronousFileChannel的lock方法或tryLock方法获取文件锁定时,都会创建一个文件锁定对象。
    文件锁定对象最初有效。在通过调用释放方法、关闭用于获取锁的通道或终止Java虚拟机(以先到者为准)释放锁之前,它一直有效。可以通过调用其isValid方法来测试锁的有效性。
    文件锁是独占的或共享的。共享锁防止其他并发运行的程序获取重叠的独占锁,但允许它们获取重叠的共享锁。独占锁防止其他程序获取任何类型的重叠锁。一旦释放,锁对其他程序可能获取的锁没有进一步的影响。
    锁是独占的还是共享的,可以通过调用其isShared方法来确定。某些平台不支持共享锁,在这种情况下,对共享锁的请求会自动转换为对独占锁的请求。
    单个Java虚拟机对特定文件的锁不会重叠。重叠方法可用于测试候选锁范围是否与现有锁重叠。
    文件锁定对象记录文件通道,锁定在其上,锁定的类型和有效性,以及锁定区域的位置和大小。只有锁的有效性会随着时间的推移而改变;锁状态的所有其他方面都是不可变的。
    文件锁代表整个Java虚拟机持有。它们不适合控制同一虚拟机中的多个线程对文件的访问。
    多个并发线程可以安全使用文件锁定对象。

    修饰符和返回值方法描述
    ChannelacquiredBy()返回获取此锁的文件所在的通道。
    FileChannelchannel()返回获取此锁的文件通道。
    voidclose()此方法调用release()方法。
    booleanisShared()指示是否共享此锁。
    abstract booleanisValid()指示此锁是否有效。锁定对象保持有效,直到它被释放或关联的文件通道被关闭(以先到者为准)。
    booleanoverlaps(long position, long size)指示此锁是否与给定的锁范围重叠。
    longposition()返回锁定区域的第一个字节在文件中的位置。
    abstract voidrelease()释放此锁。
    longsize()返回锁定区域的大小(以字节为单位)。
    StringtoString()返回描述此锁的范围、类型和有效性的字符串。

    interface FileAttribute
    一个封装文件属性值的对象,当通过调用createFile或createDirectory方法创建新文件或目录时,可以自动设置该属性值。
    可以通过工具类PosixFilePermissions将Posix权限字符串(如“-rwxr-xr-x”)转换为FileAttribute集合,用来设置文件权限。

    enum PosixFilePermission
    定义用于权限属性的比特位。枚举的每一个值与Poxis文件权限比特位对应。包含以下选项:

    • GROUP_EXECUTE
    • GROUP_READ
    • GROUP_WRITE
    • OTHERS_EXECUTE
    • OTHERS_READ
    • OTHERS_WRITE
    • OWNER_EXECUTE
    • OWNER_READ
    • OWNER_WRITE

    相关知识介绍:Posix文件权限比特位

    PosixFilePermissions
    该类仅由对PosixFilePermission对象集合进行操作的静态方法组成。

    修饰符和返回值方法描述
    static FileAttributeasFileAttribute(Set perms)创建FileAttribute,封装给定文件权限的副本,适合传递给createFile或createDirectory方法。
    static SetfromString(String perms)返回与给定字符串表示形式相对应的权限集。
    static StringtoString(Set perms)返回权限集的字符串表示形式。

    示例:
    使用FileChannel读写500MB文件

    2.1.1.7 Channels

    通道和流的工具方法。
    这个类定义了支持java.o包中的流类和通道类的互操作的静态方法。

    修饰符和返回值方法描述
    static ReadableByteChannelnewChannel(InputStream in)构造从给定流中读取字节的通道。
    static WritableByteChannelnewChannel(OutputStream out)构造向给定流写入字节的通道。
    static InputStreamnewInputStream(AsynchronousByteChannel ch)构造从给定通道读取字节的流。
    static InputStreamnewInputStream(ReadableByteChannel ch)构造从给定通道读取字节的流。
    static OutputStreamnewOutputStream(AsynchronousByteChannel ch)构造向给定通道写入字节的流。
    static OutputStreamnewOutputStream(WritableByteChannel ch)构造向给定通道写入字节的流。
    static ReadernewReader(ReadableByteChannel ch, CharsetDecoder dec, int minBufferCap)构造一个Reader,该读取器使用给定的解码器对给定通道中的字节进行解码。
    static ReadernewReader(ReadableByteChannel ch, String csName)构造一个Reader,该读取器根据命名的字符集对给定通道中的字节进行解码。
    static WriternewWriter(WritableByteChannel ch, CharsetEncoder enc, int minBufferCap)构造一个编写器,该编写器使用给定的编码器对字符进行编码,并将生成的字节写入给定的通道。
    static WriternewWriter(WritableByteChannel ch, String csName)构造一个编写器,该编写器根据命名的字符集对字符进行编码,并将生成的字节写入给定的通道。
    2.1.2 Buffer(缓冲区)

    缓冲区 Buffer 是 Java NIO 中一个核心概念,在NIO库中,所有数据都是用缓冲区处理的。
    在读取数据时,它是直接读到缓冲区中的,在写入数据时,它也是写入到缓冲区中的,任何时候访问 NIO 中的数据,都是将它放到缓冲区中。
    而在面向流I/O系统中,所有数据都是直接写入或者直接将数据读取到Stream对象中。

    «abstract»
    Buffer
    «abstract»
    ByteBuffer
    «abstract»
    MappedByteBuffer
    «abstract»
    CharBuffer
    «abstract»
    ShortBuffer
    «abstract»
    IntBuffer
    «abstract»
    LongBuffer
    «abstract»
    FloatBuffer
    «abstract»
    DoubleBuffer
    2.1.2.1 Buffer接口

    此接口是特定的基本类型数据的容器。
    缓冲区是特定基本类型数据的元素的线性有限序列。除了内容之外,缓冲区的基本特性是其capacity、limit和position:

    • 缓冲区的capacity是它包含的元素数。缓冲区的capacity从不为负,也从不改变。
    • 缓冲区的limit是不应读取或写入的第一个元素的索引。缓冲区的limit从不为负,也从不大于其capacity。
    • 缓冲区的position是要读取或写入的下一个元素的索引。缓冲区的position永远不会为负,也永远不会大于其limit。
      针对每个非布尔基本数据类型,此接口都有一个子类与之对应。

    数据传输
    该类的每个子类定义了两类get和put操作:

    • 相对操作从当前位置开始读取或写入一个或多个元素,然后按传输的元素数量递增位置。如果请求的传输超过限制,则相对get操作抛出BufferUnderflowException,相对put操作抛出bufferOverflowExceltion;无论哪种情况,都不会传输数据。
    • 绝对操作采用显式元素索引,不影响位置。如果索引参数超出限制,则绝对get和put操作将抛出IndexOutOfBoundsException。

    当然,数据也可以通过适当通道的I/O操作传送到缓冲器中或从缓冲器中传送出来,这些操作总是相对于当前位置。

    mark和reset
    缓冲区的mark是当调用重置方法时其位置将重置到的索引。标记并不总是被定义的,但当它被定义时,它永远不会是负值,也永远不会大于位置。如果定义了标记,则当位置或限制调整到小于标记的值时,标记将被丢弃。如果未定义标记,则调用reset方法会引发InvalidMarkException。

    不变式
    以下不变量适用于mark、position、limit和capacity:
    0<=mark<=position<=limit<=capacity
    新创建的缓冲区的position始终为零,mark未定义。初始limit可以是零,也可以是取决于缓冲区类型及其构造方式的其他值。新分配的缓冲区的每个元素被初始化为零。
    clear、flip和rewind
    除了用于访问position、limit和capacity值以及mark和reset的方法之外,此类还定义了缓冲区上的以下操作:
    clear()使缓冲区为新的通道读取或相对放操作序列做好准备:它将capacity,limit和position设置为零。
    flip()使缓冲区为新的通道写入或相对获取操作序列做好准备:它将limit设置为当前position,然后将position设置为零。
    rewind()使缓冲区准备好重新读取它已经包含的数据:它保持limit不变,并将position设置为零。

    只读缓冲区
    每个缓冲区都是可读的,但不是每个缓冲区是可写的。每个缓冲区类的变异方法被指定为可选操作,当对只读缓冲区调用时,这些操作将抛出ReadOnlyBufferException。只读缓冲区不允许更改其内容,但其mark、position和limit是可变的。可以通过调用其isReadOnly方法来确定缓冲区是否为只读。

    线程安全性
    缓冲区对于多个并发线程来说是不安全的。如果一个缓冲区将由多个线程使用,那么应该通过适当的同步来控制对缓冲区的访问。

    调用链
    该类中没有返回值的方法被指定为返回调用它们的缓冲区。这允许链接方法调用;b.flip().position(23).limit(42);

    修饰符和返回值方法描述
    abstract Objectarray()返回支持此缓冲区的数组(可选操作)。
    abstract intarrayOffset()返回此缓冲区的第一个元素的备份数组中的偏移量(可选操作)。
    intcapacity()返回此缓冲区的容量。
    Bufferclear()清除此缓冲区。
    Bufferflip()翻转此缓冲区。
    abstract booleanhasArray()指示此缓冲区是否由可访问数组支持。
    booleanhasRemaining()指示当前position和limit之间是否存在任何元素。
    abstract booleanisDirect()指示此缓冲区是否为直接缓冲区。
    abstract booleanisReadOnly()指示此缓冲区是否为只读。
    intlimit()返回此缓冲区的limit。
    Bufferlimit(int newLimit)设置此缓冲区的limit。
    Buffermark()将此缓冲区的标记设置为position的值。
    intposition()返回此缓冲区的position。
    Bufferposition(int newPosition)设置此缓冲区的position。
    intremaining()返回当前position和limit之间的元素数。
    Bufferreset()将此缓冲区的position重置为先前标记的position。
    Bufferrewind()回放此缓冲区。
    2.1.2.2 ByteBuffer

    字节缓冲区。
    直接缓冲区非直接缓冲区
    字节缓冲区可以是直接的,也可以是非直接的。给定直接字节缓冲区,Java虚拟机将尽最大努力直接在其上执行本机I/O操作。也就是说,它将尝试避免在每次调用底层操作系统的本机I/O操作之前(或之后)将缓冲区的内容复制到中间缓冲区(或从中间缓冲区复制)。
    可以通过调用此类的allocateDirect工厂方法来创建直接字节缓冲区。此方法返回的缓冲区通常比非直接缓冲区的分配和释放成本稍高。直接缓冲区的内容可能位于正常的垃圾收集堆之外,因此它们对应用程序内存占用的影响可能不明显。因此,建议将直接缓冲区主要分配给受底层系统本机I/O操作影响的大型长寿命缓冲区。通常,只有当直接缓冲区在程序性能上产生可测量的增益时,才最好分配它们。
    也可以通过将文件的区域直接映射到存储器来创建直接字节缓冲区。Java平台的实现可以可选地支持通过JNI从本机代码创建直接字节缓冲区。如果这些类型的缓冲区之一的实例引用了一个不可访问的内存区域,那么访问该区域的尝试将不会更改缓冲区的内容,并将导致在访问时或稍后某个时间引发未指定的异常。
    字节缓冲区是直接的还是非直接的,可以通过调用其isDirect方法来确定。提供此方法是为了在性能关键代码中实现显式缓冲区管理。
    视图缓冲区
    为了访问同质二进制数据,即相同类型的值序列,该类定义了可以创建给定字节缓冲区视图的方法。视图缓冲区只是另一个缓冲区,其内容由字节缓冲区支持。对字节缓冲区内容的更改将在视图缓冲区中可见,反之亦然;两个缓冲区的位置、限制和标记值是独立的。例如,asFloatBuffer方法创建一个FloatBuff类的实例,该实例由调用该方法的字节缓冲区支持。为char、short、int、long和double类型定义了相应的视图创建方法。

    修饰符和返回值方法描述
    static ByteBufferallocate(int capacity)分配新的字节缓冲区。
    static ByteBufferallocateDirect(int capacity)分配新的直接字节缓冲区。
    byte[]array()返回支持此缓冲区的字节数组(可选操作)。
    intarrayOffset()返回此缓冲区的第一个元素的备份数组中的偏移量(可选操作)。
    abstract CharBufferasCharBuffer()将此字节缓冲区的视图创建为字符缓冲区。
    abstract DoubleBufferasDoubleBuffer()将此字节缓冲区的视图创建为双缓冲区。
    abstract FloatBufferasFloatBuffer()分配新的字节缓冲区。
    abstract IntBufferasIntBuffer()分配新的直接字节缓冲区。
    abstract LongBufferasLongBuffer()返回支持此缓冲区的字节数组(可选操作)。
    abstract ByteBufferasReadOnlyBuffer()返回此缓冲区的第一个元素的备份数组中的偏移量(可选操作)。
    abstract ShortBufferasShortBuffer()将此字节缓冲区的视图创建为字符缓冲区。
    abstract ByteBuffercompact()压缩此缓冲区(可选操作)。
    intcompareTo(ByteBuffer that)将此缓冲区与另一缓冲区进行比较。
    abstract ByteBufferduplicate()创建共享此缓冲区内容的新字节缓冲区。
    booleanequals(Object ob)指示此缓冲区是否等于另一个对象。
    abstract byteget()相对获取方法。
    ByteBufferget(byte[] dst)相对批量获取方法。
    ByteBufferget(byte[] dst, int offset, int length)相对批量获取方法。
    abstract byteget(int index)绝对获取方法。
    abstract chargetChar()读取字符值的相对获取方法。
    abstract chargetChar(int index)读取字符值的绝对获取方法。
    abstract doublegetDouble()读取双精度值的相对获取方法。
    abstract doublegetDouble(int index)读取双精度值的绝对获取方法。
    abstract floatgetFloat()读取浮点值的相对获取方法。
    abstract floatgetFloat(int index)读取浮点值的绝对获取方法。
    abstract intgetInt()读取int值的相对获取方法。
    abstract intgetInt(int index)读取int值的绝对get方法。
    abstract longgetLong()读取长值的相对获取方法。
    abstract longgetLong(int index)读取长值的绝对获取方法。
    abstract shortgetShort()读取短值的相对获取方法。
    abstract shortgetShort(int index)读取短值的绝对获取方法。
    booleanhasArray()指示此缓冲区是否由可访问的字节数组支持。
    inthashCode()返回此缓冲区的当前哈希代码。
    abstract booleanisDirect()指示此字节缓冲区是否是直接的。
    ByteOrderorder()检索此缓冲区的字节顺序。大端还是小端。
    ByteBufferorder(ByteOrder bo)修改此缓冲区的字节顺序。
    abstract ByteBufferput(byte b)相对放置方法(可选操作)。
    ByteBufferput(byte[] src)相对批量放置方法(可选操作)。
    ByteBufferput(byte[] src, int offset, int length)相对批量放置方法(可选操作)。
    ByteBufferput(ByteBuffer src)相对批量放置方法(可选操作)。
    abstract ByteBufferput(int index, byte b)绝对放置方法(可选操作)。
    abstract ByteBufferputChar(char value)写入字符值的相对put方法(可选操作)。
    abstract ByteBufferputChar(int index, char value)写入字符值的绝对put方法(可选操作)。
    abstract ByteBufferputDouble(double value)写入双精度值的相对put方法(可选操作)。
    abstract ByteBufferputDouble(int index, double value)写入双精度值的绝对put方法(可选操作)。
    abstract ByteBufferputFloat(float value)写入浮点值的相对put方法(可选操作)。
    abstract ByteBufferputFloat(int index, float value)写入浮点值的绝对put方法(可选操作)。
    abstract ByteBufferputInt(int value)写入int值的相对put方法(可选操作)。
    abstract ByteBufferputInt(int index, int value)写入int值的绝对put方法(可选操作)。
    abstract ByteBufferputLong(int index, long value)写入长值的绝对put方法(可选操作)。
    abstract ByteBufferputLong(long value)写入长值的相对put方法(可选操作)。
    abstract ByteBufferputShort(int index, short value)写入短值的绝对put方法(可选操作)。
    abstract ByteBufferputShort(short value)写入短值的相对put方法(可选操作)。
    abstract ByteBufferslice()创建一个新的字节缓冲区,其内容是该缓冲区内容的共享子序列。新缓冲区的内容将从此缓冲区的当前位置开始。对该缓冲区内容的更改将在新缓冲区中可见,反之亦然;两个缓冲区的位置、限制和标记值将是独立的。
    新缓冲区的位置将为零,其容量和限制将为该缓冲区中剩余的字节数,其标记将未定义。当且仅当此缓冲区是直接的时,新缓冲区将是直接的;当且仅在此缓冲区为只读的时,它将是只读的。
    StringtoString()返回总结此缓冲区状态的字符串。
    static ByteBufferwrap(byte[] array)将字节数组包装到缓冲区中。
    static ByteBufferwrap(byte[] array, int offset, int length)Wraps a byte array into a buffer.

    ByteBuffer中的参数position、limit、capacity、mark含义
    position:表示当前指针的位置(下一个要操作的数据元素的位置)
    limit:表示当前数组最大的使用量,即有效位置的EOF位置(缓冲区数组中不可操作的下一个元素的位置,limit<=capacity)
    capacity:表示缓冲区最大容量(缓冲区数据的总长度)
    mark:用于记录当前position的前一个位置或者默认是-1

    ByteBuffer中常用方法含义
    flip():将参数设置为limit=position,position=0,mark=-1,翻转,即将未翻转之前0到position之间的数据放置到翻转之后的position(即0)到limit之间的这块区域,翻转将缓冲区的状态由存数据变为准备取数据(注意:将当前位置设置为EOF,指针位置指向0)。
    mark(): 把mark的值设置为当前position的值,用来做一个记录,便于以后回退到当前位置
    reset():把position设置为mark的值,相当于之前做过一个标记,现在回退到之前标记的地方。
    rewind():将参数设置为position=0,mark=-1,limit的值不变(注意:指针指向0)。
    compact():切换为写模式,把未读完成的数据向前压缩,然后再切换。如果缓冲区中数据没有读取完,就需要立马写入数据,所以需要对未读取完的数据进行压缩,避免数据丢失。compact也会改变position和limit指针位置,但position的值等于保留的数据的大小,而是等于上次读取的位置,limit=capacity位置。
    clear():将参数设置为position=0,limit=capacity,mark=-1,类似于初始化,但并不影响底层byte数组的内容(注意:clear只是把指针移到位置0,并没有真正清空数据)。

    缓冲区操作示例
    分配一个大小为10的CharBuffer:CharBuffer charBuffer = CharBuffer.allocate(10)
    在这里插入图片描述
    这时缓冲区处于写模式,调用charBuffer.put(“Hello”)直接写入字符串
    在这里插入图片描述
    接下来我们要读取缓冲区的内容,调用charBuffer.flip()切换为读模式
    在这里插入图片描述
    调用charBuffer.getChar()读取两个字符
    在这里插入图片描述
    调用charBuffer.mark()记录当前位置
    在这里插入图片描述
    再读取两个字符
    在这里插入图片描述
    调用charBuffer.reset()回到记录的位置
    在这里插入图片描述
    再读取一个字符
    在这里插入图片描述
    调用调用charBuffer.compact()切换为写模式
    在这里插入图片描述
    调用charBuffer.put(“!”)写入一个字符
    在这里插入图片描述
    调用charBuffer.rewind()倒回
    在这里插入图片描述
    调用charBuffer.clear()所有变量恢复到初始值
    在这里插入图片描述

    2.1.2.3 MappedByteBuffer

    在学习MappedByteBuffer之前,先来了解相关知识:内存映射文件
    MappedByteBuffer是一种direct字节缓冲区,其内容是文件的内存映射区域。它是基于“内存映射文件”来实现的。
    MappedByteBuffer是存放在堆外的直接内存中,可以映射到文件。
    通过java.nio包和MappedByteBuffer允许Java程序直接从内存中读取文件内容,通过将整个或部分文件映射到内存,由操作系统来处理加载请求和写入文件,应用只需要和内存打交道,这使得IO操作非常快。
    Mmap内存映射和普通标准IO操作的本质区别在于它并不需要将文件中的数据先拷贝至OS的内核IO缓冲区,而是可以直接将用户进程私有地址空间中的一块区域与文件对象建立映射关系,这样程序就好像可以直接从内存中完成对文件读/写操作一样。
    MappedByteBuffer最大一次处理2GB-1B的文件内容。如果超出这个大小,就要进行分块操作。
    MappedByteBuffer是通过FileChannel.map方法。此类使用特定于内存映射文件区域的操作扩展了ByteBuffer类。
    MappedByteBuffer及其表示的文件映射在缓冲区本身被垃圾收集之前保持有效。
    MappedByteBuffer的内容可以随时更改,例如,如果映射文件的相应区域的内容被该程序或其他程序更改。此类更改是否发生,以及何时发生,取决于操作系统,因此未指定。
    MappedByteBuffer的全部或部分可能随时无法访问,例如,如果映射的文件被截断。尝试访问MappedByteBuffer的不可访问区域不会更改缓冲区的内容,并且会导致在访问时或稍后某个时间引发未指定的异常。因此,强烈建议采取适当的预防措施,以避免此程序或同时运行的程序操纵映射文件,但读取或写入文件内容除外。
    除此之外,MappedByteBuffer的行为与普通直接字节缓冲区无异。

    修饰符和返回值方法描述
    MappedByteBufferforce()强制将对此缓冲区内容所做的任何更改写入包含映射文件的存储设备。
    booleanisLoaded()指示此缓冲区的内容是否驻留在物理内存中。
    MappedByteBufferload()将此缓冲区的内容加载到物理内存中。

    示例:
    使用MappedByteBuffer读写500MB文件
    从程序运行的结果来看,在笔者的机器上,使用MappedByteBuffer写入大文件的是耗时是普通ByteBuffer的6倍左右,读取时间差距较小,不到一倍。
    内存占用明显增加,并且可能会遇到内存溢出的错误,导致内存映射失败:

    Start to write a 500MB file.
    Exception in thread "main" java.io.IOException: Map failed
    	at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:938)
    	at com.qupeng.io.nio.file.TestMappedByteBuffer.crateLargeFile(TestMappedByteBuffer.java:47)
    	at com.qupeng.io.nio.file.TestMappedByteBuffer.main(TestMappedByteBuffer.java:18)
    Caused by: java.lang.OutOfMemoryError: Map failed
    	at sun.nio.ch.FileChannelImpl.map0(Native Method)
    	at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:935)
    	... 2 more
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    2.1.3 Selector(选择器)
    «abstract»
    Selector
    «abstract»
    SelectionKey
    OP_ACCEPT int
    OP_CONNECT int
    OP_READ int
    OP_WRITE int
    «abstract»
    SelectorProvider

    Selector是SelectableChannel对象的多路复用器。
    Selector类是NIO的核心类,Selector(选择器)提供了选择已经就绪的任务的能力。
    Selector会不断的轮询注册在上面的所有channel,如果某个channel为读写等事件做好准备,那么就处于就绪状态,通过Selector可以不断轮询发现出就绪的channel,进行后续的IO操作。
    一个Selector能够同时轮询多个channel,这样,一个单独的线程就可以管理多个channel,从而管理多个网络连接,这样就不用为每一个连接都创建一个线程,同时也避免了多线程之间上下文切换导致的开销。
    Selectable channel与selector的注册由SelectionKey对象表示。selector维护三组SelectionKey:

    1. key集包含表示此selector的当前channel注册的key。这个集合由keys()方法返回。
    2. selected-key集合表示在一个先前的选择操作期间,每个key的chanel被检测到准备好用于在key的兴趣集中标识的至少一个操作。这个集合由selectedKeys方法返回。selected-key集合是key集合的子集。
    3. cancelled-key集合是已取消但其channel尚未取消注册的key集合。此集合无法直接访问。cancelled-key始终是key集合的子集。
      在新创建的selector中,所有三个集合都是空的。
      Selector本身对于多个并发线程来说是安全的;然而,它们的key集和却不是。
    2.1.3.1 Selector
    修饰符和返回值方法描述
    abstract voidclose()关闭此选择器。
    abstract booleanisOpen()指示此选择器是否打开。
    abstract Setkeys()返回此选择器的键集。
    static Selectoropen()打开选择器。
    abstract SelectorProviderprovider()返回创建此通道的提供程序。
    abstract intselect()选择一组键,其相应通道已准备好进行I/O操作。
    abstract intselect(long timeout)选择一组键,其相应通道已准备好进行I/O操作。
    abstract SetselectedKeys()返回此选择器的选定键集。
    abstract intselectNow()选择一组键,其相应通道已准备好进行I/O操作。
    abstract Selectorwakeup()使尚未返回的第一个选择操作立即返回。
    2.1.3.2 SelectionKey

    SelectionKey表示SelectableChannel在Selector中注册的令牌。
    每次向Selector注册Channel时,都会创建一个selection key。一个key在通过调用其cancel方法、关闭其通道或关闭其selector而被取消之前保持有效。取消键不会立即将其从选择器中删除;而是将其添加到选择器的取消键集中,以便在下一次选择操作期间删除。可以通过调用其isValid方法来测试key的有效性。
    SelectionKey包含两个表示为整数值的操作集。操作集的每个位表示key的通道支持的可选择操作的类别。

    1. 兴趣集确定在下一次调用选择器的一个选择方法时将测试哪些操作类别的就绪性。使用创建key时给定的值初始化兴趣集;它可以稍后通过interestOps(int)方法进行更改。
    2. 就绪集标识键的选择器检测到key的通道已就绪的操作类别。创建key时,就绪集被初始化为0;它可以稍后在选择操作期间由选择器更新,但不能直接更新。
    修饰符和返回值方法描述
    Objectattach(Object ob)将给定对象附加到此key
    Objectattachment()检索当前附件。
    abstract voidcancel()请求取消此key的通道与其选择器的注册。
    abstract SelectableChannelchannel()返回为其创建此key的通道。
    abstract intinterestOps()检索此密钥的兴趣集。
    abstract SelectionKeyinterestOps(int ops)将此key的兴趣集设置为给定的值。
    booleanisAcceptable()测试此key的通道是否准备好接受新的套接字连接。服务端Socket使用。
    booleanisConnectable()测试此key的通道是否已完成或未能完成其套接字连接操作。客户端Socket使用。
    booleanisReadable()测试此key的通道是否准备好读取。
    abstract booleanisValid()指示此key是否有效。
    booleanisWritable()测试此key的通道是否准备好写入。
    abstract intreadyOps()检索此key的就绪操作集。
    abstract Selectorselector()返回为其创建此key的选择器。

    2.2 Java NIO Reactor模式

    Java Reactor模式就是对多路复用I/O模式的Java封装,屏蔽掉底层API交互,开放出面向对象的接口,更易于编程。
    Reactor,字面意思“核反应队”,也就是来了一个I/O事件,Reactor 就有相对应的相响应:根据事件类型分配(Dispatch)给某个线程。
    Reactor 模式主要由两部分组成: Reactor 和数据处理线程池。Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;数据处理线程池负责事件处理。
    Reactor 模式是灵活多变的,可以应对不同的业务场景,Reactor 的数量可以只有一个,也可以有多个;数据处理线程池可以是单个线程,也可以是多个线程;

    Java NIO中的Selector相当于Reactor的角色,ServerSocketChannel相当于Acceptor的角色。

    理论上就可以有 4 种方案选择:

    • 单 Reactor 单线程
      1.Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
      2.如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;
      3.如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
      Redis采用的就是这种方案(单Reactor单进程)
      Reactor模式
    • 单 Reactor 多线程
      前面3步与单Reactor单线程一样,只是把业务处理交给新的线程来处理:
      4.Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理;子线程里的 Processor
      5.对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client;
    • 多 Reactor 单线程
      这样的实现无实际意义,将最耗时的业务处理全部交由单个线程来完成,退化为单 Reactor 单线程方案
    • 多 Reactor 多线程
      1.主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;
      2.子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件;
      3.如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应;
      4.HandlerHandler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。
      Netty,Memcache,Nginx(多Reactor多进程)均采用这种方案

    2.3 示例

    Java NIO 基于TCP协议的服务端,客户端通信(非阻塞)
    Java NIO 基于多路复用的服务端,客户端通信(非阻塞,单线程)

    3 Java AIO

    AIO(Asynchronous I/O) 异步非阻塞模型, 在javajdk.17版本开始支持AIO,AIO模型需要操作系统的支持。AIO提供的最大的特点是具备异步功能,需要借助操作系统,底层操作系统具有异步IO模型,异步操作的实现是在对应的read/write/accept/connection等方法异步执行,完成后会主动调用回调函数,实现一个CompletionHandler对象。

    • 适用于连接数目多且连接比较长(业务重操作),需要操作系统充分参与并发操作。
    • 读写都是异步的,完成之后操作系统会主动调用回调函数。

    3.1 核心组件

    «interface»
    Channel
    isOpen() : boolean
    close()
    «interface»
    AsynchronousChannel
    «abstract»
    AsynchronousServerSocketChannel
    «interface»
    NetworkChannel
    bind(SocketAddress local) : NetworkChannel
    «interface»
    CompletionHandler
    «abstract»
    AsynchronousChannelGroup
    «abstract»
    AsynchronousChannelProvider
    «abstract»
    AsynchronousSocketChannel
    «abstract»
    AsynchronousFileChannel
    3.1.1 AsynchronousChannel

    支持异步I/O操作的通道。异步I/O操作通常采用以下两种形式之一:

    1. Future operation(…)
    2. void operation(… A attachment, CompletionHandler handler)

    其中operation是I/O操作的名称(例如读或写),V是I/O操作结果类型,A是附加到I/O操作以在使用结果时提供上下文的对象类型。对于使用无状态CompletionHandler来消耗许多I/O操作的结果的情况,附件很重要。
    在第一种形式中,Future接口定义的方法可以用于检查操作是否已完成,等待其完成,并检索结果。在第二种形式中,当I/O操作完成或失败时,调用CompletionHandler来消费I/O操作的结果。
    实现此接口的通道是异步可关闭的:如果通道上的I/O操作未完成,并且通道的关闭方法被调用,则I/O操作将失败,并出现异常AsynchronousCloseException。
    异步通道对于多个并发线程来说是安全的。一些通道实现可能支持并发读写,但可能不允许在任何给定时间执行一个以上的读写操作。

    取消
    Future接口定义取消执行的取消方法。这会导致等待I/O操作结果的所有线程抛CancellationException。是否可以取消底层I/O操作是高度特定于实现的,因此未指定。如果取消使通道或其所连接的实体处于不一致状态,则通道将进入特定于实现的错误状态,从而阻止进一步尝试启动与被取消的操作类似的I/O操作。例如,如果一个读取操作被取消,但实现不能保证字节没有从通道中读取,那么它将通道置于错误状态;进一步尝试启动读取操作会引发未指定的运行时异常。类似地,如果写入操作被取消,但实现无法保证字节未写入通道,则后续尝试启动写入将失败,并出现未指定的运行时异常。
    如果在mayInterruptIfRunning参数设置为true的情况下调用cancel方法,则可以通过关闭通道来中断I/O操作。在这种情况下,等待I/O操作结果的所有线程都会抛出CancellationException,通道上未完成的任何其他I/O操作也会抛出AsynchronousCloseException。
    如果调用cancel方法来取消读或写操作,则建议丢弃I/O操作中使用的所有缓冲区,或小心确保在通道保持打开时不访问缓冲区。

    修饰符和返回值方法描述
    voidclose()关闭通道。
    3.1.2 AsynchronousServerSocketChannel

    面向流的侦听套接字的异步通道。
    异步服务器套接字通道是通过调用此类的open方法创建的。新创建的异步服务器套接字通道已打开,但尚未绑定。它可以绑定到本地地址,并配置为通过调用绑定方法来侦听连接。绑定后,accept方法用于启动对通道套接字的连接的接受。尝试在未绑定通道上调用accept方法将导致引发NotYetBoundException。
    这种类型的通道对于多个并发线程来说是安全的,尽管在任何时候最多只能有一个接受操作未完成。如果线程在前一个接受操作完成之前启动接受操作,则将引发AcceptPendingException。

    修饰符和返回值方法描述
    abstract Futureaccept()接受连接。
    abstract voidaccept(A attachment, CompletionHandler handler)接受连接。
    AsynchronousServerSocketChannelbind(SocketAddress local)将通道的套接字绑定到本地地址,并将套接字配置为侦听连接。
    abstract AsynchronousServerSocketChannelbind(SocketAddress local, int backlog)将通道的套接字绑定到本地地址,并将套接字配置为侦听连接。
    abstract SocketAddressgetLocalAddress()返回此通道套接字绑定到的套接字地址。
    static AsynchronousServerSocketChannelopen()打开异步服务器套接字通道。
    static AsynchronousServerSocketChannelopen(AsynchronousChannelGroup group)打开异步服务器套接字通道。
    AsynchronousChannelProviderprovider()返回创建此通道的提供程序。
    abstract AsynchronousServerSocketChannelsetOption(SocketOption name, T value)设置套接字选项的值。
    3.1.3 AsynchronousChannelGroup

    一组异步通道,用于资源共享。
    异步通道组封装了处理由绑定到该组的异步通道发起的I/O操作的完成所需的机制。一个组有一个关联的线程池,任务被提交到该线程池,以处理I/O事件,并分派给完成处理程序,完成处理程序使用在组中的通道上执行的异步操作的结果。除了处理I/O事件,池线程还可以执行支持异步I/O操作执行所需的其他任务。
    异步通道组是通过调用此处定义的withFixedThreadPool或withCachedThreadPool方法创建的。通过在构建频道时指定组,频道被绑定到组。关联的线程池归该组所有;组的终止导致相关线程池的关闭。
    除了显式创建的组之外,Java虚拟机还维护一个自动构建的系统范围的默认组。在构建时未指定组的异步通道将绑定到默认组。默认组有一个关联的线程池,可以根据需要创建新线程。默认组可以通过下表中定义的系统属性进行配置。如果未配置默认组的ThreadFactory,则默认组的池线程为守护线程。

    以下系统属性指示线程池设置:

    • java.nio.channels.DefaultThreadPool.threadFactory
    • java.nio.channels.DefaultThreadPool.initialSize
    修饰符和返回值方法描述
    abstract booleanawaitTermination(long timeout, TimeUnit unit)等待当前group终止
    abstract booleanisShutdown()abstract boolean isShutdown()
    abstract booleanisTerminated()指示此组是否已终止。
    AsynchronousChannelProviderprovider()返回创建此频道组的提供程序。
    abstract voidshutdown()启动组的有序关闭。
    abstract voidshutdownNow()关闭组并关闭组中所有打开的频道。
    static AsynchronousChannelGroupwithCachedThreadPool(ExecutorService executor, int initialSize)使用给定的线程池创建异步通道组,根据需要创建新线程。
    static AsynchronousChannelGroupwithFixedThreadPool(int nThreads, ThreadFactory threadFactory)创建具有固定线程池的异步通道组。
    static AsynchronousChannelGroupwithThreadPool(ExecutorService executor)使用给定的线程池创建异步通道组。
    3.1.4 AsynchronousChannelProvider

    异步通道的服务提供程序类。
    异步通道提供程序都是此抽象类的一个具体子类,它有一个零参数构造函数,并实现下面指定的抽象方法。
    给定的Java虚拟机调用维护一个系统范围的默认提供者实例,该实例由提供者方法返回。该方法的第一次调用将定位下面指定的默认提供程序。
    此类中的所有方法都可以安全地供多个并发线程使用。

    修饰符和返回值方法描述
    abstract AsynchronousChannelGroupopenAsynchronousChannelGroup(ExecutorService executor, int initialSize)使用给定的线程池构造一个新的异步通道组。
    abstract AsynchronousChannelGroupopenAsynchronousChannelGroup(int nThreads, ThreadFactory threadFactory)使用固定线程池构造新的异步通道组。
    abstract AsynchronousServerSocketChannelopenAsynchronousServerSocketChannel(AsynchronousChannelGroup group)打开异步服务器套接字通道。
    abstract AsynchronousSocketChannelopenAsynchronousSocketChannel(AsynchronousChannelGroup group)打开异步套接字通道。
    static AsynchronousChannelProviderprovider()返回此Java虚拟机调用的系统范围默认异步通道提供程序。
    3.1.5 CompletionHandler

    用于消耗异步I/O操作结果的处理程序。
    此包中定义的异步通道允许指定完成处理程序来使用异步操作的结果。当I/O操作成功完成时,将调用完成的方法。如果I/O操作失败,则调用失败的方法。这些方法的实现应该及时完成,以避免调用线程被分派给其他完成处理程序。

    修饰符和返回值方法描述
    voidcompleted(V result, A attachment)操作完成时调用。
    voidfailed(Throwable exc, A attachment)操作失败时调用。
    3.1.6 AsynchronousFileChannel

    用于读取、写入和操作文件的异步通道。
    当通过调用该类定义的打开方法之一打开文件时,将创建异步文件通道。该文件包含一个可变长度的字节序列,可以读取和写入,并且可以查询其当前大小。当写入的字节超过其当前大小时,文件的大小会增加;文件被截断时,其大小会减小。
    异步文件通道在文件中没有当前位置。相反,文件位置被指定给启动异步操作的每个读写方法。CompletionHandler被指定为参数,并被调用以使用I/O操作的结果。该类还定义了启动异步操作的读写方法,并返回一个Future来表示操作的挂起结果。Future可用于检查操作是否已完成,等待其完成,并检索结果。
    除了读和写操作,此类还定义了以下操作:
    对文件进行的更新可能会强制输出到底层存储设备,以确保在系统崩溃时数据不会丢失。
    文件的某个区域可能被锁定,以防其他程序访问。
    AsynchronousFileChannel与一个线程池相关联,任务被提交到该线程池以处理I/O事件,并分派给使用通道上I/O操作结果的完成处理程序。在通道上启动的I/O操作的完成处理程序保证由线程池中的一个线程调用(这确保完成处理程序由具有预期标识的线程运行)。如果I/O操作立即完成,并且发起线程本身是线程池中的线程,则完成处理程序可以由发起线程直接调用。如果创建AsynchronousFileChannel时未指定线程池,则该通道与系统相关的默认线程池相关联,该线程池可以与其他通道共享。默认线程池由AsynchronousChannelGroup类定义的系统属性配置。
    这种类型的通道对于多个并发线程来说是安全的。根据Channel接口的规定,可以随时调用close方法。这将导致通道上所有未完成的异步操作完成,但出现异常AsynchronousCloseException。多个读写操作可能同时处于未完成状态。当多个读写操作未完成时,则不指定I/O操作的顺序以及调用完成处理程序的顺序;特别是,它们不能保证按照操作启动的顺序执行。读取或写入时使用的ByteBuffers对于多个并发I/O操作来说是不安全的。此外,在I/O操作启动后,应注意确保在操作完成之前不访问缓冲区。
    与FileChannel一样,此类实例提供的文件视图保证与同一程序中其他实例提供的同一文件的其他视图一致。然而,由于底层操作系统执行的缓存和网络文件系统协议引起的延迟,此类实例提供的视图可能与其他并发运行程序看到的视图一致,也可能不一致。无论这些其他程序是用什么语言编写的,无论它们是在同一台机器上还是在其他机器上运行,这都是正确的。任何此类不一致的确切性质都取决于系统,因此不明确。

    3.2 Java NIO Proactor模式

    Java Proactor模式就是对异步I/O的封装。proactor网络上的翻译是“前摄器”,可能叫“主动器”更合适一些,因为Proactor属于内核模块,用来主动通知应用程序异步I/O已经完成。相对的,Reactor属于应用程序模块,被动响应事件。
    Proactor模式基于“已完成”的I/O事件,操作系统处理完I/O事件,通知应用程序;Reactor模式基于“待完成”的I/O事件,操作系统通知应用程序来处理。

    1. Proactor Initiator 负责创建 Proactor 和 Handler 对象,并将 Proactor 和 Handler 都通过 Asynchronous Operation Processor 注册到内核;
    2. Asynchronous Operation Processor 负责处理注册请求,并处理 I/O 操作;
    3. Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor;Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理;
    4. Handler 完成业务处理。
      Proactor模式

    3.3 示例

    Java NIO 基于AIO的服务端,客户端通信(非阻塞)

    4 附录

    4.1 链接文件

    什么是链接文件
    链接文件是指文件在另一个目录中的一个别名。创建一个链接文件就是在其他目录中为当前的文件创建一个目录项(包括文件名和inode号),通过链接文件可以访问源文件。
    链接文件可以分为硬链接(had link)符号链接(Symbolic link)(又俗称软链接soft link)。
    硬链接相当于复制源文件,与复制的区别就是不会创建源数据块的拷贝,源链接文件的inode和数据块与源文件相同;复制完全会创建一个新的独立拷贝。
    软链接相当与创建了一个源文件的快捷方式,是一个不同类型的新文件,自然inode也不相同。

    区别硬链接符号链接
    创建命令ln <原始文件> <链接文件>ln -s <原始文件> <链接文件>
    跨越不同文件系统不可以可以
    创建目录的链接不可以可以
    与源文件的inode是否相同相同不同
    与源文件的数据块是否相同相同无数据块
    文件的链接数是否会增加不会
    删除源文件即使原始文件被移除,链接文件也不受影响删除了源文件或目录时,只删除了数据不会删除链接,一旦以同样文件名创建了源文件,链接将继续指向该文件的新数据
    如果源文件不存在不可以可对不存在的文件或目录创建软链接,当链接到的文件存在时,即可直接通过软链接访问

    4.2 FollowSymLinks

    什么是FollowSymLinks?
    FollowSymLinks选项就是告诉服务器在查找文件时遵循符号链接,但是它到底是什么意思呢?
    符号链接类似于Windows的快捷方式。网站通常被用来展示一些图片和内容。这些图片和内容的真实地址可能不是你访问的那个地址。假设你的浏览器正在浏览一个网站的内容,然后你点击鼠标右键查看网页源代码,你会发现网页上显示的图片是这样的IMG SRC=“/system/files/images/image.png”。如果你用浏览器打开这个链接,浏览器就会把这个图片展示出来。但是,如果你登录服务器,根据/system/files/images/这个地址查找图片,最终没有找到这个图片。实际上这个图片没有放在/system/files/images/文件夹下,而是放在了/pictures文件夹下。
    当你访问网站,浏览器通过/system/files/images/在服务器上查找图片时,服务器怎么知道要把/pictures文件夹下的image.png返给浏览器呢?那么这个所谓的符号链接就是负责做这件事的。在系统中,一个动态链接告诉服务器:如果有人请求/system/files/images/image.png,那么把/pictures/image.png返给他。

    FollowSymLinks在这里扮演什么角色呢?
    FollowSymLinks与服务器的安全相关。在处理服务器时不能让事情模糊不清,你要明确谁对什么有权限。FollowSymLinks指令告诉服务器要不要遵循符号链接。也就是说,在上面的例子中,如果FollowSymLinks被禁止,根据其他配置项,浏览器请求/system/files/images/image.png可能会得到403或404错误。

    4.3 Posix文件权限比特位

    Posix文件权限比特位总共有10位,例如权限“文件所有者可读&可写&可执行,同组用户可读&可执行,其他用户可读&可执行”,用字符可表示为“-rwxr-xr-x”,用二进制可表示为“-111101101”,按3位一个整数可表示为“-755”。
    其中各个比特位代表的意义如下表:

    比特位序号含义备选值
    1文件类型-:文件
    d:目录
    2文件所有者是否可读r:可读
    -:不可读
    3文件所有者是否可写w:可写
    -:不可写
    4文件所有者是否可执行x:可执行
    -:不可执行
    5同组用户是否可读r:可读
    -:不可读
    6同组用户是否可写w:可写
    -:不可写
    7同组用户是否可执行x:可执行
    -:不可执行
    8其他用户是否可读r:可读
    -:不可读
    9其他用户是否可写w:可写
    -:不可写
    10其他用户是否可执行x:可执行
    -:不可执行

    4.4 内存映射文件

    内存映射文件,是由一个文件到一块内存的映射。Win32提供了允许应用程序把文件映射到一个进程的函数 (CreateFileMapping)。内存映射文件与虚拟内存有些类似,通过内存映射文件可以保留一个地址空间的区域,同时将物理存储器提交给此区域,只是内存文件映射的物理存储器来自一个已经存在于磁盘上的文件,而非系统的页文件,而且在对该文件进行操作之前必须首先对文件进行映射,就如同将整个文件从磁盘加载到内存。

    • 物理内存
      就是内存条的内存空间。
    • 虛拟内存 是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如 Windows家族的“虚拟内存”; Linux的“交换空间”等。

    有了虚拟内存技术,进程可用内存空间不会受到物理内存大小的限制,将大文件放入内存才成为可能,这也是内存映射文件的基础。
    由此可以看出,使用内存映射文件处理存储于磁盘上的文件时,将不必再对文件执行I/O操作,这意味着在对文件进行处理时将不必再为文件申请并分配缓存,所有的文件缓存操作均由系统直接管理,由于取消了将文件数据加载到内存、数据从内存到文件的回写以及释放内存块等步骤,使得内存映射文件在处理大数据量的文件时能起到相当重要的作用。另外,实际工程中的系统往往需要在多个进程之间共享数据,如果数据量小,处理方法是灵活多变的,如果共享数据容量巨大,那么就需要借助于内存映射文件来进行。实际上,内存映射文件正是解决本地多个进程间数据共享的最有效方法。
    内存映射文件并不是简单的文件I/O操作,实际用到了Windows的核心编程技术–内存管理。所以,如果想对内存映射文件有更深刻的认识,必须对Windows操作系统的内存管理机制有清楚的认识,下面给出使用内存映射文件的一般方法:
    首先要通过CreateFile()函数来创建或打开一个文件内核对象,这个对象标识了磁盘上将要用作内存映射文件的文件。在用CreateFile()将文件映像在物理存储器的位置通告给操作系统后,只指定了映像文件的路径,映像的长度还没有指定。为了指定文件映射对象需要多大的物理存储空间还需要通过CreateFileMapping()函数来创建一个文件映射内核对象以告诉系统文件的尺寸以及访问文件的方式。在创建了文件映射对象后,还必须为文件数据保留一个地址空间区域,并把文件数据作为映射到该区域的物理存储器进行提交。由MapViewOfFile()函数负责通过系统的管理而将文件映射对象的全部或部分映射到进程地址空间。此时,对内存映射文件的使用和处理同通常加载到内存中的文件数据的处理方式基本一样,在完成了对内存映射文件的使用时,还要通过一系列的操作完成对其的清除和使用过资源的释放。这部分相对比较简单,可以通过UnmapViewOfFile()完成从进程的地址空间撤消文件数据的映像、通过CloseHandle()关闭前面创建的文件映射对象和文件对象。

  • 相关阅读:
    Microsoft Graph
    C语言中指针的介绍
    请你详细说说类加载流程,类加载机制及自定义类加载器
    JAVA高级(一)
    经典/最新计算机视觉论文及代码推荐
    D. Binary String To Subsequences
    一文带你弄懂 JVM 三色标记算法!
    Chapter 11 Working with Dates and Times
    17、Flink 之Table API: Table API 支持的操作(1)
    vm虚拟机保护技术简介&EzMachine例题-vm逆向分析
  • 原文地址:https://blog.csdn.net/yunyun1886358/article/details/127778760