首页 > 编程语言 >Java NIO编程实例

Java NIO编程实例

时间:2022-11-22 06:22:07浏览次数:66  
标签:Java NIO Buffer 编程 buffer byteBuffer ByteBuffer Channel

 

文章目录

 


前言

基于NIO的网络编程实例


提示:以下是本篇文章正文内容,下面案例可供参考

一、NIO与BIO的比较

NIO与BIO的比较 :

  1. BIO是以流的方式处理的 , NIO是以块的方式处理的(因为有Buffer) , 块I/O效率更高
  2. BIO是基于字节流字符流处理的,NIO是基于Channel和Buffer处理的,单个线程可监听多个客户端的通道

NIO三个核心组件之间的关系

  1. 每个Channel都对应一个Buffer , Channel是双向的,可读可写;
  2. Selector对应一个线程 ;
  3. 一个线程对应多个Channel ;
  4. 程序切换到哪个Channel是由时间Event决定的 ;
  5. Selector会根据不同的事件在各个通道上切换 ;
  6. Buffer 就是一个内存块 , 底层是一个数组 ;
  7. 数据的读取/写入是通过Buffer进行的 ,是可以读也可以写的 ,但需要flip方法做读写切换 ,与BIO有本质区别

二、Buffer的机制及其子类

1.Buffer的使用

Buffer的子类型 : 除了Boolean类型,其余7个Java子类型Buffer都有

public static void main(String[] args) {
        // buffer的使用
        // 1. 创建一个Buffer
        IntBuffer intBuffer = IntBuffer.allocate(5); // 创建一个容量为5的Buffer

        // 2. 向Buffer中存放数据
        for (int i = 0; i < 5; i++) {
            intBuffer.put(i*2);
        }
        // 3. 从Buffer中读取数据
        // 将Buffer做读写切换
        intBuffer.flip();

        while (intBuffer.hasRemaining()){
            System.out.println(intBuffer.get()); // get方法里维护了一个索引
        }

    }
 

2.Buffer的四个基本类型

以InteBuffer为例 , 真正的数据是存放在 final int[] hb;数组里的

Buffer中定义了所有缓冲区都具有的4个属性

	private int mark = -1;  //标记,一般不被修改
    private int position = 0; //下一个要被读写的元素的索引
    private int limit; //缓冲区的当前终点  (数组索引的最大值)
    private int capacity; // 最大容量
 

其中 , position不能超过limit , position可以被理解为一个游标 , 读写的时候是根据position的位置进行的

当调用了 flip()函数反转过后 , position会被置为0

同时 , 上述的4个参数都有其对应的函数来修改他们的值

public final Buffer clear()方法能将这4个参数恢复到初始状态 , 但是数据不会真正的被擦除

三、Channel的使用

1. Channel的特征

  1. 通道是可以同时读写的
  2. 可以实现异步读写数据
  3. 可以从Buffer中读取数据 , 也可以向Buffer写入数据

2. Channel的子类

Channel的子类 : FileChannel文件数据的读写 , DatagramChannel UDP数据的读写 , ServerScoketChannel和ScoketChannel用于TCP数据的读写
我们用于网络编程最常用的当然就是ServerScoketChannel和ScoketChannel了

(1) FileChannel实例:

public static void main(String[] args) throws IOException {
        String str = "hello world";
        // 创建一个输出流
        FileOutputStream fileOutputStream = new FileOutputStream("d://file01.txt");

        // 通过输出流获取对应的FileChannel , 其真实类型为FileChannelImpl
        FileChannel fileChannel = fileOutputStream.getChannel();

        //创建一个ButeBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        // 将str放到byte Buffer
        byteBuffer.put(str.getBytes());

        // filp反转
        byteBuffer.flip();

        //将byteBuffer数据写入fileChannel
        fileChannel.write(byteBuffer);
        fileOutputStream.close(); //关闭最底层的流

    }
 

这里需要注意 : 当需要buffer从读转为写时,需要调用flip函数做读写切换

(2) 拷贝文件

/**
     * 拷贝文件
     * */
    public static void copy() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("1.txt");
        FileChannel channel = fileInputStream.getChannel();

        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel channel1 = fileOutputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

        while (true){
            // 这里必须要清空一次数据,将关键属性重置
            /**
             * 如果这里不做复位,read的值会一直是0,程序会一直读取数据,进入死循环
             * */
            byteBuffer.clear();
            int read = channel.read(byteBuffer);
            if (read == -1){
                break;
            }
            // 将buffer中的数据写入到channel1
            byteBuffer.flip();
            channel1.write(byteBuffer);
        }

        fileInputStream.close();
        fileOutputStream.close();
    }
 

四、Buffer类型化和只读

1. 类型化

所谓的类型化就是指 , 存进去的时什么数据类型的 . 读取的就要是什么数据类型 , 否则会报错

public static void main(String[] args) {
        // 创建一个buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(64);

        for (int i = 0; i < 64; i++) {
            byteBuffer.put((byte)i);

        }

        //反转并读取
        byteBuffer.flip();

        //获取一个只读的buffer
        ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();

        while (readOnlyBuffer.hasRemaining()){

            System.out.println(readOnlyBuffer.get());
        }
    }
 

2. Buffer的分散和聚合

Scattering: 将数据写入到buffer中,可以采用buffer数组,依次写入

Gathering : 从buffer读取数据时 , 采用buffer数组以此读

分散和聚合涉及到同时操作多个Buffer

五、MappedByteBuffer

操作系统级别 , 性能比较高

MappedByteBuffer可以直接在内存(堆外内存)中修改文件 , 操作系统不需要再拷贝一次;

public static void main(String[] args) throws IOException {
        RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
        FileChannel channel = randomAccessFile.getChannel();

        /**
         * p1 FileChannel.MapMode.READ_WRITE 使用读写模式
         *
         * p2 直接修改的起始位置
         *
         * p3 目标文件将多少个字节映射到内存中
         * p2 p3 表示程序可以直接修改的范围
         * */
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

        // 修改对应内容
        mappedByteBuffer.put(0,(byte)'A');
        mappedByteBuffer.put(3,(byte)9);
        randomAccessFile.close();
    }
 

六、Selector

能够检测多个注册上来的通道中是否有时间发生

只有连接/通道上真正有读写事件发生时,才会进行读写

避免了多线程上下文切换导致的开销

1. SelectionKey在NIO体系中的作用

  1. 当客户端连接时, 会通过ServerSocketChannel得到对应的SocketChannel;
  2. 将SocketChannel注册到Selector上, 使用的是register(Selector sel, int ops), 一个selector上可以注册多个SocketChannel;
  3. 注册后会返回一个SelectionKey , 会和该Selector关联起来
  4. Selector进行监听selector方法, 返回有事件发生的channel;
  5. 进一步得到各个有事件发生的SelectionKey , 并通过SelectionKey反向获取SocketChannel的channel
  6. 根据得到的channel完成业务处理

七、NIO非阻塞网络编程的快速入门

服务器端

public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //创建一个Selector对象,
        Selector selector = Selector.open();

        // 绑定端口6666, 在服务器端监听
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
        // 设置为非阻塞
        serverSocketChannel.configureBlocking(false);

        // 把serverSocketChannel注册到selector
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 循环等待用户连接
        while (true){
            if (selector.select(1000) == 0){ //等待(阻塞)一秒, 没有事件发生
//            if (selector.selectNow() == 0){ // 也可以设置成非阻塞的
                System.out.println("服务器等待了一秒,无连接");
                continue;
            }

            // 如果返回的>0 , 就获取相关的selectionKey集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 返回关注事件的集合

            // 遍历selectionKeys
            Iterator<SelectionKey> keyIterator = selectionKeys.iterator();

            while (keyIterator.hasNext()){
                // 获取到selectionKey
                SelectionKey key = keyIterator.next();
                //根据key对应的通道获取事件并做相应处理
                if (key.isAcceptable()){
                    //如果是OP_ACCEPT, 表示有新的客户端产生
                    //给该客户端生成SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //将socketChannnel设置为非阻塞
                    socketChannel.configureBlocking(false);
                    //将socketChannel注册到selector上, 设置事件为OP_READ,同时给socketChannel关联一个buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }

                if (key.isReadable()){
                    // 发生了OP_READ
                    SocketChannel channel=(SocketChannel)key.channel();
                    ByteBuffer buffer = (ByteBuffer)key.attachment();
                    channel.read(buffer);
                    System.out.println("from 客户端"+new String(buffer.array()));
                }

                // 手动从集合中移除当前的selectionKey, 防止多线程情况下的重复操作
                keyIterator.remove();

            }


        }

    }
}
 

客户端

public class NIOClient {

    public static void main(String[] args) throws IOException {
        // 得到一个网络通道
        SocketChannel socketChannel = SocketChannel.open();
        // 设置为非阻塞
        socketChannel.configureBlocking(false);
        //设置服务器端ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        if (!socketChannel.connect(inetSocketAddress)){

            while (!socketChannel.finishConnect()){
                //如果没有连接成功,客户端是非阻塞的,可以做其它工作
                System.out.println("等待连接...");
            }
        }

        // 如果连接成功,就发送数据
        String str = "hello world";
        ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
        // 发送数据 , 将buffer中的数据写入到channel中
        socketChannel.write(buffer);
        System.in.read();

    }

}
 
 

标签:Java,NIO,Buffer,编程,buffer,byteBuffer,ByteBuffer,Channel
From: https://www.cnblogs.com/jobbible/p/16913990.html

相关文章

  • java学习
     一、前言1、JRE和JDKJREJava程序的运行环境,包含JVM(Java虚拟机)和运行时所需要的核心类库JDKJava程序开发工具包,包含JRE和开发人员使用的工具(编译工具:javac.ex......
  • 自己学网页编程。
    自学网页编程的CSS代码展示:*{  margin:0;  padding:0;}.w{  width:1200px;  margin:auto;}body{  background-color:#f3f5f7; ......
  • Java - HuTool工具的介绍与使用(总)
    本篇主要介绍HuTool工具,其是java工具类,对于一些静态方法进行封装,虽然很小,但很全,里面拥有平时我们会用到的工具类,就无需我们自己去封装了以前我还自己去封装,emmm,我真的......
  • Java Swing 学习记录
    目录参考资料什么是Swing?Swing和AWTSwing组件1JFrame窗口2JPanel面板3LayoutManager布局管理器3.1边框布局管理器3.2流式布局管理器3.3卡片布局管理器3.4网......
  • Javac编译器
    Javac编译器Java技术下的编译期在不同的语境下会有不同的编译器实现产品:1、前端编译器:jdk的javac、EclipseJDT中的增量式编译器2、即时编译器:hotspot虚拟机中的c1、c2......
  • java15源码-ThreadPoolExecutor
    一Executors工具类创建ThreadPoolExecutorSingleThreadExecutornewFixedThreadPoolnewCachedThreadPoolnewScheduledThreadPoolnewSingleThreadScheduledExecuto......
  • JAVA-动漫美女拼图游戏
    代码1packagecom.itheima_02;publicclassApp{publicstaticvoidmain(String[]args){PictureFramepf=newPictureFrame();}}代码2p......
  • 【Java】Redis保存Java对象
    1.前言这是一篇来自2018年的文章,当时已经在现在这家公司工作。公司刚起步是购买外包公司产品做定制化开发,在开发微信版的过程中遇到了一个问题。由于微信端需要通过H5的入......
  • Java自定义类加载器
    通过继承java.lang.ClassLoader可以自定义类加载器。步骤:1、继承ClassLoader。2、读取字节文件到字节数组中。3、使用defineClass(类名,字节数组,off,length):将字节数组转换......
  • Java开发学习(四十二)----MyBatisPlus查询语句之条件查询
    一、条件查询的类MyBatisPlus将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合。这个我们在前面都有见过,比如查询所有和分页查询的时候,都有看到......