java NIO

java NIO

简介

NIO是java 1.4版本引进的一个新的IO API, NIO 支持面向缓冲区的,通过通道的IO操作。

IONIO
面向流面向缓冲区
阻塞IO非阻塞IO
(无)选择器(Selectors)

缓冲区:

在java NIO 中负责数据的存取,缓冲区就是数组.用于存储不同类型的数据.根据数据类型不同(boolean 除外),提供了相应类型的缓冲区

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
package cn.ckh2019.test;

import org.junit.Test;

import java.nio.ByteBuffer;

/**
 * @author Chen Kaihong
 * 2019-06-22 14:44
 *
 * 缓存区存取数据的两个核心方法
 * put() : 存入数据到缓冲区中
 * get() : 获取缓冲区的数据
 *
 * 缓冲区中的四个核心属性
 * capacity : 容量,表示缓冲区存储数据的最大容量.一旦声明不能改变
 * limit : 界限,表示缓冲区中可以操作数据的大小.(limit 后的数据不能进行读写)
 * position : 位置,表示缓冲区中正在操作数据的位置 (规律 : position <= limit <= capacity )
 * mark : 标记,表示记录当前position的位置.可以通过reset()恢复到这个位置
 *
 */
public class TestBuffer {

    @Test
    public void test1(){
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        //利用put()存入数据到缓冲区中
        byteBuffer.put("ckh".getBytes());

        // 切换到读取数据的模式 此时 position会变为 0 ,limit变为缓冲区数据的长度,capacity不变
        byteBuffer.flip();
        byteBuffer.mark();
        //利用get() 读取缓冲区的数据
        byte[] dst = new byte[byteBuffer.limit()];
        byteBuffer.get(dst);

        byteBuffer.reset();
        // rewind() 可重复读数据(position回到0)
        byteBuffer.rewind();

        //判断缓冲区是否还有剩余的数据
        if (byteBuffer.hasRemaining()){
            //获取缓冲区中可以操作的剩余数量
            System.out.println(byteBuffer.remaining());
        }
        //clear() 清空缓冲区,各指针回到最初状态,缓冲区的数据依然存在,但是数据处于被遗忘状态
        byteBuffer.clear();
    }
}

直接缓冲区和非直接缓冲区

  • 非直接缓冲区: 通过allocate() 方法分配缓冲区,将缓冲区建立在JVM的内存中
    在这里插入图片描述
  • 直接缓冲区 : 通过allocatedDirect() 方法分配直接缓冲区,将缓冲区建立在操作系统的物理内存中.可以提高效率.
    在这里插入图片描述
    @Test
    public void test(){
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        //isDirect()  判断是否是直接缓冲区
        System.out.println(buffer.isDirect());
    }

通道

用于源节点与目标节点的连接,在java NIO 中负责缓冲区数据的传输 。Channel本身不存储数据,因此需要配合缓冲区进行传输 。
在这里插入图片描述

通道的一些主要实现类

java.nio.channels.Channel接口

  1. FileChannel : 用于读取、写入、映射和操作文件的通道 。
  2. SocketChannel : 通过TCP读写网络中数据的通道 。
  3. ServerSocketChannel : 可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel 。
  4. DatagramChannel : 通过UDP读写网络中数据的通道 。

获取通道的方式

  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 test2(){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            fis = new FileInputStream("D:/Apicture/abc.jpg");
            fos = new FileOutputStream("1.jpg");

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

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

            //将通道中的数据读到缓冲区
            while (inChannel.read(buffer) != -1){
                buffer.flip();
                //将缓冲区的数据写入通道中
                outChannel.write(buffer);
                buffer.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } 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();
                }
            }

        }
    }

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

        /*
            StandardOpenOption : 
            READ 以读取方式打开文件
            WRITE   已写入方式打开文件
            CREATE 如果文件不存在,创建
            CREATE_NEW 如果文件不存在,创建;若存在,异常。
            APPEND 在文件的尾部追加
            DELETE_ON_CLOSE 当流关闭的时候删除文件
            TRUNCATE_EXISTING 把文件设置为0字节
            SPARSE 文件不够时创建新的文件
            SYNC 同步文件的内容和元数据信息随着底层存储设备
            DSYNC 同步文件的内容随着底层存储设备
         */

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

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

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

下面的代码测试直接缓冲区和非直接缓冲区的速度

    @Test
    public void test4() throws Exception {
        FileInputStream fis = new FileInputStream("D:/Apicture/gakki.mp4");
        FileOutputStream fos = new FileOutputStream("D:/Apicture/gakki1.mp4");
        FileChannel inChannel = fis.getChannel();
        FileChannel outChannel = fos.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        long start = System.currentTimeMillis();
        while (inChannel.read(buffer) != -1){
            buffer.flip();
            outChannel.write(buffer);
            buffer.clear();
        }
        System.out.println(System.currentTimeMillis()-start);
        /*
        测试三次 1908 855 752
         */
        outChannel.close();
        inChannel.close();
        fos.close();
        fis.close();
    }

    @Test
    public void test5() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("D:/Apicture/gakki.mp4"),StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("D:/Apicture/gakki1.mp4"),StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

        MappedByteBuffer inMappedBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
        MappedByteBuffer outMappedBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
        long start = System.currentTimeMillis();
        byte[] dst = new byte[inMappedBuffer.limit()];
        inMappedBuffer.get(dst);
        outMappedBuffer.put(dst);
        System.out.println(System.currentTimeMillis()-start);
         /*
        测试三次 118 140 127
         */
        outChannel.close();
        inChannel.close();
    }

分散与聚集

分散读取 : 将通道中的数据分散到多个缓冲区中
聚集写入 : 将多个缓冲区的数据聚集到通道中

    @Test
    public void test6() throws Exception {
        RandomAccessFile raf = new RandomAccessFile("d:/Apicture/1.txt","rw");

        FileChannel channel = raf.getChannel();

        ByteBuffer buffer1  = ByteBuffer.allocate(100);
        ByteBuffer buffer2 = ByteBuffer.allocate(512);

        //分散读取
        ByteBuffer[] buffers = {buffer1,buffer2};
        channel.read(buffers);

        buffer1.flip();
        buffer2.flip();
        System.out.println(new String(buffer1.array(),0,buffer1.limit()));
        System.out.println(new String(buffer2.array(),0,buffer1.limit()));

        //聚集写入
        RandomAccessFile raf2 = new RandomAccessFile("D:/Apicture/11.txt","rw");
        FileChannel channel1 = raf2.getChannel();
        channel1.write(buffers);
        channel.close();
        channel1.close();
        raf.close();
        raf2.close();
    }

字符集

    @Test
    public void test7() throws CharacterCodingException {
        /*
        //这段代码可查看所由支持的编码格式
        Map<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());
        }*/

        Charset charset = Charset.forName("GBK");

        //获取编码器
        CharsetEncoder ce = charset.newEncoder();
        //获取解码器
        CharsetDecoder cd = charset.newDecoder();

        CharBuffer charBuffer = CharBuffer.allocate(1024);
        charBuffer.put("ckh2019");
        charBuffer.flip();

        //编码
        ByteBuffer byteBuffer = ce.encode(charBuffer);
        System.out.println(byteBuffer.array().length);
        for (int i = 0;i < 7;i ++){
            System.out.println(byteBuffer.get());
        }

        //解码
        byteBuffer.flip();
        CharBuffer charBuffer1 = cd.decode(byteBuffer);
        System.out.println(charBuffer1.toString());

    }

NIO 的非阻塞网络通信

用NIO实现阻塞式网络通信

package cn.ckh2019.test;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * @author Chen Kaihong
 * 2019-06-25 21:19
 */
public class TestBlockingNIO {

    /**
     * 客户端
     * @throws IOException
     */
    @Test
    public void client() throws IOException {
        //获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9000));
        FileChannel fileChannel = FileChannel.open(Paths.get("D:/Apicture/1.txt"));

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

        //读取本地文件,并发送到服务端
        while (fileChannel.read(buffer) != -1){
            buffer.flip();
            socketChannel.write(buffer);
            buffer.clear();
        }

        
        socketChannel.shutdownOutput();

        //接收服务端的反馈
        int len = 0;
        while ((len = socketChannel.read(buffer)) != -1){
            buffer.flip();
            System.out.println(new String(buffer.array(),0,len));
            buffer.clear();
        }

        socketChannel.close();
        fileChannel.close();
    }

    /**
     * 服务端
     */
    @Test
    public void server() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        FileChannel fileChannel = FileChannel.open(Paths.get("D:/Apicture/2.txt"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);

        //绑定连接
        serverSocketChannel.bind(new InetSocketAddress(9000));

        //获取客户端连接的通道
        SocketChannel socketChannel = serverSocketChannel.accept();

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

        //接收客户端的数据保存到本地
        while (socketChannel.read(buffer) != -1){
            buffer.flip();
            fileChannel.write(buffer);
            buffer.clear();
        }

        //发送反馈给客户端
        buffer.put("OK!!!".getBytes());
        buffer.flip();
        socketChannel.write(buffer);

        fileChannel.close();
        socketChannel.close();

    }
}

用NIO 完成非阻塞的网络通信

package cn.ckh2019.test;

import org.junit.Test;

import java.awt.image.BufferedImage;
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.Set;

/**
 * @author Chen Kaihong
 * 2019-06-26 9:44
 */
public class TestNonBlockingNIO {

    /**
     * 客户端
     */
    @Test
    public void client() throws IOException {
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9000));

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

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        buffer.put(new Date().toString().getBytes());
        buffer.flip();
        socketChannel.write(buffer);
        buffer.clear();
        socketChannel.close();
    }

    /**
     * 服务端
     */
    @Test
    public void server() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //切换非阻塞模式
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(9000));

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

        //将通道注册到选择器,并且指定"监听接收事件"
        /*
        SelectionKey的四个常量
        读 : SelectionKey.OP_READ
        写 : SelectionKey.OP_WRITE
        连接 : SelectionKey.OP_CONNECT
        接收 : SelcetionKey.OP_ACCEPT

        如果要接听多个状态 可以用位或运算符 : SelcetionKey.OP_ACCEPT|SelectionKey.OP_CONNECT
         */
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        //轮循式获取选择器上已经准备就绪的事件
        while (selector.select()>0){
            //获取当前选择器中所有注册的选择键(已就绪的监听事件)
            Set<SelectionKey> set = selector.selectedKeys();
            for (SelectionKey key : set){
                //判断具体是什么事件准备就绪
                if (key.isAcceptable()){
                    //如果是接收就绪.获取客户端连接
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    //切换到非阻塞模式
                    socketChannel.configureBlocking(false);
                    //将该通道注册到选择器上
                    socketChannel.register(selector,SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    //获取当前选择器上 "读就绪" 的通道
                    SocketChannel socketChannel = (SocketChannel) key.channel();

                    //读取数据
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    int len = 0;
                    while ((len = socketChannel.read(buffer)) > 0){
                        buffer.flip();
                        System.out.println(new String(buffer.array(),0,len));
                        buffer.clear();
                    }
                }
            }
            //取消选择键
            set.removeAll(set);
        }
    }
}

利用UDP协议完成NIO的非阻塞网络通信

package cn.ckh2019.test;

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.Date;
import java.util.Iterator;
import java.util.Scanner;

/**
 * @author Chen Kaihong
 * 2019-06-26 10:22
 */
public class UDPNonBlockingNIO {

    /**
     * 发送端
     */
    @Test
    public void send() throws IOException {
        DatagramChannel datagramChannel = DatagramChannel.open();
        datagramChannel.configureBlocking(false);
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String str = scanner.next();
            buffer.put(str.getBytes());
            buffer.flip();
            datagramChannel.send(buffer,new InetSocketAddress("127.0.0.1",9000));
            buffer.clear();
        }
        datagramChannel.send(buffer,new InetSocketAddress("127.0.0.1",9000));
        datagramChannel.close();
    }

    /**
     * 接收端
     */
    @Test
    public void receive() throws IOException {
        DatagramChannel datagramChannel = DatagramChannel.open();
        datagramChannel.configureBlocking(false);
        datagramChannel.bind(new InetSocketAddress(9000));

        Selector selector = Selector.open();
        datagramChannel.register(selector, SelectionKey.OP_READ);
        while (selector.select() > 0) {
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                if (key.isReadable()) {
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    datagramChannel.receive(buffer);
                    buffer.flip();
                    System.out.println(new String(buffer.array(),0,buffer.limit()));
                    buffer.clear();
                }
            }
            keys.remove();
        }
    }
}

管道

java NIO 是两个线程之间的单向数据连接。Pipe有一个 source通道和一个sink通道.数据会被写到sink通道,从source通道读取。

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

        Pipe.SinkChannel sinkChannel = pipe.sink();
        buffer.put(new Date().toString().getBytes());
        buffer.flip();
        sinkChannel.write(buffer);

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

        sourceChannel.close();
        sinkChannel.close();
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值