NIO系列:2.NIO总结

写在前面的话:

本文是对NIO的一些总结,对于需要从零基础入门NIO的朋友,可以看下面这篇文章:
如何学习Java的NIO? - Java3y的回答 - 知乎
NIO主要有三个核心部分组成:
1)buffer缓冲区
2)Channel管道
3)Selector选择器

个人学习感受就是,只要能理解NIO的三个核心部分,那么就能基本掌握了NIO的使用以及体会NIO的整体思想。
下面开始对NIO的三个核心部分做一些总结。

------------------------------------------ 我是分割线 ------------------------------------------

缓冲区(Buffer)

缓冲区(Buffer):缓冲区本质上是一个可以读写数据的内存块,可以理解成是一个容器对象(含数组),该对象提供了一组方法,可以更轻松地使用内存块,缓冲区对象内置了一些机制,能够跟踪和记录缓冲区的状态变化情况。Channel 提供从文件、网络读取数据的渠道,但是读取或写入的数据都必须经由 Buffer
Buffer类及其子类:
在这里插入图片描述
常用Buffer子类一览:
1)ByteBuffer,存储字节数据到缓冲区
2)ShortBuffer,存储字符串数据到缓冲区
3)CharBuffer,存储字符数据到缓冲区
4)IntBuffer,存储整数数据到缓冲区
5)LongBuffer,存储长整型数据到缓冲区
6)DoubleBuffer,存储小数到缓冲区
7)FloatBuffer,存储小数到缓冲区

Buffer示例:

import java.nio.IntBuffer;

public class BasicBuffer {
    
    public static void main(String[] args) {

        //举例说明Buffer 的使用 (简单说明)
        //创建一个Buffer, 大小为 5, 即可以存放5个int
        IntBuffer intBuffer = IntBuffer.allocate(5);

        //向buffer 存放数据
        intBuffer.put(10);
        intBuffer.put(11);
        intBuffer.put(12);
        intBuffer.put(13);
        intBuffer.put(14);
        for(int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put( i * 2);
        }

        //如何从buffer读取数据
        //将buffer转换,读写切换
        intBuffer.flip();
        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
}

打印结果:

0
2
4
6
8
通道(Channel)

NIO的通道类似于流,但有些区别如下:
1)通道可以同时进行读写,而流只能读或者只能写
2)通道可以实现异步读写数据
3)通道可以从缓冲读数据,也可以写数据到缓冲:

Channel在NIO中是一个接口,常用的 Channel 类有:
FileChannel、DatagramChannel、ServerSocketChannel 和 SocketChannel
(ServerSocketChanne 类似 ServerSocket , SocketChannel 类似 Socket)
FileChannel用于文件的数据读写,DatagramChannel用于UDP的数据读写,ServerSocketChannel和SocketChannel用于TCP的数据读写。
在这里插入图片描述
FileChannel主要用来对本地文件进行 IO 操作,常见的方法有:
1)public int read(ByteBuffer dst) ,从通道读取数据并放到缓冲区中
2)public int write(ByteBuffer src) ,把缓冲区的数据写到通道中
3)public long transferFrom(ReadableByteChannel src, long position, long count),从目标通道中复制数据到当前通道
4)public long transferTo(long position, long count, WritableByteChannel target),把数据从当前通道复制给目标通道

MappedByteBuffer可以让文件直接在内存(堆外的内存)中进行修改, 而如何同步到文件由NIO来完成
前面我们讲的读写操作,都是通过一个Buffer 完成的,NIO 还支持通过多个Buffer (即 Buffer 数组) 完成读写操作,即 Scattering 和 Gathering

下面贴上一些Channel示例。

Channel示例1,使用前面学习后的 ByteBuffer(缓冲) 和 FileChannel(通道), 将 “hello,nio” 写入到 file01.txt 中:

import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
 
public class NIOFileChannel01 {
    public static void main(String[] args) throws Exception{
 
        String str = "hello,nio";
        //创建一个输出流->channel
        FileOutputStream fileOutputStream = new FileOutputStream("d:\\file01.txt");
 
        //通过 fileOutputStream 获取 对应的 FileChannel
        //这个 fileChannel 真实 类型是  FileChannelImpl
        FileChannel fileChannel = fileOutputStream.getChannel();
 
        //创建一个缓冲区 ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
 
        //将 str 放入 byteBuffer
        byteBuffer.put(str.getBytes());
 
 
        //对byteBuffer 进行flip
        byteBuffer.flip();
 
        //将byteBuffer 数据写入到 fileChannel
        fileChannel.write(byteBuffer);
        fileOutputStream.close();
    }
}

Channel示例2,使用前面学习后的 ByteBuffer(缓冲) 和 FileChannel(通道), 将 file01.txt 中的数据读入到程序,并显示在控制台屏幕:

import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
 
public class NIOFileChannel02 {
    public static void main(String[] args) throws Exception {
 
        //创建文件的输入流
        File file = new File("d:\\file01.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
 
        //通过fileInputStream 获取对应的FileChannel -> 实际类型  FileChannelImpl
        FileChannel fileChannel = fileInputStream.getChannel();
 
        //创建缓冲区
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
 
        //将 通道的数据读入到Buffer
        fileChannel.read(byteBuffer);
 
        //将byteBuffer 的 字节数据 转成String
        System.out.println(new String(byteBuffer.array()));
        fileInputStream.close();
    }
}

Channel示例3,使用 FileChannel(通道) 和方法 read\write,完成文件的拷贝:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
 
public class NIOFileChannel03 {
    public static void main(String[] args) throws Exception {
 
        FileInputStream fileInputStream = new FileInputStream("1.txt");
        FileChannel fileChannel01 = fileInputStream.getChannel();
 
        FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
        FileChannel fileChannel02 = fileOutputStream.getChannel();
 
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
 
        while (true) { //循环读取
 
            //这里有一个重要的操作,一定不要忘了
            byteBuffer.clear(); //清空buffer
            int read = fileChannel01.read(byteBuffer);
            System.out.println("read =" + read);
            if(read == -1) { //表示读完
                break;
            }
            //将buffer 中的数据写入到 fileChannel02 -- 2.txt
            byteBuffer.flip();
            fileChannel02.write(byteBuffer);
        }
 
        //关闭相关的流
        fileInputStream.close();
        fileOutputStream.close();
    }
}
选择器(Selector)

1)Java 的 NIO,用非阻塞的 IO 方式。可以用一个线程,处理多个的客户端连接,就会使用到Selector(选择器)
2)Selector 能够检测多个注册的通道上是否有事件发生(注意:多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。
3)只有在 连接/通道 真正有读写事件发生时,才会进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程
4)避免了多线程之间的上下文切换导致的开销

Selector类相关方法:

public abstract class Selector implements Closeable { 
    public static Selector open();              //得到一个选择器对象
    public int select(long timeout);            //监控所有注册的通道,当其中有 IO 操作可以进行时,将
                                                //对应的 SelectionKey 加入到内部集合中并返回,参数用来设置超时时间
    public Set<SelectionKey> selectedKeys();    //从内部集合中得到所有的 SelectionKey	
}

NIO中的 ServerSocketChannel功能类似BIO中的ServerSocket,SocketChannel功能类似Socket

SelectionKey相关方法:

public abstract class SelectionKey {
    public abstract Selector selector();               //得到与之关联的 Selector 对象
    public abstract SelectableChannel channel();       //得到与之关联的通道
    public final Object attachment();                  //得到与之关联的共享数据
    public abstract SelectionKey interestOps(int ops); //设置或改变监听事件
    public final boolean isAcceptable();               //是否可以accept
    public final boolean isReadable();                 //是否可以读
    public final boolean isWritable();                 //是否可以写
}

在这里插入图片描述
ServerSocketChannel 在服务器端监听新的客户端 Socket 连接,功能类似ServerSocket:

public abstract class ServerSocketChannel extends AbstractSelectableChannel implements NetworkChannel {
    public static ServerSocketChannel open()                         //得到一个 ServerSocketChannel 通道
    public final ServerSocketChannel bind(SocketAddress local)       //设置服务器端端口号
    public final SelectableChannel configureBlocking(boolean block)  //设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
    public SocketChannel accept()                                    //接受一个连接,返回代表这个连接的通道对象
    public final SelectionKey register(Selector sel, int ops)        //注册一个选择器并设置监听事件
}

在这里插入图片描述
SocketChannel,网络 IO 通道,具体负责进行读写操作,功能类似Socket:

public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel, NetworkChannel{
    public static SocketChannel open();                             //得到一个 SocketChannel 通道
    public final SelectableChannel configureBlocking(boolean block);//设置阻塞或非阻塞模式,取值 false 表示采用非阻塞模式
    public boolean connect(SocketAddress remote);                   //连接服务器
    public boolean finishConnect();                                 //如果上面的方法连接失败,接下来就要通过该方法完成连接操作
    public int write(ByteBuffer src);                               //往通道里写数据
    public int read(ByteBuffer dst);                                //从通道里读数据
    public final SelectionKey register(Selector sel, int ops, Object att);//注册一个选择器并设置监听事件,最后一个参数可以设置共享数据
    public final void close();                                      //关闭通道
}

在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章