一文带你深入详解NIO Channel类,建议收藏

Java NIO中一个socket连接使用一个Channel来表示。从更广泛的层面来说,一个通道可以表示一个底层的文件描述符,例如硬件设备、文件、网络连接等。

Java NIO的通道可以更加细化。例如,不同的网络传输协议类型,在Java中都有不同的NIO Channel实现。

其中最为重要的四种Channel实现:

  • FileChannel:文件通道,用于文件的数据读写。
  • SocketChannel:套接字通道,用于套接字TCP连接的数据读写。
  • ServerSocketChannel:服务器套接字通道(或服务器监听通道),允许我们监听TCP连接请求,为每个监听到的请求创建一个SocketChannel通道。
  • DatagramChannel:数据报通道,用于UDP的数据读写。

1 FileChannel

1.1 获取FileChannel

  1. 可以通过文件的输入流、输出流获取FileChannel
@Test
public void getChannel(){
    try {
        // 创建一个文件输出流
        FileOutputStream fos = new FileOutputStream("D:\\h.txt");
        FileChannel channel1 = fos.getChannel();
        // 创建一个文件输入流
        FileInputStream fins = new FileInputStream("D:\\h.txt");
        FileChannel channel2 = fins.getChannel();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    }
}
  1. 也可以通过RandomAccessFile(文件随机访问)类来获取FileChannel实例,代码如下:
//创建RandomAccessFile随机访问对象
RandomAccessFile rFile = new RandomAccessFile("filename.txt","rw");
//获取文件流的通道(可读可写)
FileChannel channel = rFile.getChannel();
复制代码

1.2 读取FileChannel

在大部分应用场景中,从通道读取数据都会调用通道的int read(ByteBuffer buf)方法,它把从通道读取的数据写入ByteBuffer缓冲区,并且返回读取的数据量。

public class FileChannelReadDemo01 {

  
    public static void main(String[] args) {
        testReadDataFromChannel();
    }

    public static void testReadDataFromChannel() {
        // 创建一个文件输出流
        RandomAccessFile  fos = null;
        FileChannel channel = null;
        try {
            fos = new RandomAccessFile ("D:\\h.txt","rw");
            channel = fos.getChannel();
            // 定义一个字节缓冲区域
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int length = -1;
            while ((length = channel.read(buffer)) != -1) {
                // 调用flip,buffer由写入变为读
                buffer.flip();
                byte[] array = buffer.array();
                System.out.print(new String(array, 0, length,"utf-8"));

            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            closeQuietly(fos);
            closeQuietly(channel);
        }

    }

    public static void closeQuietly(java.io.Closeable o) {
        if (null == o) {
            return;
        }
        try {
            o.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

1.3 写入FileChannel

把数据写入通道,在大部分应用场景中都会调用通道的write(ByteBuffer)方法,此方法的参数是一个ByteBuffer缓冲区实例,是待写数据的来源。

write(ByteBuffer)方法的作用是从ByteBuffer缓冲区中读取数据,然后写入通道自身,而返回值是写入成功的字节数。

对于入参buffer实例来说,需要从其中读取数据写入channel通道中,所以入参buffer必须处于读模式,不能处于写模式。

//如果buf处于写模式(如刚写完数据),需要翻转buf,使其变成读模式
buf.flip();
int outlength = 0;
//调用write()方法,将buf的数据写入通道
while ((outlength = outchannel.write(buf)) != 0) {
    System.out.println("写入的字节数:" + outlength);
}

1.4 关闭channel

当通道使用完成后,必须将其关闭。关闭非常简单,调用close()方法即可。

1.5 强制刷新到磁盘

在将缓冲区写入通道时,出于性能的原因,操作系统不可能每次都实时地将写入数据落地(或刷新)到磁盘,完成最终的数据保存。

在将缓冲区数据写入通道时,要保证数据能写入磁盘,可以在写入后调用一下FileChannel的force()方法。

1.6 【案例】 文件复制

public class FileNIOCopyDemo {

    public static void main(String[] args) throws IOException {
        // 源文件
        File srcFile = new File("D:\\h.txt");
        // 目标文件
        File destFile = new File("D:\\hd.txt");
        // 校验源文件是否存在
        fileIsExists(srcFile);
        // 创建目标文件
        createFile(destFile);

        long startTime = System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel inChannel = null;
        FileChannel outchannel = null;

        try {
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            inChannel = fis.getChannel();
            outchannel = fos.getChannel();

            int length = -1;
            // 创建一个ByteBuffer
            ByteBuffer buf = ByteBuffer.allocate(1024);
            // 读取文件
            while ((length = inChannel.read(buf)) != -1) {
                //翻转buf,变成成读模式
                buf.flip();

                int outlength = 0;
                // 从buffer中读取数据,然后写入到channe中
                while ((outlength = outchannel.write(buf)) != 0) {
                    System.out.println("写入字节数:" + outlength);
                }
                // 清除buf,变成写入模式
                buf.clear();
            }
            //强制刷新磁盘
            outchannel.force(true);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeQuietly(fis);
            closeQuietly(fos);
            closeQuietly(inChannel);
            closeQuietly(outchannel);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("一共耗时:【" + (endTime - startTime) + "】 ms");


    }

    private static void createFile(File destFile) throws IOException {
        if (!destFile.exists()) {
            destFile.createNewFile();
        }
    }

    private static void fileIsExists(File srcFile) throws FileNotFoundException {
        if (!srcFile.exists()) {
            throw new FileNotFoundException("source.file.not.exists");
        }
    }

    private static void closeQuietly(java.io.Closeable o) {
        if (null == o) {
            return;
        }
        try {
            o.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

一共耗时:【14】 ms

作为文件复制的程序来说,以上代码的效率不是最高的。更高效的文件复制可以调用文件通道的transferFrom()方法。

public class FileNIOFastCopyDemo {

    public static void main(String[] args) throws IOException {
        // 源文件
        File srcFile = new File("D:\\h.txt");
        // 目标文件
        File destFile = new File("D:\\hd.txt");
        // 校验源文件是否存在
        fileIsExists(srcFile);
        // 创建目标文件
        createFile(destFile);

        long startTime = System.currentTimeMillis();

        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel inChannel = null;
        FileChannel outchannel = null;

        try {
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            inChannel = fis.getChannel();
            outchannel = fos.getChannel();

            long size = inChannel.size();
            long pos = 0;
            long count = 0;
            while (pos < size) {
                //每次复制最多1024个字节,没有就复制剩余的
                count = size - pos > 1024 ? 1024 : size - pos;
                //复制内存,偏移量pos + count长度
                pos += outchannel.transferFrom(inChannel, pos, count);
            }
            //强制刷新磁盘
            outchannel.force(true);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeQuietly(fis);
            closeQuietly(fos);
            closeQuietly(inChannel);
            closeQuietly(outchannel);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("一共耗时:【" + (endTime - startTime) + "】 ms");


    }

    private static void createFile(File destFile) throws IOException {
        if (!destFile.exists()) {
            destFile.createNewFile();
        }
    }

    private static void fileIsExists(File srcFile) throws FileNotFoundException {
        if (!srcFile.exists()) {
            throw new FileNotFoundException("source.file.not.exists");
        }
    }

    private static void closeQuietly(Closeable o) {
        if (null == o) {
            return;
        }
        try {
            o.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

 

2 SocketChannel

在NIO中,涉及网络连接的通道有两个:

  • 一个是SocketChannel,负责连接的数据传输;
  • 另一个是ServerSocketChannel,负责连接的监听。

其中,NIO中的SocketChannel传输通道与OIO中的Socket类对应,NIO中的ServerSocketChannel监听通道对应于OIO中的ServerSocket类。

ServerSocketChannel仅应用于服务端,而SocketChannel同时处于服务端和客户端。所以,对于一个连接,两端都有一个负责传输的SocketChannel。

无论是ServerSocketChannel还是SocketChannel,都支持阻塞和非阻塞两种模式。如何进行模式的设置呢?调用configureBlocking()方法,具体如下:

// 设置为非阻塞模式。
socketChannel.configureBlocking(false)
// 设置为阻塞模式。
socketChannel.configureBlocking(true)

在阻塞模式下,SocketChannel的连接、读、写操作都是同步阻塞式的,,在效率上与Java OIO面向流的阻塞式读写操作相同。

2.1 获取SocketChannel传输通道

  1. 通过SocketChannel静态方法open()获得一个套接字传输通道
  2. 然后将socket设置为非阻塞模式
  3. 最后通过connect()实例方法对服务器的IP和端口发起连接。

 

最后


给大家分享一篇一线开发大牛整理的java高并发核心编程神仙文档,里面主要包含的知识点有:多线程、线程池、内置锁、JMM、CAS、JUC、高并发设计模式、Java异步回调、CompletableFuture类等。

领取文档地址:GitHub标星12.5K+Java高并发核心编程知识笔记助我提升,感觉之前学的都是渣渣


感谢阅读,文章对你有帮助的话,不妨 一键三连 支持一下吧。你们的支持是我最大的动力,祝大家万事胜意!
 

你可能感兴趣的:(网络,java,linux,nio,socket)