Java NIO (三)NIO Channel类

news2024/11/15 19:48:39

1 概述

        前面提到,Java NIO中一个socket连接使用一个Channel来表示。从更广泛的层面来说,一个通道可以表示一个底层的文件描述符,例如硬件设备、文件、网络连接等。然而,远不止如此,Java NIO的通道可以更加细化。例如,不同的网络传输协议,在Java中都有不同的NIO Channel实现。

        这里不对Java NIO的全部通道类型进行过多描述,仅着重介绍其中最为重要的四种Channle实现:FileChannel、SocketChannel、ServerSocketChannel、DatagramChannel。

        对于以上四种通道,说明如下:

        (1)FileChannel:文件通道,用于文件的数据读写。

        (2)SocketChannel:套接字通道,用于套接字TCP连接的数据读写。

        (3)ServerSocketChannel:服务器套接字通道(或服务器监听通道),允许监听TCP连接请求,位每个监听的请求创建一个SocketChannel通道。

        (4)DatagramChannel:数据报通道,用于UDP的数据读写。

        这四种通道涵盖了文件IO、TCP网络、UDP IO三类基础IO读写操作。

2 FileChannel

        FileChannel是专门操作文件的通道。通过FileChannel,既可以从一个文件中读取数据,也可以把数据写入文件中。特别申明一下,FileChannel位阻塞模式,不能设置为非阻塞模式。

        下面分别介绍FileChannel的获取、读取、写入、关闭四个操作。

        2.1 获取FileChannel

        

public class Test_FileChannel {
    final static String srcFile = "/Users/jay/Documents/files/nio.txt";
    final static String outFile = "/Users/jay/Documents/files/nio1.txt";
    public static void main(String[] args) throws IOException {
        //创建一个文件输入流
        FileInputStream fis = new FileInputStream(srcFile);
        //获取文件流的通道
        FileChannel inChannel = fis.getChannel();
        //创建一个文件输出流
        FileOutputStream fos = new FileOutputStream(outFile);
        //获取文件流的通道
        FileChannel outChannel = fos.getChannel();
        System.out.println(outChannel.size());

    }
}

2.2 读取FileChannle

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

public class Test_FileChannel {
    final static String srcFile = "/Users/jay/Documents/files/nio.txt";
    final static String outFile = "/Users/jay/Documents/files/nio1.txt";
    public static void main(String[] args) throws IOException {
        RandomAccessFile rw = new RandomAccessFile(srcFile, "rw");
        //获取通道(可读可写)
        FileChannel channel = rw.getChannel();
        //获取一个字节缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(30);
        int length = -1;
        //调用通道read方法,读取数据并写入字节类型的缓冲区
        while((length = channel.read(buffer)) != -1){
            System.out.println(length);
        }

    }
}

2.3 写入FileChannel

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

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

public class Test_FileChannel {
    final static String srcFile = "/Users/jay/Documents/files/nio.txt";
    final static String outFile = "/Users/jay/Documents/files/nio1.txt";
    public static void main(String[] args) throws IOException {
        RandomAccessFile rw = new RandomAccessFile(srcFile, "rw");
        //获取通道(可读可写)
        FileChannel channel = rw.getChannel();
        //获取一个字节缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(30);
        int length = -1;
        //调用通道read方法,读取数据并写入字节类型的缓冲区
        while((length = channel.read(buffer)) != -1){
            System.out.println(length);
        }
        buffer.flip();
        int outlength = 0;
        while((outlength = channel.write(buffer)) != 0){
            System.out.println("写入的字节数 = " + outlength);
        }
    }
}

         

2.4  关闭通道

        当通道使用完后,必须将其关闭。调用close()方法 .

public class Test_FileChannel {
    final static String srcFile = "/Users/jay/Documents/files/nio.txt";
    final static String outFile = "/Users/jay/Documents/files/nio1.txt";
    public static void main(String[] args) throws IOException {
        RandomAccessFile rw = null;
        FileChannel channel = null;
        try {
             rw = new RandomAccessFile(srcFile, "rw");
            //获取通道(可读可写)
            channel = rw.getChannel();
            //获取一个字节缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(30);
            int length = -1;
            //调用通道read方法,读取数据并写入字节类型的缓冲区
            while((length = channel.read(buffer)) != -1){
                System.out.println(length);
            }
            buffer.flip();
            int outlength = 0;
            while((outlength = channel.write(buffer)) != 0){
                System.out.println("写入的字节数 = " + outlength);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            channel.close();
        }

    }
}

2.5 强制刷新到磁盘

        在将缓冲区写入通道时,出于性能的原因,操作系统不可能每次都实时地将写入数据保存到硬盘,完成最终的数据保存。在将缓冲区数据写入通道时,要保证数据能写入硬盘,可以在写入后调用FileChannel类的force()方法。channel.force(true)

2.6 使用FileChannle 完成文件发复制实战

需求:使用FileChannel复制文件,把原文件中的内容复制到目标文件中。

public class FileNIOCopyDemo {
    //原文件路径
    public static final String srcPath = "";
    //目标文件路径
    public static final String destPath = "";
    public static void main(String[] args) {
        //演示复制资源文件
        nioCopyFile(srcPath,destPath);
    }



    public static void nioCopyFile(String srcPath, String destPath){
        File src_file = new File(srcPath);
        File dest_file = new File(destPath);
        try {
            //如果目标文件不存在,则新建
            if(!dest_file.exists()){
                dest_file.createNewFile();
            }
            long start_time = System.currentTimeMillis();
            FileInputStream fis = null;
            FileOutputStream fos = null;
            //输入通道
            FileChannel inChannel = null;
            //输出通道
            FileChannel outChannel = null;
            try {
                fis = new FileInputStream(src_file);
                fos = new FileOutputStream(dest_file);
                inChannel = fis.getChannel();
                outChannel = fos.getChannel();
                int length = -1;
                //新建buf,处于写模式
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //从输入通道读取buffer
                while((length = inChannel.read(buffer)) != -1){
                    //buffer第一次切换成功,从写模式变成读模式
                    buffer.flip();
                    int outlength = 0;
                    //把buffer写入输出的通道
                    while ((outlength = outChannel.write(buffer)) != 0){
                        System.out.println("写入的字节数:" + outlength);
                    }
                    //buffer第二次模式切花,清除buffer,变成写模式
                    buffer.flip();
                }
                //强制刷新到磁盘
                outChannel.force(true);
            }finally {
                outChannel.close();
                fos.close();
                inChannel.close();
                fis.close();
            }
            long end_time = System.currentTimeMillis();
            System.out.println("复制花费的时间是:" + (end_time - start_time) + "毫秒!");
        }catch (Exception e){

        }
    }
}

3 SocketChannel

        3.1 概述

        在NIO中,涉及网路连接的通道有两个:一个是SocketChannel,负责连接的数据传输;另一个是ServerSocketChannel,负责连接的监听。其中,NIO中的SocketChannel传输通道与OIO中的Socket类对应,NIO中的ServerSocketChannel监听通道对应于OIO中的ServerSocket类。

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

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

        (1)socketChannel.configureBlocking(false):设置为非阻塞模式。

        (2)socketChannel.configureBlocking(true):设置为阻塞模式。

        在阻塞模式下,SocketChannel的连接、读写操作都是同步阻塞式的,在效率上与Java OIO面向流的阻塞式读写操作相同。因此,在这里不介绍阻塞模式下通道的具体操作。在非阻塞模式下,通道的操作是异步、高效的,这也是相对于传统的OIO的优势所在。

        3.2 获取SocketChannel传输通道

【重要通知:本章小节里的代码只是方法演示,不具备运行功能。在介绍完SocketChannel常用的方法后,会举例并成功运行】

        在客户端,先通过SocketChannel静态方法open()获得一个套接字传输通道,然后将socket设置为非阻塞模式,最后通过connet()实例方法对服务器的IP和端口发起连接。在非阻塞情况下,与服务器的连接可能还没有建立,socketChannel.connect()方法就反悔了,因此需要不断地自旋,检查当前是否连接到主机。

客户端:ClientTest.java

public class ClientTest {
    public static void main(String[] args) {
        //获取一个套接字传输通道
        try {
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.connect(new InetSocketAddress("127.0.0.1",80));
            while(! socketChannel.finishConnect()){
                System.out.println("连接成功");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

        在连接建立的事件到来时,服务端的ServerSocketChannel能成功地查询出这个新连接事件,并且通过调用服务端ServerSocketChannel监听套接字的accept()方法来获取新连接的套接字通道。

服务端:ServerTest.java

public class ServerTest {
    public static void main(String[] args) throws IOException {
        //新连接事件到来,首先通过事件获取服务器监听通道
        //key是具体的什么事件,是否为新连接事件-后面会说在,这里理解为一个事件对象
        ServerSocketChannel server = (ServerSocketChannel) key.channel();
        //获取新连接的套接字通道
        SocketChannel socketChannel = server.accept();
        //设置为非阻塞模式
        socketChannel.configureBlocking(false);
    }
}

【说明】NIO套接字通道主要用于非阻塞的传输场景。

3.3 读取SocketChannel传输通道

        当SocketChannel传输通道可读时,可以从SocketChannel读取数据,具体方法与前面的文件通道读取方法时相同的。调用read()方法,将数据读入缓冲区ByteBuffer。

ByteBuffer buffer = ByteBuffer.allocate(1024);
int byteRead = socketChannel.read(buffer);

        在读取时,因为是异步的,所以我们必须检查read()的返回值,以便判断当前是否读取了数据。read()方法的返回值是读取的字节数,如果是-1,那么表示读取到对方的输出结束标志,即对方已经输出结束,准备关键连接。实际上,通过read()方法读数据本身是很简单的,比较困难的是在非阻塞模式下如何知道通道何时是可读的。这需要用到NIO的新组件——Selector通道选择器,稍后会介绍它。

3.4 写入SocketChannel传输通道

        和前面把数据写入FileChannel一样,大部分应用场景都会调用通道的write(ByteBuffer buf)方法。

//写入缓冲区前要求ByteBuffer是读模式
buffer.flip();
socketChannel.write(buffer);

3.5 关闭SocketChannel传输通道

        在关闭SocketChannel传输通道前,如果传输通道用来写入数据,则建议调用一次shutdownOutput()终止输出方法,向对方发送一个输出的结束标志(-1),然后调用socketChannel.close()方法,关闭套接字连接。

SocketChannel socketChannel = SocketChannel.open();
socketChannel.shutdownOutput();
socketChannel.close();

3.6 使用 SocketChannel 发送文件 实战

        需求:使用FileChannel读取本地文件内容,然后在客户端使用SocketChannel 把文件信息和文件内容发送到服务器。

public class NioSendClient {
    private Charset charset = Charset.forName("UTF-8");
    public static final String SMALL_PATH = "/Users/jay/Documents/files/src_nio.txt";
    public static final String BIG_PATH = "/Users/jay/Documents/files/孔乙己.docx";

    //向服务器传输文件
    public void sendFile() throws Exception {
        File file = new File(BIG_PATH);
        FileChannel fileChannel = new FileInputStream(file).getChannel();
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.socket().connect(new InetSocketAddress("127.0.0.1",18899));
        socketChannel.configureBlocking(false);
        System.out.println("成功连接服务端");
        while(!socketChannel.finishConnect()){
            int count = 0;
            //不断自旋、等待,或者做一些其他的是
            System.out.println(count ++);
        }
        //发送文件的名称
        ByteBuffer fileNameByteBuffer = charset.encode(file.getName());
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //发送的文件长度
        int fileNameLen = fileNameByteBuffer.remaining();
        buffer.clear();
        buffer.putInt(fileNameLen);
        //切换到读模式
        buffer.flip();
        socketChannel.write(buffer);
        System.out.println("Client 文件名称长度发送完成:"+fileNameByteBuffer);
        //发送文件
        socketChannel.write(fileNameByteBuffer);
        System.out.println("Client 文件名称名称发送完成:" + file.getName());
        //清空
        buffer.clear();
        buffer.putInt((int) file.length());
        //切换到读模式
        buffer.flip();
        //写入文件长度
        socketChannel.write(buffer);
        System.out.println("Client 文件长度发送完成:"+ file.length());
        //发送文件内容
        System.out.println("开始传输文件");
        int length = 0;
        long offset = 0L;
        buffer.clear();
        while((length = fileChannel.read(buffer)) > 0){
            buffer.flip();
            socketChannel.write(buffer);
            offset += length;
            System.out.println("| " + (100 * offset / file.length()) + "% |");
            buffer.clear();
        }
        //等待一分钟关闭连接
        Thread.sleep(60000);
        if (length == -1){
            fileChannel.close();
            socketChannel.shutdownOutput();
            socketChannel.close();
        }
        System.out.println("文件传输成功");
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1402916.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python并发编程的概念和重要性

并发编程是一种编程方式,它允许在单台处理器上同时处理多个任务或操作。这些任务可以在单个处理器上通过时间分片技术实现,也可以在多核或多处理器系统上真正地并行执行。并发性对于提高系统资源利用率、提升应用程序性能以及改善用户体验都至关重要。 并…

计算机网络-分层结构,协议,接口,服务

文章目录 总览为什么要分层怎样分层正式认识分层概念小结 总览 为什么要分层 发送文件前要做的准备工作很多 把这个准备工作分层小问题解决,也就分层解决 怎样分层 每层相互独立,每层做的工作不同 界面自然清晰,层与层之间的接口能够体现…

JS-日期对象

日期对象:用来表示时间的对象 作用:可以得到当前系统时间 实例化 在代码中发现了new关键字时,一般将这个操作称为实例化 创建一个时间对象并获取时间 1)获得当前时间 const datenew Date() 2)获得指定时间 const datenew D…

蓝桥杯理历年真题 —— 数学

1. 买不到的数目 这道题目,考得就是一个日常数学的积累,如果你学过这个公式的话,就是一道非常简单的输出问题;可是如果没学过,就非常吃亏,在考场上只能暴力求解,或是寻找规律。这就要求我们什么…

【window】Windows11:该文件没有与之关联的应用来执行该操作

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站:人工智能 之前win10升级win11后,受不了桌面软件图标的的小箭头,所以弄掉了,但是随之而来产…

揭开Spring MVC的真面目

官方对于Spring MVC的描述为: Spring Web MVC是基于Servlet API框架构建的原始Web框架,从一开始就包含在Spring框架中。它的正式名称“Spring Web MVC”来自其源模块的名称(Spring-webmvc),但它通常被称为“Spring-MVC…

数学建模--Radar图绘制

1.Radar图简介 最近在数学建模中碰见需要绘制Radar图(雷达图)的情况来具体分析样本的各个特征之间的得分与优劣关系,这样的情况比较符合雷达图的使用场景,一般来说,雷达图适用于展示多个维度的数据,并在一个平面上直观地呈现出不同…

宠物空气净化器推荐哪个好?实惠的猫用猫用净化器牌子测评

作为宠物主人,我们深知养宠物的乐趣和责任,但同时也面临着一些挑战,比如宠物掉毛、异味和空气质量等问题。这就是为什么越来越多的家庭选择宠物空气净化器,为我们创造一个清新、健康的室内环境。 无论我们多么爱我们的毛茸茸伙伴…

如何在 Ubuntu 22.04 上安装 Apache Web 服务器

前些天发现了一个人工智能学习网站,通俗易懂,风趣幽默,最重要的屌图甚多,忍不住分享一下给大家。点击跳转到网站。 如何在 Ubuntu 22.04 上安装 Apache Web 服务器 介绍 Apache HTTP 服务器是世界上使用最广泛的 Web 服务器。它…

【C++】priority_queue模拟实现过程中值得注意的点

👀樊梓慕:个人主页 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》《算法》 🌝每一个不曾起舞的日子,都是对生命的辜负 前言 本篇文章旨在记录博主在模…

[已解决]mysql关闭SSL功能和永久关闭SSL设置

概述 在搭建服务器连接本地数据库时发现有个报错信息: SQLState - 08S01 com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure The last packet successfully received from the server was 292 milliseconds ago. The last …

如何查看苹果手机的CPU型号?

摘要 本文将介绍如何在苹果手机上查看CPU型号。通过简单的设置操作,您可以轻松地获取您的iPhone的CPU型号信息。此外,我们还将介绍一些克魔助手可以提供的其他功能,如内存监控、GPU性能监控和网络抓包等,以帮助您优化和提升iOS应…

设计模式—行为型模式之观察者模式

设计模式—行为型模式之观察者模式 观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe&#…

三.Winform使用Webview2加载本地HTML页面

Winform使用Webview2加载本地HTML页面 往期目录创建Demo2界面创建HTML页面在Demo2窗体上添加WebView2和按钮加载HTML查看效果 往期目录 往期相关文章目录 专栏目录 创建Demo2界面 经过前面两小节 一.Winform使用Webview2(Edge浏览器核心) 创建demo(Demo1)实现回车导航到指定…

同星多通道CAN FD转USB/WIFI设备,解决近距离无线通讯问题

新品发布/New products release 2024年1月,同星智能连续发布FlexRay系列产品TP1034和以太网系列产品TP1051,上周发布多通道总线记录仪产品TLog1004。1月19日,同星智能又推出一款2/4路CAN FD转USB和WIFI的工具,解决近距离无线通讯…

L1-079 天梯赛的善良(Java)

天梯赛是个善良的比赛。善良的命题组希望将题目难度控制在一个范围内,使得每个参赛的学生都有能做出来的题目,并且最厉害的学生也要非常努力才有可能得到高分。 于是命题组首先将编程能力划分成了 106 个等级(太疯狂了,这是假的&…

如何使用支付宝沙箱环境本地配置模拟支付并结合内网穿透远程调试

文章目录 前言1. 下载当面付demo2. 修改配置文件3. 打包成web服务4. 局域网测试5. 内网穿透6. 测试公网访问7. 配置二级子域名8. 测试使用固定二级子域名访问 正文开始前给大家推荐个网站,前些天发现了一个巨牛的 人工智能学习网站, 通俗易懂&#xff…

基于光口的以太网 udp 回环实验

文章目录 前言一、系统框架整体设计二、系统工程及 IP 创建三、UDP回环模块修改说明四、接口讲解五、顶层模块设计六、下载验证前言 本章实验我们通过网络调试助手发送数据给 FPGA,FPGA通过光口接收数据并将数据使用 UDP 协议发送给电脑。 提示:任何文章不要过度深思!万事万…

【SpringBoot技术专题】「开发实战系列」Undertow web容器的入门实战及调优方案精讲

Undertow web容器的入门实战及调优方案精讲 Undertow web容器Undertow 介绍官网API给出一句话概述Undertow:官网API总结特点:Lightweight(轻量级)HTTP Upgrade Support(支持http升级)、HTTP/2 Support支持H…

浅谈linux中的根文件系统(rootfs的原理和介绍)【转】

浅谈linux中的根文件系统(rootfs的原理和介绍)【转】 转自:https://www.cnblogs.com/sky-heaven/p/13742173.html linux中有一个让不少初学者都不是特别清楚的概念,叫作“根文件系统”。我接触linux前先后后也好几年了&#xff0…