【Java基础】10分钟看懂Java NIO

news2024/10/2 16:27:19

一、IO概述

IO的操作方式通常分为几种:同步阻塞BIO、同步非阻塞NIO、异步非阳塞AIO

1、在JDK1.4之前,我们建立网络连接的时候采用的是 BIO 模式。

2、Java NIO(New IO或Non Blocking IO) 是从Java 1.4版本开始引入的一个新的IOAPI,可以替代标准的Java IO API。NIO支持面向缓冲区的、基于通道的IO操作NIO将以更加高效的方式进行文件的读写操作。BIO与NIO一个比较重要的不同是我们使用 BIO的时候往往会引入多线程,每个连接对应一个单独的线程,而 NIO则是使用单线程或者只使用少量的多线程,让连接共用一个线程。

3、AIO 也就是NIO2,在Java 7 中引入了 NIO的改进版 NIO2它是异步非阻塞的IO 模型。

二、BIO、NIO、AIO应用场景

1、BIO方式适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高, 并发局限于应用中,JDK1.4以前的唯一选择,但程序简单易理解。

2、NIO方式适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器,弹幕 系统,服务器间通讯等。编程比较复杂,JDK1.4开始支持。

3、AIO方式使用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分 调用OS参与并发操作,编程比较复杂,JDK7开始支持

三、NIO的基本用法

NIO是New I/O的简称,与旧式基于流的I/O相对,从名字上来看,它表示新的一套I/O标准。它是从JDK1.4中被纳入到JDK中的。

与旧式的IO流相比,NIO是基于Block的,它以块为单位来处理数据,最为重要的两个组件是缓冲区Buffer和通道Channel。缓冲区是一块连续的内存块,是NIO读写数据的载体;通道表示缓冲数据的源头和目的地,它用于向缓冲区读取或者写入数据,是访问缓冲区的接口。

1、Buffer的基本原理

Buffer中最重要的3个参数:位置(position)、容量(capacity)、上限(limit)。他们3者的含义如下

位置(position): 表示当前缓冲区的位置,从position位置之后开始读写数据。
容量(capacity): 表示缓冲区的最大容量
上限(limit): 表示缓冲区的实际上限,它总是小于或等于容量

position 和limit 的含义取决于 Buffer 处在读模式还是写模式。不管 Buffer 处在什么模式,capacity的含义总是一样的。

以下是关于capacity,position 和limit 在读写模式中的说明

(1) capacity

作为一个内存块,Buffer 有一个固定的大小值,也叫“capacity”.你只能往里写capacity 个byte、long,char等类型。一旦 Buffer满了,需要将其清空(通过读数据或者清除数据)才能继续写数据往里写数据

(2) position

  • 写数据到 Buffer 中时,position 表示写入数据的当前位置,position 的初始值为0。当一个 byte、long 等数据写到 Buffer后, position 会向下移动到下一个可插入数据的 Buffer单元。position 最大可为 capacity -1(因为 position 的初始值为0)

  • 读数据到 Buffer 中时,position 表示读入数据的当前位置,如 position=2 时表示已开始读入了3个byte,或从第3个byte 开始读取。通过 ByteBuffer.flip(切换到读模式时 position 会被重置为0,当Buffer从 position 读入数据后,position 会下移到下一个可读入的数据 Buffer 单元。

(3) limit

  • 写数据时,limit 表示可对 Buffer 最多写入多少个数据。写模式下,limit 等于Buffer的 capacity。

  • 读数据时,limit 表示 Buffer 里有多少可读数据 (not null 的数据),因此能读到之前写入的所有数据(limit被设置成已写数据的数量,这个值在写模式下就是position)

1.1、Buffer 的类型

Java NIO 有以下 Buffer类型

  • ByteBuffer

  • MappedByteBuffer

  • CharBuffer

  • DoubleBuffer

  • FloatBuffer

  • IntBuffer

  • LongBuffer

  • ShortBuffer

这些 Buffer 类型代表了不同的数据类型。换句话说,就是可以通过char,short,intlong,float 或 double 类型来操作缓冲区中的字节。

1.2、buffer常用api

JDK1.4时,引入的api

  • public final int capacity( )//返回此缓冲区的容量

  • public final int position( )//返回此缓冲区的位置

  • public final Buffer position (int newPositio)//设置此缓冲区的位置

  • public final int limit( )//返回此缓冲区的限制

  • public final Buffer limit (int newLimit)//设置此缓冲区的限制

  • public final Buffer mark( )//在此缓冲区的位置设置标记

  • public final Buffer reset( )//将此缓冲区的位置重置为以前标记的位置

  • public final Buffer clear( )//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖

  • public final Buffer flip( )//反转此缓冲区

  • public final Buffer rewind( )//重绕此缓冲区

  • public final int remaining( )//返回当前位置与限制之间的元素数

  • public final boolean hasRemaining( )//告知在当前位置和限制之间是否有元素

  • public abstract boolean isReadOnly( );//告知此缓冲区是否为只读缓冲区

JDK1.6时引入的api

  • public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组

  • public abstract Object array();//返回此缓冲区的底层实现数组

  • public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量

  • public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区

以下是buffer的例子:

package com.biyu.buffer;

import org.junit.jupiter.api.Test;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

public class BufferDemo2 {

    static private final int start = 0;
    static private final int size = 1024;

    //内存映射文件io
     @Test
     public void b04() throws Exception {
         RandomAccessFile raf = new RandomAccessFile("d:\\atguigu\\01.txt", "rw");
         FileChannel fc = raf.getChannel();
         MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, start, size);

         mbb.put(0, (byte) 97);
         mbb.put(1023, (byte) 122);
         raf.close();
     }
    //直接缓冲区
    @Test
    public void b03() throws Exception {
        String infile = "d:\\atguigu\\01.txt";
        FileInputStream fin = new FileInputStream(infile);
        FileChannel finChannel = fin.getChannel();

        String outfile = "d:\\atguigu\\02.txt";
        FileOutputStream fout = new FileOutputStream(outfile);
        FileChannel foutChannel = fout.getChannel();

        //创建直接缓冲区
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);

        while (true) {
            buffer.clear();
            int r = finChannel.read(buffer);
            if(r == -1) {
                break;
            }
            buffer.flip();
            foutChannel.write(buffer);
        }
    }

    //只读缓冲区
    @Test
    public void b02() {
        ByteBuffer buffer = ByteBuffer.allocate(10);

        for (int i = 0; i < buffer.capacity(); i++) {
            buffer.put((byte)i);
        }

        //创建只读缓冲区
        ByteBuffer readonly = buffer.asReadOnlyBuffer();

        for (int i = 0; i < buffer.capacity(); i++) {
            byte b = buffer.get(i);
            b *=10;
            buffer.put(i,b);
        }

        readonly.position(0);
        readonly.limit(buffer.capacity());

        while (readonly.remaining()>0) {
            System.out.println(readonly.get());
        }
    }


    //缓冲区分片
    @Test
    public void b01() {
        ByteBuffer buffer = ByteBuffer.allocate(10);

        for (int i = 0; i < buffer.capacity(); i++) {
            buffer.put((byte)i);
        }

        //创建子缓冲区
        buffer.position(3);
        buffer.limit(7);

        //获取
        while (buffer.hasRemaining()) {
            int value = buffer.get();
            System.out.println(value + " ");
        }
        System.out.println("******************");

        ByteBuffer slice = buffer.slice();

        //改变子缓冲区内容
        for (int i = 0; i <slice.capacity() ; i++) {
            byte b = slice.get(i);
            b *=10;
            slice.put(i,b);
        }

        buffer.position(0);
        buffer.limit(buffer.capacity());

        while(buffer.remaining()>0) {
            System.out.println(buffer.get());
        }
    }
}

2、FileChannel通道

FileChannel是用于操作文件的通道,可以用于读取文件、也可以写入文件

package com.biyu.channel;

import java.io.RandomAccessFile;
import java.nio.channels.DatagramChannel;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

//通道之间数据传输
public class FileChannelDemo4 {

    //transferTo()
    public static void main(String[] args) throws Exception {
        // 创建两个fileChannel
        RandomAccessFile aFile = new RandomAccessFile("d:\\a.txt","rw");
        FileChannel fromChannel = aFile.getChannel();

        RandomAccessFile bFile = new RandomAccessFile("d:\\b.txt","rw");
        FileChannel toChannel = bFile.getChannel();

        //fromChannel 传输到 toChannel
        long position = 0;
        long size = fromChannel.size();
        fromChannel.transferTo(0,size,toChannel);

        aFile.close();
        bFile.close();
        System.out.println("over!");
    }
}

3、SocketChannel通道

NIO中通过SocketChannel与ServerSocketChannel替代TCP协议的网络通信编程。

获取对象 public static SocketChannelopen()
连接服务器 boolean connect(SocketAddress remote)
SocketAddress是抽象类,使用其子类InetSocketAddress创建的对象。InetSocketAddress(String ip,int port)
等待客户端连接 SocketChannel accept()

客户端代码:

package com.biyu.channel;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

public class SocketChannelDemo {

    public static void main(String[] args) throws Exception {
        //创建SocketChannel
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("www.baidu.com", 80));

//        SocketChannel socketChanne2 = SocketChannel.open();
//        socketChanne2.connect(new InetSocketAddress("www.baidu.com", 80));

        //设置阻塞和非阻塞
        socketChannel.configureBlocking(false);

        //读操作
        ByteBuffer byteBuffer = ByteBuffer.allocate(16);
        socketChannel.read(byteBuffer);
        socketChannel.close();
        System.out.println("read over");

    }

}

4、ServerSocketChannel通道

ServerSocketChannel 服务端通道,用于服务端监听TCP连接

获取对象 public static ServerSocketChannel open()
绑定端口号 ServerSocketChannel bind(SocketAddress local)

服务端代码:

package com.biyu.channel;

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

public class ServerSocketChannelDemo {

    public static void main(String[] args) throws Exception {
        //端口号
        int port = 8888;

        //buffer
        ByteBuffer buffer = ByteBuffer.wrap("hello atguigu".getBytes());

        //ServerSocketChannel
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定
        ssc.socket().bind(new InetSocketAddress(port));

        //设置非阻塞模式
        ssc.configureBlocking(false);

        //监听有新链接传入
        while(true) {
            System.out.println("Waiting for connections");
            SocketChannel sc = ssc.accept();
            if(sc == null) { //没有链接传入
                System.out.println("null");
                Thread.sleep(2000);
            } else {
                System.out.println("Incoming connection from: " + sc.socket().getRemoteSocketAddress());
                buffer.rewind(); //指针0
                sc.write(buffer);
                sc.close();
            }
        }
    }
}

5、NIO Selector选择器

Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。

package com.biyu.selector;

import org.junit.jupiter.api.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

public class SelectorDemo2 {

    //服务端代码
    @Test
    public void serverDemo() throws Exception {
        //1 获取服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2 切换非阻塞模式
        serverSocketChannel.configureBlocking(false);

        //3 创建buffer
        ByteBuffer serverByteBuffer = ByteBuffer.allocate(1024);

        //4 绑定端口号
        serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",8080));

        //5 获取selector选择器
        Selector selector = Selector.open();

        //6 通道注册到选择器,进行监听
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //7 选择器进行轮询,进行后续操作
        while(selector.select()>0) {
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //遍历
            Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
            while(selectionKeyIterator.hasNext()) {
                //获取就绪操作
                SelectionKey next = selectionKeyIterator.next();
                //判断什么操作
                if(next.isAcceptable()) {
                    //获取连接
                    SocketChannel accept = serverSocketChannel.accept();

                    //切换非阻塞模式
                    accept.configureBlocking(false);

                    //注册
                    accept.register(selector,SelectionKey.OP_READ);

                } else if(next.isReadable()) {
                    SocketChannel channel = (SocketChannel) next.channel();

                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

                    //读取数据
                    int length = 0;
                    while((length = channel.read(byteBuffer))>0) {
                        byteBuffer.flip();
                        System.out.println(new String(byteBuffer.array(),0,length));
                        byteBuffer.clear();
                    }

                }

                selectionKeyIterator.remove();
            }
        }
    }

    //客户端代码
    @Test
    public void clientDemo() throws Exception {
        //1 获取通道,绑定主机和端口号
        SocketChannel socketChannel =
                SocketChannel.open(new InetSocketAddress("127.0.0.1",8080));

        //2 切换到非阻塞模式
        socketChannel.configureBlocking(false);

        //3 创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        //4 写入buffer数据
        byteBuffer.put(new Date().toString().getBytes());

        //5 模式切换
        byteBuffer.flip();

        //6 写入通道
        socketChannel.write(byteBuffer);

        //7 关闭
        byteBuffer.clear();
    }

    public static void main(String[] args) throws IOException {
        //1 获取通道,绑定主机和端口号
        SocketChannel socketChannel =
                SocketChannel.open(new InetSocketAddress("127.0.0.1",8080));

        //2 切换到非阻塞模式
        socketChannel.configureBlocking(false);

        //3 创建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNext()) {
            String str = scanner.next();

            //4 写入buffer数据
            byteBuffer.put((new Date().toString()+"--->"+str).getBytes());

            //5 模式切换
            byteBuffer.flip();

            //6 写入通道
            socketChannel.write(byteBuffer);

            //7 关闭
            byteBuffer.clear();
        }

    }
}

四、NIO编程步骤总结

第一步 创建ServerSocketChannle通道,绑定监听端口

第二步 设置通道是非阻塞模式

第三步 创建Selector选择器

监听连接事件第四步 把Channel注册到Selector选择器上,

第五步 调用Selector的select方法 (循环调用)监测通道的就绪状况

第六步 调用selectKeys方法获取就绪channel集合

第七步 遍历就绪channel集合,判断就绪事件类型,实现具体的业务操作

第八步 根据业务,是否需要再次注册监听事件,重复执行

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

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

相关文章

跨域问题解决方案

目录 1.同源策略 2.解决方案(后端) (1)在后端方法添加CrossOrigin (2)添加CORS过滤器 (3)实现WebMvcConfigure接口&#xff0c;重写addCorsMappings方法 3.解决方案(前端) (1)前端配置代理 1.同源策略 同源策略&#xff08;Same origin policy&#xff09;是一种约定&am…

代码随想录算法训练营day53 | 动态规划之子序列 1143.最长公共子序列 1035.不相交的线 53. 最大子序和

day531143.最长公共子序列1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组1035.不相交的线53. 最大子序和1.确定dp数组&#xff08;dp table&#xff09;以及下标的含义2.确定递推公式3.dp数组如何…

【深入理解 线程池】

深入理解 线程池介绍源码学习线程池的类继承体系ThreadPoolExector核心数据结构核心配置参数线程池的执行流程如图&#xff1a;线程池的优雅关闭线程池的生命周期正确关闭线程池的步骤任务的提交过程分析任务的执行过程shutdonw() 与任务执行过程综合分析shutdonwNow() 与任务执…

python3.6 处理报错free(): invalid pointer

在运行脚本的时候遇到了这个报错&#xff0c;我在笔记本的win10 python3.7上正常运行&#xff0c;把程序考到服务器报了这个错&#xff0c;free(): invalid pointer 脚本里写了异常处理&#xff0c;用的是纯净的虚拟环境&#xff0c;所以我感觉问题是出在系统环境上 在网上搜…

Linux:tcp socket客户端和服务器端代码

服务器端代码&#xff1a; #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <arpa/inet.h> #include <sys/un.h> #include <sy…

手工数据采集耗时耗力?Smartbi数据填报实现数据收集分析自动化

企业在日常经营管理过程中&#xff0c;往往需要收集很多内外部的信息&#xff0c;清洗整理后再进行存储、分析、呈现、决策支持等各种作业&#xff0c;如何高效收集结构化数据是企业管理者经常要面对的问题。传统手工的数据采集方式不仅耗费了大量人力时间成本&#xff0c;还容…

爽,我终于掌握了selenium图片滑块验证码

因为种种原因没能实现愿景的目标&#xff0c;在这里记录一下中间结果&#xff0c;也算是一个收场吧。这篇文章主要是用selenium解决滑块验证码的个别案列。 思路&#xff1a; 用selenium打开浏览器指定网站 将残缺块图片和背景图片下载到本地 对比两张图片的相似地方&#x…

【含源码】用python做游戏有多简单好玩

有很多同学问我还有其他什么小游戏吗&#xff0c;游戏是怎么做的&#xff0c;难不难。我就用两篇文章来介绍一下&#xff0c;如何使用Python做游戏。 兔子与灌 俄罗斯方块 休闲五子棋 走迷宫 推箱子 消消乐 超多小游戏玩转不停↓ 更多小游戏可以评论区讨论哦&#xff0c;喜欢…

C中AES_cbc_encrypt加密对应java中的解密

前言知识&#xff1a; 1.AES&#xff08;Advanced Encryption Standard&#xff09;高级加密标准&#xff0c;作为分组密码&#xff08;把明文分成一组一组的&#xff0c;每组长度相等&#xff0c;每次加密一组数据&#xff0c;直到加密完整个明文&#xff09;。 2.在AES标准…

C#基础教程12 数组

文章目录 C# 数组(Array)C# 中的数组声明数组初始化数组赋值给数组访问数组元素C# 数组细节C# 数组(Array) 数组是一个存储相同类型元素的固定大小的顺序集合。数组是用来存储数据的集合,通常认为数组是一个同一类型变量的集合。 声明数组变量并不是声明 number0、number1…

【设计模式】工厂模式

工厂模式 所谓工厂&#xff0c;顾名思义&#xff0c;就是创建出一类相似的产品的&#xff0c;工厂模式可以帮我们创建各个复杂/简单对象。属于创建型模式。 工厂模式分为三类: 简单工厂工厂方法抽象工厂 简单工厂 比方说我们需要根据配置文件去解析配置&#xff0c;不同后…

html5播放器禁止拖拽、视频禁止拖动的实例

阿酷TONY / 2023-3-8 / 长沙html5播放器禁止拖拽功能,常用于场景&#xff1a;企业培训、在线教学内容禁止学员拖动视频进行观看。应用代码实例&#xff1a;<div id"player"></div> <script src"//player.polyv.net/script/player.js">&l…

pytest初识

一、单元测试框架 &#xff08;1&#xff09;什么是单元测试框架&#xff1f; 单元测试是指在软件开发中&#xff0c;针对软件的最小单元&#xff08;函数、方法&#xff09;进行正确性的检查测试 &#xff08;2&#xff09;单元测试框架 java&#xff1a;junit和testng pytho…

Windows SSH 配置和SCP的使用

使用用户界面安装 ssh 功能 要在 Windows 10/11 上启用 SSH 服务器&#xff0c;请按照以下步骤操作&#xff1a; 按“Windows 键 I”打开“设置”菜单&#xff0c;然后选择“应用程序”。在左侧菜单栏中选择“应用和功能”。从列表中选择“可选功能”。 点击“添加功能”按钮…

[数据结构]:15-堆排序(顺序表指针实现形式)(C语言实现)

目录 前言 已完成内容 堆排序实现 01-开发环境 02-文件布局 03-代码 01-主函数 02-头文件 03-PSeqListFunction.cpp 04-SortCommon.cpp 05-SortFunction.cpp 结语 前言 此专栏包含408考研数据结构全部内容&#xff0c;除其中使用到C引用外&#xff0c;全为C语言代码…

android h5考勤管理系统myeclipse开发mysql数据库编程服务端java计算机程序设计

一、源码特点 android h5考勤管理系统是一套完善的WEBandroid设计系统&#xff0c;对理解JSP java&#xff0c;安卓app编程开发语言有帮助&#xff08;系统采用web服务端APP端 综合模式进行设计开发&#xff09;&#xff0c;系统具有完整的源代 码和数据库&#xff0c;系统主…

平板触控笔要原装的吗?开学季必备电容笔推荐

如今对那些把ipad当做学习工具的用户而言&#xff0c;Apple Pencil就显得尤为重要了。但由于Apple Pencil的售价实在太高&#xff0c;让学生党望而却步。因此&#xff0c;最好的办法就是选择平替电容笔。我是一个ipad设备的忠实用户&#xff0c;同时也是一个数码热衷者&#xf…

MySQL增量备份和全量备份

1 全量备份 1.1 创建用于备份的目录 mkdir /root/mysql.backup1.2 创建存入备份文件的目录 mkdir /root/mysql.backup/data1.3 进入备份目录&#xff0c;创建备份脚本 cd /root/mysql.backupvim mysqlBackuoShell.sh#!/bin/bash #保存备份个数,31条 number31 #备份保存路…

Infineon Aurix 系列网络安全概述

目录 硬件安全 其它 1999年&#xff0c;英飞凌推出了第一代AUDO(汽车统一处理器)系列。基于统一的以RISC/MCU/DSP处理器为核心&#xff0c;这款32位TriCore™微控制器是一款强大的计算机器。其不断改进和优化TriCore的概念&#xff0c;最终形成了现在的第六代TriCore™,由于其…

ACWING蓝桥杯每日一题python(持续更新

ACWing蓝桥杯每日一题 一直没时间去总结算法&#xff0c;终于有空可以总结一下刷的acwing了&#xff0c;因为没时间所以最近只刷了ACWING的蓝桥杯每日一题。。。真是该死 1.截断数组 首先我们要知道&#xff0c;如果sum(a)不能被3整除或者len(a) < 3 &#xff0c;那么他肯…