NIO:文件通道与网络通道讲解

  • Post author:
  • Post category:其他


通道是java.nio的一个创新,既不是一个扩展,也不是一个增强,而是一种全新的IO交流方式,可以提供与IO服务的直接连接,

Channel用于在字节缓冲区和通道另一侧的实体(文件或套接字)之间有效的传输数据

在这里插入图片描述



类库介绍

顶层接口Channel:

在这里插入图片描述

子接口ReadableByteChannel,定义了一个通道可以读的行为

子接口WritableByteChannel,定义了一个通道可以写的行为:

在这里插入图片描述

所以,当一个通道实现了ReadableChannel接口,那么该通道就可以读,当一个通道实现了WritableChannel,那么该通道就可以写,如果同时实现了两个接口,则该通道就既可以读,也可以写,是双向的

在这里插入图片描述

可以看到ByteChannel引申了可读和可写的接口,这是一种便捷的技巧,后面实现了ByteChannel的类就同时具有了读和写的能力,但是这样确是没有意义的,为什么这样说,对于网络通道来说,双向是正常的,但是对于

文件通道来说,有些就是单向

的,比如FileInputStream获得的文件通道,就只具有可读的特性,

虽然也有write方法,这是因为ByteChannel接口的缘故,但是调用该方法会抛出相应的异常

在这里插入图片描述

Scatter和Gather是定义了对缓冲区数组进行操作,这里了解即可,并不常用:

在这里插入图片描述

文件通道:

在这里插入图片描述

网络通道:

在这里插入图片描述



文件通道

从上面的类图可以看出,文件通道主要的类只有一个,那就是

FileChannel

,刚才在上面也提到了通过单向文件流如FileInputStream获得的FileChannel,是不能写的,那么首先先来学习一下如何获得通道,获得通道并不能直接去new一个FileChannle,而是通过下面这三种流调用getChannel()方法来获得,这三种流:FileInputStream,FileOutputStream,RandomAccessFile

demo:

public class FileChannelDemo {
    public static void main(String[] args) throws FileNotFoundException {
        FileInputStream file = new FileInputStream("e:\\sun.jpg");
        FileOutputStream file2 = new FileOutputStream("e:\\sun.jpg");
        RandomAccessFile file3 = new RandomAccessFile("e:\\sun.jpg", "rw");
        FileChannel channel = file.getChannel();
        FileChannel channel2 = file2.getChannel();
        FileChannel channel3 = file3.getChannel();
    }
}

演示一下单向通道虽然同时有read和write方法,但是错误的调用会抛出相应的异常,下面的demo我通过输出流打开了一个通道,调用了read方法,编译没有报错,但是运行出错,错误的含义很明确:

在这里插入图片描述

得到文件通道后,就可以通过缓冲区来输入输出二进制数据了,这里通过一个复制文件的例子来实例一下,演示用法

public class FileChannelDemo {
    public static void main(String[] args) throws IOException {
        //1.拿到两个流
        File file = new File("e:\\sun.jpg");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileOutputStream fileOutputStream = new FileOutputStream("e:\\sun2.jpg");
        //2.拿到两个通道
        FileChannel inChannel = fileInputStream.getChannel();
        FileChannel outChannel = fileOutputStream.getChannel();
        //3.按照文件大小开辟缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate((int)file.length());
        //4.读取到缓冲区中,再从缓冲区中写到文件通道中,完成复制
        inChannel.read(byteBuffer);
        byteBuffer.flip();
        outChannel.write(byteBuffer);
        //5.关闭通道
        inChannel.close();
        outChannel.close();
    }
}

nio还提供了直接从通道到通道的方法,所以上面的例子还可以这样写

public class FileChannelDemo3 {
    public static void main(String[] args) throws IOException {
        //1.拿到两个流
        FileInputStream fileInputStream = new FileInputStream("e:\\sun.jpg");
        FileOutputStream fileOutputStream = new FileOutputStream("e:\\sun2.jpg");
        //2.通过流拿到通道
        FileChannel inChannel = fileInputStream.getChannel();
        FileChannel outChannel = fileOutputStream.getChannel();
        //3.通道之间的传输
        outChannel.transferFrom(inChannel,0, inChannel.size());
        //4.关闭通道
        inChannel.close();
        outChannel.close();
    }
}


channel to channel

就是FileChannel类添加的一些优化方法,以提高传输过程中的效率问题,共提供了两个方法:transferTo和transferFrom

在这里插入图片描述

这两个方法允许将一个通道交叉连接到另一个通道,直接绕过了缓冲区,操作系统提供了本地支持的话,传输大量数据时,这会是一个巨大的帮助。需要注意的是,这两个方法

不能用于两个网络通道之间

,可以是两个文件通道,但是

传参可以是网络通道

,也就是说对于transferFrom,把网络通道上的数据直接交给一个输出文件流这种操作是可以的。请求的数据传输将从 position参数指定的位置开始,传输的字节数不超过count参数的值,实际传输的字节数可能会少于请求的字节数

对于传输数据来源是一个文件的 transferTo( )方法,如果position + count的值大于文件 的 size 值,传输会在文件尾的位置终止。假如传输的目的地是一个非阻塞模式的 socket 通道,那么 当发送队列(send queue)满了之后传输就可能终止,并且如果输出队列(output queue)已满的话 可能不会发送任何数据。类似地,对于 transferFrom( )方法:如果来源src是另外一个 FileChannel 并且已经到达文件尾,那么传输将提早终止;如果来源src是一个非阻塞 socket 通道,只有当前 处于队列中的数据才会被传输(可能没有数据)。由于网络数据传输的非确定性,阻塞模式的 socket 也可能会执行部分传输,这取决于操作系统。许多通道实现都是提供它们当前队列中已有的 数据而不是等待您请求的全部数据都准备好。



网络通道

网络通道的类图在上面也已经给出了,网络通道主要有三个:

DatagramChannel



SocketChannel



ServerSocketChannel

,这三个通道都是间接继承自

SelectableChannel

,该抽象类定义了该通道阻塞相关的方法

在这里插入图片描述



ServerSocketChannel

先来看看ServerSocketChannel的api

在这里插入图片描述

使用静态的open方法打开该通道,然后需要为该通道绑定端口号,由于该通道没有bind方法,所以需要调用socket方法,先拿到对应的socket,然后利用socket的bind方法,进行绑定端口。通过accept方法来监听连接,通过一个实例来看一下:

public class ServerSocketChannelDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.创建一个ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2.绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(8888));
        //3.设置非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //4.服务器开始工作
        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();
            if(socketChannel == null) {
                System.out.println("服务器先去做其他事情了...");
                Thread.sleep(1000);
            } else {
                System.out.println("有连接接入,欢迎:"+socketChannel.getRemoteAddress());
                //5.包装要发送的数据
                String msg = "Hello,This is server";
                ByteBuffer buffer = ByteBuffer.allocate(msg.length());
                buffer.put(msg.getBytes());
                buffer.flip();
                socketChannel.write(buffer);
            }
        }
    }
}


SocketChanel

ServerSocketChannel是用来建立连接的,那么SocketChannel就是用来通信的,也是用的最多的通道,在建立连接时,accpet成功后,也即是建立连接成功后就会返回一个SocketChannel,SocketChannel的api:

在这里插入图片描述

其他的方法类比ServerSocketChannl,需要说明的方法是connect方法和finishConnect方法,规定只有在使用了connect方法之后,才可以使用finishConnect方法。由于是非阻塞的,所以connect方法会立即返回,如果返回false,那么后续的建立连接就需要通过finishConnect方法来进行,可以看一下代码示例

同样由于是非阻塞模式,所以在read的时候需要加上while循环,让read操作不断的去读,这样才能避免服务端发送消息后,客户端没有读到的结果,具体的代码示例:

package SocketChannel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/**
 * 
 */
public class SocketChannelDemo {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.创建SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        //2.设置非阻塞模式
        socketChannel.configureBlocking(false);
        //3.连接服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
        //4.如果没连接上,先去做其他事情
        while (!socketChannel.finishConnect()) {
            System.out.println("服务器没有相应,我想去做其他事情了");
            Thread.sleep(1000);
        }
        //5.连接上了,准备缓冲区,接收数据
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (socketChannel.read(buffer) == 0) {
            Thread.sleep(200);
            System.out.println("通道数据没准备好,先去做其他事情");
        }
        buffer.flip();
        byte[] array = buffer.array();
        System.out.println(new String(array));
        buffer.clear();
        socketChannel.close();
    }
}



版权声明:本文为weixin_42220532原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。