zoukankan      html  css  js  c++  java
  • 浅析Java Nio 之通道

    浅析Java Nio 之通道


    通道

    通道是用于字节缓冲区和位于通道另一边的数据实体之间执行传输数据。
    channel
    顶层Channel接口提供了isOpen方法判断通道是否打开和close方法关闭一个打开的通道,InterruptibleChannel是一个标志接口,当通道使用时可以标志该通道可以被中断的。WritableByteChannel和ReadableByteChannel表明通道只能字节缓冲区上操作。

    • 打开通道
      IO主要分为File IO和Steam IO,对应通道分为Filechannel和SocketChannel(Socketchannel、ServerSocketChannel和DatagramChannel)。
      通道可以多种方式创建,SocketChannel提供直接创建Socket通道的工厂方法(SocketChannel.open),FileChannel只能通过一个打开的RandomAccessFile、FileInputSteam或者FileOutputSteam对象调用getChannel方法获取,不可以直接创建。
    • 使用通道
      通道可以将数据传输给Buffer或者从Buffer获取数据进行传输。通道可以是单向或者双向的,通过实现WritableByteChannel或者ReadableByteChannel接口来传输数据,同时实现两个接口,那么就是双向的,否则就是单向的。ByteChannel同时继承了WritableByteChannel和ReadableByteChannel,因此所有实现了ByteChannel接口的类都是双向通道。
      FileChannel和SocketChannel都是双向通道,但是FileChannel可以用不同的权限进行访问,如果是通过FileInputSteam.getChannel方法得到的FileChannel是只读,由于FileInputSteam是通过read-only权限来获取文件句柄的,如果对该通道调用write方法会抛出NonWritableChannelException异常。ByteChannel的read和write方法返回已传输的字节数,缓冲区position也会前移对应的传输字节数。
      通道可以使用blocking和nonblocking模式运行,nonblocking模式的通道永远不会让调用的线程休眠。因此,请求的操作要么立即完成,要么返回一个结果表明没有进行任何的操作。只有面向stream的通道才可以使用nonblocking模式。nonblocking和Selector组合使用可以实现多路复用IO。
    • 关闭通道
      通道是不可以重复利用的,一个打开的通道表明正在与一个特定的IO服务连接,当通道关闭的时候,连接也会关闭,不能再次调用。当一个通道close之后,再次去调用IO操作会导致抛出ClosedChannelException异常的。当通道关闭之后,再次调用close方法,没有任何影响,只是立即返回。close方法可以在任何时候进行调用,如果一些其他线程也调用close方法,其他的线程会被阻塞,直到第一个调用close方法的完成,然后其他线程会返回没有任何影响。通道实现了InterruptibleChannel接口,它是asynchronously closeable的,如果一个线程阻塞在IO操作在interruptible channel,然后其他线程可能会调用该通道的close方法,这样会导致阻塞线程抛出AsynchronousCloseException异常。同样该通道也是interruptible,如果一个线程在通道上阻塞,其他线程调用该线程的interrupt方法,这样会导致通道被关闭,阻塞线程会抛出ClosedByInterruptException异常和中断标志位也会被设置。如果线程的中断标志位已经设置和线程试图去调用通道的阻塞IO操作,通道会立即关闭和该线程会抛出ClosedByInterruptException异常,该中断标志位仍然设置。

    Scatter和Gather

    Scatter和Gather可以在多个缓冲区上执行IO操作。write操作是在多个缓冲区按顺序抽取并沿着通道传输。read操作是将从通道读取的数据按顺序传输到多个缓冲区。

    文件通道

    文件通道是阻塞式的。对于文件IO,在于asynchronous IO,他能够允许一个进程从操作系统请求一个或多个IO操作而不必等待操作完成,而是直接返回。等IO操作完成之后,进程会收到IO操作完成通知。
    FileChannel不能直接创建,需要通过打开的File对象调用getChannel()方法获取。getChannel方法会返回一个连接到跟文件具有相同访问权限的Channel对象。
    FileChannel是线程安全的,多个线程可以在同一个实例上并发调用而不会引起问题,影响通道position和文件大小的操作是单线程的,一个线程在执行操作的时候,其他线程必须等待。

    • 访问文件
      每一个FileChannel对象对应一个文件描述符。FileChannel有一个file position ,position决定在文件哪里进行数据读写。当字节被write和read时,文件position会被更新。如果position值达到文件大小的值,read()会返回-1,write方法position前进到超过文件大小的值。文件会扩展以容纳新写入的字节。truncate方法去掉指定的newSize值之外的数据,如果newSize的值大于原size,文件不会修改,否则超过newSize的所有字节会被丢弃,文件的position会设置为newSize值。force会告诉通道强制将所有待定的修改同步到磁盘。所有现代文件系统缓存数据和延迟磁盘文件更新来提高性能。
    • 文件锁定
      文件锁定依赖本地的操作系统实现,文件锁可以是共享的或者是独占的。FileChannel实现的文件锁定是对文件加锁而不是通道或者线程。如果一个线程在某个文件上获得一个独占锁,第二个线程利用一个单独打开的通道来请求文件独占锁,请求会被通过,但如果是两个线程是运行在不同的JVM上,第二个线程会被阻塞,因为,锁是操作系统或者文件系统在进程级别上来判优的。锁是与文件关联,而不是与文件句柄或者通道关联。如果是向在多线程并发下进行访问,应该是内存映射文件锁定。lock方法可以指定锁定区域开始的position和size(不能为负值),也可以设置为共享还是独占的,请求共享锁的话,必须要有读权限,独占锁的话则需要有写权限。

    内存映射文件

    FileChannel的map方法可以在一个打开的文件和一个特殊类型的ByteBuffer之间建立一个虚拟内存映射
    (返回一个MappedByteBuffer对象)。
    get方法会从磁盘文件中获取文件当前的数据内容,如果建立映射之后文件被其他进程修改,对该进程也是可见的。
    put方法会更新磁盘上的文件,对文件的修改对其他进程也是可见的。
    操作系统的虚拟内存会自动缓存内存页,不会消耗Java虚拟机堆内存。当一个内存页生效之后,能够以硬件速度进行访问而不需要再次调用系统命令来获取数据。
    内存映射文件也可以可读可写的,如果通道是READ_ONLY权限访问,但是请求MapMode.READ_WRITE模式的话,会抛出NonWritableChannelException,如果没有read权限的通道,却去请求MapMode.READ_ONLY映射模式,会抛出NonReadableChannelException,不过如果是read/write权限的通道去请求READ_ONLY映射模式是可以的。MapMode.PRIVATE是写时拷贝的映射,put方法所做的任何修改都会产生一个临时的私有数据拷贝,不会对底层文件进行修改,一旦缓冲区被GC,所做的修改都会丢失,可以防止底层文件的修改。
    load方法会加载整个文件常驻内存实现最缓冲区访问延迟。MapperByteBuffer.force会将所有映射缓冲区的修改刷新到底层文件中,但如果是以MapMode.READ_ONLY或MapMode.PRIVATE模式建立的话,force不会起作用。
    FileChannel还提供了极快的Channel-to-Channel传输方法
    transferTo(long position, long count,WritableByteChannel target)
    transferFrom(ReadableByteChannel src,long position, long count)
    他们允许将一个通道交叉连接到另一个通道,不要中间缓冲区来传输数据。transferTo会从读取原通道position开始
    count字节的数据传输到指定target通道,如果position+count的值大于原通道的size,会在文件结尾结束,该操作不会修改原通道的position。transferFrom会从source通道中读取从position开始count字节,如果src到达文件尾,传输可能会提前结束,通道的position会前进它实际所读取的字节数。如果在传输中出现错误,会抛出IOException。

    Socket通道

    SocketChannel和DatagramChannel实现了WritableByteChannel和ReadableByteChannel接口而ServerSocketChannel没有实现。ServerSocketChannel负责监听传入的连接和创建新的SocketChannel对象,本身不传输数据。
    Socket通道类在实例化的时候会创建一个对应的Socket对象,通道将协议操作委托给Socket。

    • 非阻塞模式
      Socket通道可以在非阻塞模式下运行,可以通过configureBlocking方法设置阻塞模式,true为阻塞,false为非阻塞。使用非阻塞模式,可以同时管理多个Socket通道,减少了上下文切换开销。blockingLock方法会返回一个非透明的对应引用,只有持有该对象锁的线程才能修改通道的阻塞模式。
    • ServerSocketChannel
      ServerSocketChannel是一个基于通道的socket监听器,可以通过静态的open工厂方法创建一个新的ServerSocketChannel对象和返回一个对应的没绑定端口的ServerSocket关联的通道,可以通过Socket方法来获得对应ServerSocket,由于ServerSocketChannel没有bind方法,只有获取ServerSocket来bind端口。绑定端口之后,可以通过ServerSocket调用accept方法来阻塞的获取Socket对象,也可以通过ServerSocketChannel上调用accept方法返回SocketChannel对象,可以在非阻塞模式运行。
    • SocketChannel
      SocketChannel和Socket都是封装点对点和有序的网络连接,模拟连接导向的流协议(TCP/IP)。SocketChannel可以作为客户端向一个监听服务器发起连接,直到连接成功,才能收到数据并且从连接到的地址接收。SocketChannel是通过静态工厂方法open方法创建SocketChannel对象,SocketChannel可以调用Socket返回对等Socket对象。新创建的SocketChannel对象没有连接,如果调用IO操作对抛出NotYetConnectedException异常。SocketChannel可以调用connect方法(默认处于阻塞模式)进行连接,直到关闭。如果是非阻塞模式进行connect,它会向指定的地址进行连接并立即返回,如果是返回true,说明连接立即建立,返回false的话,则连接没有建立成功,它会并发的继续请求连接。
      通过调用finishConnect来完成连接过程,在一个非阻塞模式的SocketChannel对象上调用finishConnect方法。
      • 如果没有调用connect方法,会抛出NoConnectionPendingException异常
      • 如果正在进行连接,finishConnect会立即返回false值
      • 如果非阻塞模式下下调用connect方法后,SocketChannel被切换回阻塞模式,调用线程会阻塞到连接建立完成,然后返回true。
      • 在第一次调用connect,最后一次调用finishConnect后,连接建立过程已经完成。那么SocketChannel对象内部状态会被更新为已连接状态,然后返回true。
      • 如果是已经建立连接,直接返回true。
        如果通道正在连接,isConnectPending方法会返回true。
    • DatagramChannel
      DatagramChannel是模拟包导向的无连接协议(UDP/IP),通过调用静态的open方法来创建实例,可以通过socket方法来获取对等的DatagramSocket对象,DatagramChannel对象可以充当服务器,也可以当客户端,它可以发送单独的数据报给不同的目的地址,也可以接收来自任意地址的数据报。每个到达的数据报会包含它的源信息。如果没有绑定特定端口,会在底层socket创建时候,会生成动态端口分配。通道通过receive和send方法来实现接收和发送数据。receive会将下次传入的数据报数据净荷复制到准备好的ByteBuffer并返回SocketAddress对象指出数据来源。如果通道阻塞模式,receive会无限休眠直到有包到达。如果是非阻塞模式,并没有可接收的包会返回null。send会发送ByteBuffer对象的内容到给定SocketAddress对象目的地址和端口,内容范围为从当前position开始末尾处。如果通道是阻塞的话,线程可能会休眠直到数据报加入到传输队列,如果是非阻塞的话,要么返回0,要么返回ByteBuffer的字节数,发送数据报是一个全有或者全无的操作,如果传输队列没有足够的空间去发送数据报,就不会有数据传输过去。
      DatagramChannel是不可靠的,它不能保证数据传输能准确发送到目的地,send返回非0值只能表明数据报被成功加到本地网络的传输队列。如果数据报比较达,可能会导致分片,加大数据报在传输过程中丢失的功能。数据报碎片会在目的地重新排序组合,但是只要有一个碎片没有被接收,整个数据报会被丢弃。
      如果DatagramChannel处于已连接状态,它会将所有除了它连接到的地址之外的数据报全部忽略,避免了接收、检测数据报的麻烦。处于已连接状态,不能发送数据报到connect指定的目的地址之外的地址,否则会抛出SecurityException。不过DatagramChannel可以任意次数的进行连接和断开连接的操作,每次连接可以连接到不同的远程地址。如果没有连接,调用read和write方法会抛出NotYetConnectedException异常。

    管道

    管道是两个实体之间进行单向传输数据的导管。管道可以通过Pipe的静态工厂方法open来创建,Pipe定义了两个嵌套通道类来实现管路,SourceChannel管道负责读的一端和sinkChannel管道负责写的一端,他们是在管道创建的时候实例化的,Pipe可以通过source和sink来获取。管道可以用来在同一个JAVA虚拟机内部传输数据。

    管道工具类

    java.nio.channels.Channels定义了几个静态工厂方法操作流和读写器(Reader/Writer)。

    • newChannel(InputStream)方法返回一个从给定输入流读取数据的通道
    • newChannel(OutputStream)方法返回一个向给定输出流写入数据的通道
    • newInputStream(ReadableByteChannel) 返回一个将从给定通道读取字节的流。
    • newOutputStream(WritableByteChannel) 返回一个将从给定通道写入数据的流
    • newReader(ReadableByteChannel,CharestDecoder,minBufferCap) 返回一个Reader,从给定通道读取数据并根据给定的CharestDecoder进行解码。
    • newReader(ReadableByteChannel,charestName) 返回一个Reader,会从给定的通道读取数据并根据给定的字符集对字节进行解码
    • newWriter(WritableByteChannel,CharestEncoder,minBufferCap) 返回一个Writer,会对给定的CodeEncoder对字节编码写入给定的通道。
    • newWriter(WritableByteChannel,charestName) 返回一个Writer,会对给定的字符集名称对字节进行编码写入到给定的通道中。
  • 相关阅读:
    hdu 5170 GTY's math problem(水,,数学,,)
    hdu 5178 pairs(BC第一题,,方法不止一种,,我用lower_bound那种。。。)
    hdu 5179 beautiful number(构造,,,,)
    cf12E Start of the season(构造,,,)
    cf12D Ball(MAP,排序,贪心思想)
    cf 12C Fruits(贪心【简单数学】)
    cf 12B Correct Solution?(贪心)
    hdu 5171 GTY's birthday gift(数学,矩阵快速幂)
    hdu 5172 GTY's gay friends(线段树最值)
    cf 11D A Simple Task(状压DP)
  • 原文地址:https://www.cnblogs.com/xianyijun/p/5390515.html
Copyright © 2011-2022 走看看