• 通道(Channel)


    层次结构图

    从上图可以看出,Channel是所有类的父类,它定义了通道的基本操作。从Channel引申出的其他接口都是面向字节的子接口,这也意味着通道只能在字节缓冲区(ByteBuffer)上操作。

    Channel和Buffer

    Channel和Buffer之间的关系,如下图所示:

    Channel中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

    通道基础

      先来看一下基本的Channel接口,下面代码是Channel接口的完整源码:

     1 public interface Channel extends Closeable {
     2 
     3     /**
     4      * Tells whether or not this channel is open.  </p>
     5      *
     6      * @return <tt>true</tt> if, and only if, this channel is open
     7      */
     8     public boolean isOpen();
     9 
    10     /**
    11      * Closes this channel.
    12      *
    13      * <p> After a channel is closed, any further attempt to invoke I/O
    14      * operations upon it will cause a {@link ClosedChannelException} to be
    15      * thrown.
    16      *
    17      * <p> If this channel is already closed then invoking this method has no
    18      * effect.
    19      *
    20      * <p> This method may be invoked at any time.  If some other thread has
    21      * already invoked it, however, then another invocation will block until
    22      * the first invocation is complete, after which it will return without
    23      * effect. </p>
    24      *
    25      * @throws  IOException  If an I/O error occurs
    26      */
    27     public void close() throws IOException;
    28 
    29 }

      和缓冲区不同,Channel的API主要由接口来指定。不同的操作系统上通道的实现会有根本性的差异,所以通道API仅仅描述了可以做什么,因此很自然的,通道实现经常使用操作系统的本地代码,通道接口允许开发者以一种受控且可移植的方式来访问底层的I/O服务。从上面最基础的源代码可以看出,所有的通道只有两种共同的操作:检查一个通道是否打开 isOpen() 方法和关闭一个打开了的通道 close()方法,其余所有的东西都是那些实现Channel接口以及它的子接口的类。

      从最基础的Channel引申出的其他接口都是面向字节的子接口:在Channel的众多实现中,有一个SelectableChannel实现,其表示可被选择的通道。任何一个SelectableChannel都可以将自己注册到一个Selector中,这样,这个Channel就能被Selector(如果对Selector不了解,可看文章I/O 模型中Selector部分)所管理,而一个Selector可以管理多个SelectableChannel。当这个SelectableChannel的数据准备好时,Selector就会接到通知,去获取那些准备好的数据。而SocketChannel就是SelectableChannel的一种。

      同时,通道只能在字节缓冲区上操作。层次接口表明其他数据类型的通道也可以从Channel接口引申而来。这是一种很好的映射,不过非字节实现是不可能的,因为操作系统都是以字节的形式实现底层I/O接口的。

    Channel的主要实现

      FileChannel:用于读取、写入、映射和操作文件的通道。

      DatagramChannel:通过UDP读写网络中的数据通道。

      SocketChannel:通过tcp读写网络中的数据。

      ServerSocketChannel:可以监听新进来的tcp连接,对每一个连接都创建一个SocketChannel。

    获取通道的方式

      (1)通过getChannel()方法获取;

        前提是该类支持该方法。支持该类的方法有:FileInputStream、FileOutputStream、RandomAccessFile、Socket、ServerSocket、DatagramSocket。

      (2)通过静态方法open();

      (3)通过JDK1.7中的Files的newByteChannel()方法;

    Scatter(分散)/Gather(聚集)

      ❤ 分散:从Channel中读取是指在读操作时将读取的数据写入多个Buffer中,因此,Channel将从Channel中读取的数据分散到多个Buffer中;

      ❤ 聚集:指将数据写入到Channel中时将多个Buffer的数据写入同一个Channel,因此,Channel将多个Buffer中的数据聚集后发送到Channel。

    下面例子是分散:

    ByteBuffer header = ByteBuffer.allocate(128);
    ByteBuffer body   = ByteBuffer.allocate(1024);
    ByteBuffer[] bufferArray = { header, body };
    channel.read(bufferArray);

    read()方法按照buffer在数组中的顺序将从channel中读取的数据写入到buffer,当一个buffer被写满后,channel紧接着向另一个buffer中写。

     认识了解Channel

    看一下基本的Channel接口:

    ByteChannel:

    1 public interface ByteChannel
    2     extends ReadableByteChannel, WritableByteChannel
    3 {
    4 
    5 }

     WritableByteChannel:

    public interface WritableByteChannel
        extends Channel
    {
        public int write(ByteBuffer src) throws IOException;
    
    }

     ReadableByteChannel :

    public interface ReadableByteChannel extends Channel {
        public int read(ByteBuffer dst) throws IOException;
    
    }

       通道可以是单向的也可以是双向的。一个Channel类可能只实现了定义read() 方法的 ReadableByteChannel接口,而另一个Channel类也许只是实现了定义write() 方法的 WritableChannel接口,那么实现这两种接口之一的类都是单向的,就只能在一个方向上传输数据。如果一个类同时实现了这两个接口,那么这个类它就是双向的,可以进行双向的传输数据,就像上面的ByteChannel。

      通道不仅可以单向双向,也可以是阻塞和非阻塞的,非阻塞模式的通道永远不会让调用的线程休眠,请求的操作要么立即完成,要么返回一个结果表明未进行任何操作。只有面向流(stream-oriented)的通道,如sockets和pipes才能使用非阻塞模式(例如:从SelectableChannel引申而来的类可以和支持选择的选择器(Selector)一起使用)。

    文件通道

    由于我们在开发中文件I/O用到的地方比较多,所以对于文件通道必须要详细了解。

      通道是访问I/O服务的导管,I/O可以广义的分为两大类:File I/O和Stream I/O。那么相应的,通道也可以广义上的分外两种类型,分别是文件(File)Channel和套接字(Socket)通道。文件通道指的是 FileChannel,套接字通道则有三个,分别是SocketChannel、ServerSocketChannel和DatagramChannel。

      通道可以通过多种方式创建。Socket通道可以通过Socket通道的工厂方法直接创建,但是一个FileChannel对象却只能通过一个打开的RandomAccessFile、FileInputStream或FileOutputStream对象上调用getChannel()方法来获取,开发者不能直接创建一个FileChannel。

    接下来通过UML图来了解一下文件通道的类层次关系:

    文件通道总是阻塞的,因此不能被置于非阻塞模式下。

      前面提到过,FileChannel对象不能直接创建,一个FileChannel实例只能通过一个打开的File对象(RandomAccessFile、FileInputStream或FileOutputStream)上调用getChannel()方法获取,通过调用getChannel()方法会返回一个连接到相同文件的FileChannel对象且该FileChannel对象具有与File对象相同的访问权限,然后就可以使用通道对象来利用强大的FileChannel API了。

      FileChannel对象是线程安全的,多个线程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有操作都是多线程的。影响通道位置或者影响文件的操作都是单线程的,如果有一个线程已经在执行会影响通道位置或文件大小的操作,那么其他想尝试进行此类操作之一的线程必须等待,并发行为也会受到底层操作系统或者文件系统的影响。

    使用文化通道

      下面通过使用文件通道,读取文件中的数据:

     1 public static void main(String[] args) throws Exception{
     2         File file = new File("D:/ceshi.txt");
     3         FileInputStream fis = new FileInputStream(file);
     4         FileChannel fc = fis.getChannel();
     5         ByteBuffer bb = ByteBuffer.allocate(35);
     6         fc.read(bb);
     7         bb.flip();
     8         while (bb.hasRemaining())
     9         {
    10             System.out.print((char)bb.get());
    11         }
    12         bb.clear();
    13         fc.close();
    14     }

    输出结果

    Hello !
        FileChannel.

    这是最简单的操作,前面讲过文件通道必须通过一个打开的RandomAccessFile、FileInputStream、FileOutputStream获取到,因此这里使用FileInputStream来获取FileChannel。接着只要使用read方法将内容读取到缓冲区内即可,缓冲区内有了数据,就可以使用前文对于缓冲区的操作读取数据了。

    接着看一下使用文件通道写数据:

     1 public static void main(String[] args) throws Exception{
     2         File file = new File("D:/ceshi.txt");
     3         RandomAccessFile raf = new RandomAccessFile(file, "rw");
     4         FileChannel fc = raf.getChannel();
     5         ByteBuffer bb = ByteBuffer.allocate(60);
     6         String str = "Hello,FileChannel!";
     7         bb.put(str.getBytes());
     8         bb.flip();
     9         fc.write(bb);
    10         bb.clear();
    11         fc.close();
    12     }

    输出结果

    这里使用了RandomAccessFile去获取FileChannel,然后操作其实差不多,write方法写ByteBuffer中的内容至文件中,注意写之前还是要先把ByteBuffer给flip一下。

    可能有人觉得这种连续put的方法非常不方便,但是没有办法,之前已经提到过了:通道只能使用ByteBuffer

    参考:https://www.cnblogs.com/xrq730/p/5080503.html

    作者:Joe
    努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
  • 相关阅读:
    JSP error: Only a type can be imported
    关于JAVA插入Mysql数据库中文乱码问题解决方案
    MySQL SQL优化——分片搜索
    myeclipse 调试JSP页面
    jsp:usebean 常用注意事项
    spring XML格式
    VB 要求对象
    VB 对象变量或with块变量未设置
    Spring依赖注入
    Spring 读取XML配置文件的两种方式
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/9856659.html
Copyright © 2020-2023  润新知