Java NIO

Java NIO简介

  • Java NIO(New IO / Non Blocking IO),从java1.4版本开始引入的一个新的IO API,可以替代标准的Java IO API.NIO与原来的IO有同样的作用和目的,但是使用方式完全不同,NIO支持面向缓冲区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作

NIO 与 IO的主要区别

IONIO
面向流(Stream Oriented)面向缓冲区(Buffer Oriented)
阻塞IO(Blocking IO)非阻塞IO(Non Blocking IO)
选择器(Selectors)

通道和缓冲区

  • Java NIO系统的核心在于:通道(Channel)和缓冲区(Buffer)。通道表示打开到IO设备(例如:文件、套接字)的连接。若需要使用NIO系统,需要获取用于连接IO设备的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理
  • 简而言之,Channel负责传输,Buffer负责存储

缓冲区的数据读取

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

在这里插入图片描述
在这里插入图片描述

package com.atguigu.nio;
import org.junit.Test;

import java.nio.ByteBuffer;

/**
 * 一、缓冲区(Buffer):在Java NIO中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据
 *
 * 根据数据类型不同(boolean除外),提供了相应类型的缓冲区:
 * ByteBuffer
 * CharBuffer
 * ShortBuffer
 * IntBuffer
 * LongBuffer
 * FloatBuffer
 * DoubleBuffer
 * 上述缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区
 *
 * 二、缓冲区存取数据的两个核心方法
 * put(): 存入数据到缓冲区中
 * get(): 获取缓冲区中的数据
 *
 * 四、缓冲区中的四个核心属性:
 * capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变
 * limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后的数据不能进行读写)
 * position : 位置,表示缓冲区中正在操作数据的位置
 *
 * mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置
 *
 * 0 <= mark <= position <= limit <= capacity
 *
 * 五、直接缓冲区与非直接缓冲区:
 * 非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在JVM内存中
 * 直接缓冲区: 通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
 *
 */
public class TestBuffer {

    @Test
    public void test3() {
        //创建直接缓冲区
        ByteBuffer buf = ByteBuffer.allocateDirect(1024);

        //判断是否直接缓冲区
        System.out.println(buf.isDirect());
    }

    @Test
    public void test2() {
        String str = "abcde";
        ByteBuffer buf = ByteBuffer.allocate(1024);

        buf.put(str.getBytes());
        buf.flip();
        byte[] dst = new byte[buf.limit()];
        buf.get(dst, 0, 2);
        System.out.println(new String(dst, 0, 2));

        System.out.println(buf.position());

        //mark() : 标记
        buf.mark();

        buf.get(dst, 2, 2);
        System.out.println(new String(dst, 2, 2));

        System.out.println(buf.position());
        //reset() : 恢复 position 到 mark位置
        buf.reset();
        System.out.println(buf.position());

        //判断缓冲区中是否还有剩余的数据
        if (buf.hasRemaining()) {
            //获取缓冲区中可以操作的数量
            System.out.println(buf.remaining());

        }
    }


    @Test
    public void test1() {
        String str = "abcde";
        //1.分配一个指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //2.使用 put() 存入数据到缓冲区中
        buf.put(str.getBytes());

        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //3.切换到读取数据模式
        buf.flip();

        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //4.读取缓冲区的数据
        byte[] bytes = new byte[buf.limit()];
        buf.get(bytes);
        System.out.println(new String(bytes, 0, bytes.length));

        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //5.rewind()  可重复读数据
        buf.rewind();

        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());

        //6.clear() 清空缓冲区。但是缓冲区中的数据依然存在,但是出于“被遗忘”状态
        buf.clear();
        System.out.println(buf.position());
        System.out.println(buf.limit());
        System.out.println(buf.capacity());
    }
}

通道(Channel)

  • 通道(Channel):Channel表示IO源于目标打开的连接。Channel类似于传统的"流"。只不过Channel本身不能访问数据,Channel只能与Buffer进行交互

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

package com.atguigu.nio;

import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

/**
 * 一、通道(Channel): 用于源节点与目标节点的连接,在 Java NIO 中负责缓冲区中数据的传输。Channel 本身不存储数据,因此需要配合缓冲区进行传输
 *
 * 二、通道的主要实现类
 *  java.nio.channels.Channel 接口:
 *      |--FileChannel
 *      |--SocketChannel
 *      |--ServerSocketChannel
 *      |--DatagramChannel
 *
 *
 * 三、获取通道
 * 1. Java 针对支持通道的类提供了 getChannel() 方法
 *      本地IO:
 *      FileInputStream/FileOutputStream
 *      RandomAccessFile
 *      网络IO:
 *      Socket
 *      ServerSocket
 *      DatagramSocket
 *
 * 2.在 JDK 1.7 中的 NIO.2 针对各个通道提供了一个静态方法 open()
 * 3.在 JDK 1.7 中的 NIO.2 的 Files 功具类的 newByteChannel()
 *
 * 四、通道之间的数据传输
 * transferFrom()
 * transferTo()
 *
 * 五、分散(Scatter)与聚集(Gather)
 * 分散读取(Scattering Reads): 将通道中的数据分散到多个缓冲区中
 * 聚集写入(Gathering Writes): 将多个缓冲区中的数据聚集到通道中
 *
 * 六、字符集: Charset
 * 编码:字符串->字节数组
 * 解码:字节数组->字符串
 */
public class TestChannel {

    @Test
    public void test6() throws CharacterCodingException {
        Charset cs1 = Charset.forName("GBK");
        //获取编码器
        CharsetEncoder ce = cs1.newEncoder();
        //获取解码器
        CharsetDecoder cd = cs1.newDecoder();

        CharBuffer cBuf = CharBuffer.allocate(1024);
        cBuf.put("哈哈哈哈哈哈");

        cBuf.flip();

        //编码
        ByteBuffer bf = ce.encode(cBuf);
        for (int i = 0; i < 12; i++) {
            System.out.println(bf.get(i));
        }

        //解码
        CharBuffer cBuf1 = cd.decode(bf);
        System.out.println(cBuf1.toString());

    }


    //字符集
    @Test
    public void test5() {
        SortedMap<String, Charset> map = Charset.availableCharsets();
        Set<Map.Entry<String, Charset>> set =map.entrySet();

        for (Map.Entry<String, Charset> entry : set) {
            System.out.println(entry.getKey() + "=" + entry.getValue());

        }

    }

    @Test
    public void test4() throws IOException {
        RandomAccessFile raf1 = new RandomAccessFile("1.txt", "rw");

        //1.获取通道
        FileChannel channel1 = raf1.getChannel();

        //2.分配指定大小的缓冲区
        ByteBuffer buf1 = ByteBuffer.allocate(100);
        ByteBuffer buf2 = ByteBuffer.allocate(1024);

        //3.分散读取
        ByteBuffer[] bufs = {buf1, buf2};
        channel1.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 raf2 = new RandomAccessFile("2.txt", "rw");
        FileChannel channel2 = raf2.getChannel();

        channel2.write(bufs);

        channel2.close();
        channel1.close();

    }

    //通道之间的数据传输(直接缓冲区)
    @Test
    public void test3() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("7.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

//        inChannel.transferTo(0, inChannel.size(), outChannel);
        outChannel.transferFrom(inChannel, 0, inChannel.size());
        inChannel.close();
        outChannel.close();
    }


    //使用直接缓冲区完成文件的复制(内存映射文件)
    @Test
    public void test2() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("1.jpg"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("5.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE);

        //内存映射文件
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

        //直接对缓冲区进行数据的读写操作
        byte[] dst = new byte[inMappedBuf.limit()];
        inMappedBuf.get(dst);
        outMappedBuf.put(dst);

        inChannel.close();
        outChannel.close();


    }


    //利用通道完成文件的复制(非直接缓冲区)
    @Test
    public void test1() {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            fis = new FileInputStream("1.jpg");
            fos = new FileOutputStream("2.jpg");

            //1.获取通道
            inChannel = fis.getChannel();
            outChannel = fos.getChannel();

            //2.分配指定大小的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);

            //3.将通道中的数据存入缓冲区中
            while (inChannel.read(buf) != -1) {
                //切换成读数据模式
                buf.flip();
                //4.将缓冲区中的数据写入通道
                outChannel.write(buf);
                //清空缓冲区
                buf.clear();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inChannel != null) {
                try {
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
}

NIO的非阻塞式网络通信

非阻塞式TCP通信

package com.atguigu.nio;
import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

/**
 * 一、使用NIO完成网络通信的三个核心
 *
 * 1.通道(Channel):负责连接
 *
 *  java.nio.channels.Channel接口
 *      |--SelectableChannel
 *          |--SocketChannel
 *          |--ServerSocketChannel
 *          |--DatagramChannel
 *
 *          |--Pipe.SinkChannel
 *          |--Pipe.SourceChannel
 *
 * 2.缓冲区(Buffer): 负责数据的存取
 *
 * 3.选择器(Selector): 是SelectableChannel 的多路复用器。用于监控 SelectableChannel的IO状况
 *
 */
public class TestNonBlackingNIO {

    //客户端
    public static void main(String[] args) throws IOException {
        //1.获取通道
        SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
        //2.获取指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);
        //3.切换通道为非阻塞模式
        sChannel.configureBlocking(false);
        //4.准备要写出的数据
        Scanner scan = new Scanner(System.in);
        while (scan.hasNext()) {
            String str = scan.next();

            buf.put((LocalDateTime.now() + ":" + str).getBytes());
            //5.切换buf为写出模式
            buf.flip();
            //6.将数据通过sChannel写出
            sChannel.write(buf);
            //7.清理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) {
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> it = keys.iterator();
            //7.遍历已经就绪的key
            while (it.hasNext()) {
                SelectionKey sk = it.next();
                //8.判断具体是什么事件准备就绪
                if (sk.isAcceptable()) {
                    //9.若"接收就绪",获取客户连接
                    SocketChannel sChannel = ssChannel.accept();
                    //10.切换非阻塞模式
                    sChannel.configureBlocking(false);
                    //11.将该通道注册到选择器上
                    sChannel.register(selector, SelectionKey.OP_READ);
                } else if (sk.isReadable()) {
                    //12.如果是“读就绪”,获取当前选择器上的"读就绪"状态的通道
                    SocketChannel sChannel = (SocketChannel) sk.channel();
                    //13.读取数据
                    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();
                    }

                }
                //取消选择器
                it.remove();
            }
        }
    }
}

非阻塞式UDP通信

package com.atguigu.nio;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
import java.util.Scanner;

public class TestNonBlockingNIO2 {

    public static void main(String[] args) throws IOException {
        DatagramChannel dc = DatagramChannel.open();

        dc.configureBlocking(false);

        ByteBuffer buf = ByteBuffer.allocate(1024);

        Scanner scan = new Scanner(System.in);

        while (scan.hasNext()) {
            String str = scan.next();
            buf.put(str.getBytes());
            buf.flip();
            dc.send(buf, new InetSocketAddress("127.0.0.1", 8989));
            buf.clear();
        }
        dc.close();
    }

    @Test
    public void receive() throws IOException {
        DatagramChannel dc = DatagramChannel.open();
        dc.configureBlocking(false);
        dc.bind(new InetSocketAddress(8989));
        Selector selector = Selector.open();
        dc.register(selector, SelectionKey.OP_READ);

        while (selector.select() > 0) {
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey sk = it.next();
                if (sk.isReadable()) {
                    ByteBuffer buf = ByteBuffer.allocate(1024);
                    dc.receive(buf);
                    buf.flip();
                    System.out.println(new String(buf.array(), 0, buf.limit()));
                    buf.clear();
                }
                it.remove();
            }
        }
    }
}

管道(Pipe)

  • Java NIO管道是2个线程之间的单向数据连接。Pipe有一个source通道和一个sink通道。数据会被写到sink通道,从source通道读取。
package com.atguigu.nio;

import org.junit.Test;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.Pipe;

public class TestPipe {

    @Test
    public void test1() throws IOException {
        //1.获取管道
        Pipe pipe = Pipe.open();
        //2.将缓冲区的数据写入管道
        ByteBuffer buf = ByteBuffer.allocate(1024);

        Pipe.SinkChannel sinkChannel = pipe.sink();

        buf.put("通过单向管道写数据".getBytes());
        buf.flip();
        sinkChannel.write(buf);

        //3.读取缓冲区中的数据
        buf.flip();
        Pipe.SourceChannel sourceChannel = pipe.source();
        sourceChannel.read(buf);
        System.out.println(new String(buf.array(), 0, buf.limit()));

        sourceChannel.close();
        sinkChannel.close();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值