这次终于把Java NIO搞懂了

java1234

共 373字,需浏览 1分钟

 · 2020-10-15

点击上方蓝色字体,选择“标星公众号”

优质文章,第一时间送达

66套java从入门到精通实战课程分享

Java NIO 和 IO 的区别

缓冲区存取数据的两个核心方法

put:存入数据到缓冲区

get:获取缓冲区中的数据

缓冲区的四个核心属性

capacity:容量,表示缓冲区中最大存储数据的容量,一旦声明不能改变

position:位置,表示缓冲区中正在操作数据的位置

limit:界限,表示缓冲区中可以操作数据的大小。(limit后的数据不能进行读写)

mark:标记,表示记录当前position的位置,可以通过reset恢复到mark的位置

0 <= mark <= position <= limit <= capacity

直接缓冲区与非直接缓冲区

非直接缓冲区:通过allocate()方法分配的缓冲区,将缓冲区建立在JVM的内存中

直接缓冲区:通过allocateDirect()方法分配的缓冲区,将缓冲区建立在操作系统的物理内存中。可以提高效率

非直接缓冲区工作原理图

直接缓冲区工作原理图

通道

通道(Channel):由java.nio.channels包定义的。channel表示IO源与目标打开的连接。channel类似于传统的“流”。只不过channel本身不能直接访问数据,channel只能与buffer进行交互,在Java NIO中负责缓冲区数据的传输。

应用程序向系统发起读写请求,调用操作系统的IO接口,IO接口由CPU统一调配,当读写请求过大,会大大占用CPU的资源,会严重影响效率,CPU要处理大量的IO请求,分配IO接口,就没法做其他事情了。

CPU:中央处理器

进行了修改,添加了DMA,直接存储器;当应用程序向操作系统发起IO请求,首先DMA会向CPU申请权限,如果CPU给与权限,那么后续的读写请求就全权由DMA负责操作;这样的好处就是在执行IO请求时,CPU可以不进行干预,去处理其他事情

但是DMA仍然有缺点,比如当一个大型的应用程序发起大量的IO请求,DMA仍然要向CPU请求资源,影响效率

在IO接口和内存之间,会有一个DMA传输数据总线

通道,可以理解为一个完全独立的处理器,专门用于IO操作;通道仍然依附于CPU,但是它有自己的一套指令,是独立的处理器

通道的主要实现类

在java.nio.channels.Channel接口:

    |--FileChannel:文件通道,专门用于操作本地文件,用于本地文件传输

    |--SocketChannel

    |--ServerSocketChannel

    |--DatagramChannel

 

SocketChannel 和 ServerSocketChannel 用于TCP;DatagramChannel 用于UDP(UDP,User Datagram Protocol)

后三个都是用于网络IO

 

获取通道

JDK1.7以后有三种方式

1、Java针对支持通道的类提供了getChannel()方法

        本地IO:FileInputStream/FileOutputStream/RandomAccessFile

        网络IO:Socket/ServerSocket/DatagramSocket

2、在JDK1.7中的NIO.2 针对各个通道提供了一个静态方法 open()

3、在JDK1.7中的NIO.2 的Files工具栏的newByteChannel()

//用非直接通道完成文件的传输
@Test
public void test5(){
    FileInputStream fis = null;
    FileOutputStream fos = null;
    FileChannel inChannel = null;
    FileChannel outChannel = null;
    try {
        //获取文件流
        fis = new FileInputStream("E:\\休闲生活\\桌面壁纸\\王丽坤.jpg");
        fos = new FileOutputStream("E:\\休闲生活\\桌面壁纸\\2.jpg");
 
 
        // 1. 获取通道
        inChannel = fis.getChannel();
        outChannel = fos.getChannel();
 
 
        // 2. 分配缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
 
 
        // 3. 读取数据
        while (inChannel.read(buf) != -1){
            // 4. 切换读模式
            buf.flip();
            // 5. 写数据
            outChannel.write(buf);
            buf.clear();    // 缓冲区循环重复读写数据
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 6. 关闭通道,关闭流
        if(inChannel!=null){
            try {
                inChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(outChannel!=null){
            try {
                outChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fis!=null){
            try {
                fis.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(fos!=null){
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


//用直接通道完成文件的传输
@Test
public void test6(){
    FileChannel inChannel = null;
    FileChannel outChannel = null;
    try {
        //FileChannel.open()的两个参数:路径path,模式
        //StandardOpenOption.READ 读模式
        inChannel = FileChannel.open(Paths.get("E:\\休闲生活\\桌面壁纸\\王丽坤.jpg"), StandardOpenOption.READ);
        //StandardOpenOption.CREATE_NEW 创建模式,当路径下有同名文件时报错,没有就创建
        //StandardOpenOption.CREATE 创建模式,当路径下有同名文件时会覆盖,没有就创建
        outChannel = FileChannel.open(Paths.get("E:\\休闲生活\\桌面壁纸\\2.jpg"), StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
 
 
        // 内存映射文件
        MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());
 
 
        //读写文件
        byte[] bytes = new byte[inMappedBuffer.limit()];
        inMappedBuffer.get(bytes);
        outMappedBuffer.put(bytes);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if(inChannel!=null){
            try {
                inChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(outChannel!=null){
            try {
                outChannel.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}


通道间的数据传输

transferTo()

transferFrom()

    // 通道之间的数据传输
    @Test
    public void test7() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("E:\\学习视频\\JavaNIO\\nio\\1. 尚硅谷_NIO_NIO 与 IO 区别.avi"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("E:\\学习视频\\JavaNIO\\nio\\1.avi"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);
 
        inChannel.transferTo(0,inChannel.size(),outChannel);
//        outChannel.transferFrom(inChannel,0,inChannel.size());
 
        inChannel.close();
        outChannel.close();
    }


分散(Scatter)与聚集(Gather)

分散读取:Scattering Reads,将通道中的数据分散到多个缓冲区中

聚集写入:Gathering Writes,将多个缓冲区中的数据聚集到通道中

//分散与聚集
@Test
public void test8() throws IOException {
    RandomAccessFile file = new RandomAccessFile("C:\\Users\\FMM.000\\Desktop\\spring ioc流程.txt","rw");
    // 获取通道
    FileChannel fileChannel = file.getChannel();
    // 分配指定大小的缓冲区
    ByteBuffer buf1 = ByteBuffer.allocate(100);
    ByteBuffer buf2 = ByteBuffer.allocate(1024);
    // 分散读取
    ByteBuffer[] bufs = {buf1,buf2};
    fileChannel.read(bufs);
 
 
    for(ByteBuffer buffer : bufs){
        buffer.flip();
    }
    System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
    System.out.println("-------------------------------------");
    System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));
 
 
    // 聚集写入
    RandomAccessFile file1 = new RandomAccessFile("C:\\Users\\FMM.000\\Desktop\\ioc流程.txt","rw");
    FileChannel fileChannel1 = file1.getChannel();
    fileChannel1.write(bufs);
    // 关闭通道
    fileChannel.close();
    fileChannel1.close();
}


字符集Charset

编码:字符串--->字节数组

解码:字节数组--->字符串

// 编码解码
@Test
public void test10() throws CharacterCodingException {
    Charset charset = Charset.forName("GBK");
    // 获取编码器
    CharsetEncoder encoder = charset.newEncoder();
    // 获取解码器
    CharsetDecoder decoder = charset.newDecoder();
 
 
    CharBuffer charBuffer = CharBuffer.allocate(1024);
    charBuffer.put("尚硅谷威武!");
    // 切换读模式
    charBuffer.flip();
 
 
    // 编码
    ByteBuffer buffer = encoder.encode(charBuffer);
    for (int i = 0; i < 12; i++) {
        System.out.println(buffer.get());
    }
    // 解码
    buffer.flip();
    CharBuffer cb = decoder.decode(buffer);
    System.out.println(cb.toString());
    System.out.println("============================");
    buffer.flip();
    Charset cs = Charset.forName("UTF-8");
    CharBuffer cBuf = cs.decode(buffer);
    System.out.println(cBuf.toString());
}


使用NIO完成网络通信的三大核心:

1、通道(Channel):负责连接

    java.nio.channels.Channel 接口:

        |--SelectableChannel

            |--SocketChannel

            |--ServerSocketChannel    //上两个是TCP

            |--DatagramChannel        //UDP,都是用于网络 IO

 

            |--Pipe.SinkChannel

            |--Pipi.SourceChannel

 

2、缓冲区(Buffer):负责数据的存取

3、选择器(Selector):是 SelectableChannel 的多路复用器,用于监控 SelectableChannel 的 IO 状况

        SelectionKey:表示 SelectableChannel 和 Selector 之间的注册关系。每次向选择器注册通道时就会选泽一个事件(选择键)

示例1:阻塞式IO

当client端向server端发送请求时,如果server端不能确定client请求的读/写的数据,server端会处于阻塞状态,阻塞状态下server端下的此线程不能做其他操作,一直等待,当server有client端需要读/写的数据时,会将数据读/写给用户,然后释放资源

// 模拟网络IO,客户端
@Test
public void nioClient(){
    SocketChannel socketChannel = null;
    FileChannel fileChannel = null;
    try {
        // 1. 获取通道
        socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
 
        fileChannel = FileChannel.open(Paths.get("E:\\休闲生活\\桌面壁纸\\无情的战争.jpg"), StandardOpenOption.READ);
        // 2. 分配指定大小缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        // 3. 读取本地文件,并发送到服务器
        while (fileChannel.read(buf)!=-1){
            buf.flip();
            socketChannel.write(buf);
            buf.clear();
        }
 
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 关闭通道
        try {
            if(fileChannel!=null){
                fileChannel.close();
            }
            if (socketChannel!=null){
                socketChannel.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
 
// 服务端
@Test
public void nioServer(){
    ServerSocketChannel ssChannel = null;
    FileChannel outChannel = null;
    try {
        // 1. 获取通道
        ssChannel = ServerSocketChannel.open();
        outChannel = FileChannel.open(Paths.get("E:\\休闲生活\\桌面壁纸\\2.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
        // 2. 绑定连接
        ssChannel.bind(new InetSocketAddress(9898));
        // 3. 获取客户端通道
        SocketChannel sChannel = ssChannel.accept();
        // 4. 分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        // 5. 接收客户端数据,并保存到本地
        while (sChannel.read(buf)!=-1){
            buf.flip();
            outChannel.write(buf);
            buf.clear();
        }
 
 
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        // 关闭通道
        try {
            if(outChannel!=null){
                outChannel.close();
            }
            if(ssChannel!=null){
                ssChannel.close();
            }
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}


非阻塞式IO

示例1:非阻塞式IO

public class TestNonBlockingIO {
 
 
    @Test
    public void client() throws IOException {
        //获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        //切换成非阻塞式
        sChannel.configureBlocking(false);
        //分配指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //发送数据给服务端
//        buf.put(LocalDateTime.now().toString().getBytes());
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String str = scanner.next();
            buf.put((new Date().toString() +"\n" + str).getBytes());
            buf.flip();
            sChannel.write(buf);
            buf.clear();
        }
 
 
        //关闭通道
        sChannel.close();
    }
 
 
    @Test
    public void server() throws IOException {
        // 1. 获取通道
        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        // 2. 切换非阻塞式
        ssChannel.configureBlocking(false);
        // 3. 绑定连接
        ssChannel.bind(new InetSocketAddress(9898));
        // 4. 获取选择器
        Selector selector = Selector.open();
        // 5. 将通道注册到选择器上,并指定“监听连接事件”
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 6. 轮询式的获取选择器上以及“准备就绪”的事件
        while (selector.select()>0){
            // 7. 获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
            Iterator it = selector.selectedKeys().iterator();
            while (it.hasNext()){
                // 8. 获取准备“就绪”的事件
                SelectionKey sk = it.next();
                // 9. 判断具体是什么事件准备就绪
                if(sk.isAcceptable()){
                    // 10. 若“接收就绪”,获取客户端连接
                    SocketChannel sChannel = ssChannel.accept();
                    // 11. 切换非阻塞模式
                    sChannel.configureBlocking(false);
                    // 12. 将该通道注册到选择器上
                    sChannel.register(selector,SelectionKey.OP_READ);
                } else if(sk.isReadable()){
                    // 13. 获取当前选择器上“读就绪”状态的通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    // 14. 读取数据
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    int len = 0;
                    while ((len = sChannel.read(buf))>0){
                        buf.flip();
                        System.out.println(new String(buf.array(),0,len));
                        buf.clear();
                    }
                }
                // 15. 取消选择键,SelectionKey
                it.remove();
            }
        }
    }
}




版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:

https://blog.csdn.net/mrcool2012/article/details/108972988




粉丝福利:108本java从入门到大神精选电子书领取

???

?长按上方锋哥微信二维码 2 秒
备注「1234」即可获取资料以及
可以进入java1234官方微信群



感谢点赞支持下哈 

浏览 21
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报