NIO与网络编程

10 篇文章 0 订阅


前面的记录: java基础——IO流——20200616

简介

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

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

缓冲区 Buffer

  • 在NIO中负责数据的存储
  • 缓冲区用数组存储不同类型的数据,根据数据类型不同(boolean除外),提供了相应的Buffer子类
    • ByteBuffer
    • CharBuffer
    • ShortBuffer
    • IntBuffer
    • LongBuffer
    • FloatBuffer
    • DoubleBuffer
  • Buffer主要用于与NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区读入通道中的 。
public abstract class Buffer {
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
  • capacity: 容量,表示缓冲区中最大存储数据的容量,一旦声明不能改变。
  • limit:表示缓冲区中可以操作数据的大小,即超出limit的数据不能读写
  • position :表示缓冲区中正在操作数据的位置
  • mark 表示记录当前position的位置,可以通过reset恢复到mark的位置。

常用API

@Test
    public void test1(){
        //1. 分配一个指定大小的huanchongqu
        ByteBuffer buf = ByteBuffer.allocate(1024);

        //   Invariants: mark <= position <= limit <= capacity
        System.out.println(buf.position()); //0
        System.out.println(buf.limit()); // 1024
        System.out.println(buf.capacity()); // 1024
        System.out.println(buf.mark()); // java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024]

        System.out.println("----------put----------");
        buf.put("sadfdv".getBytes());
        System.out.println(buf.position()); //6
        System.out.println(buf.limit()); // 1024
        System.out.println(buf.capacity()); // 1024


//        for (int i = 0; i < buf.position(); i++) {
//            System.out.print((char)buf.get(i));
//        }//sadfdv

        System.out.println("---------切换模式---------");
        /**
         * postion x(6)-->0
         * limit cap(1024)-->x(6)
         * capacity 1024-->1024
         */
        buf.flip();
        System.out.println(buf.position()); //0
        System.out.println(buf.limit()); // 6
        System.out.println(buf.capacity()); // 1024


        System.out.println("------------get-----------");
        byte[] read = new byte[buf.limit()];
        buf.get(read);
        System.out.println(new String(read,0,buf.limit()));//sadfdv
        System.out.println(buf.position()); //6
        System.out.println(buf.limit()); // 6
        System.out.println(buf.capacity()); // 1024

        /**
         * 又切换回读模式
         */
        System.out.println("------------rewind-----------");
        buf.rewind();
        System.out.println(buf.position()); //0
        System.out.println(buf.limit()); // 6
        System.out.println(buf.capacity()); // 1024



        System.out.println("------------clear-----------");
        buf.clear();
        System.out.println(buf.position()); //0
        System.out.println(buf.limit()); // 1024
        System.out.println(buf.capacity()); // 1024

        for (int i = 0; i < 6; i++) {
            System.out.print((char)buf.get(i));
        }//sadfdv (其实数据还在)



    }
    @Test
    public void test2(){
        //1. 分配一个指定大小的huanchongqu
        ByteBuffer buf = ByteBuffer.allocate(1024);

        System.out.println("----------put----------");
        buf.put("ABCDEFG".getBytes());
        System.out.println(buf.position()); //6
        System.out.println(buf.limit()); // 1024
        System.out.println(buf.capacity()); // 1024


        System.out.println("------------get-----------");
        buf.flip();
        byte[] read = new byte[buf.limit()];
        buf.get(read,0,2); //read=[65,66,0,0,0,0,0]
        System.out.println(new String(read,0,2));//AB
        System.out.println(buf.position()); //2

        buf.mark();
        System.out.println("------------mark-----------");

        buf.get(read,2,2);//read=[65,66,67,68,0,0,0]
        System.out.println(new String(read,2,2));//CD
        System.out.println(buf.position()); //4


        System.out.println("------------reset-----------");
        buf.reset();
        System.out.println(buf.position()); //2

        //如果缓冲区中还有剩余数据 return position < limit;
        if (buf.hasRemaining()){
            //获取缓冲区还可操作的空间 [limit - position;]
            System.out.println(buf.remaining());//5
        }
    }
  • 非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在JVM内存中
  • 直接缓冲区:通过allocateDirect()方法分配缓冲区,将缓冲区建立在物理内存中;可以提高效率,但是不安全
    • 进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留于常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给哪些易受基础系统的本机IO操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
    • 直接字节缓冲区还可以通过FileChannel的map()方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer。java平台的实现有助于通过JNI从本地代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区 的内容,并且会在访问期间或稍后的某个时间抛出不确定的异常。
    • 字节缓冲区域是直接的还是非直接的可以用isDirect()方法来确定。提供该方法是为了能够在性能关键型代码中执行显式缓冲区管理。

通道 Channel

  • package java.nio.channels;
  • 表示IO源与目标打开的连接,类似传统的”流"。只不过Channel本身不能直接访问数据,只能与Buffer进行交互。

DMA:直接存储器存储

  • 主要实现类
    • FileChannel;
    • SocketChannel;
    • ServerSocketChannel;
    • DatagramChannel
  • 三种方式获取通道
    • 1.支持通道的类有getChannel()方法
      • 本地IO:FileInputStream/ FileOutputStream/ RandomAccessStream
      • 网络IO:Socket/ServerSocket/DatagramSocket
    • 2.NIO2针对各个通道的类提供了静态方法open()
    • 3.NIO2的Files工具类 的newByteChannel()方法

复制图片

String path = "static/img/";
    @Test
    public void test1(){
        try(FileInputStream fis = new FileInputStream(path+"save.png");
            FileOutputStream fos = new FileOutputStream(path+"save-copy.png");
            //1. channel
            FileChannel inChannel = fis.getChannel();
             FileChannel fosChannel = fos.getChannel();

        ){
            //2. buffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            //3. 将通道中的数据存入缓冲区

            while (inChannel.read(buffer)!=-1){

                //4. 将缓冲区的数据写入通道
                buffer.flip();
                fosChannel.write(buffer);
                buffer.clear();
                buffer.rewind();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }


    }
/**
     * 非直接缓冲区复制文件(内存映射文件)
     */
    @Test
    public void test2(){
        try(

            //1. channel
                FileChannel inChannel = FileChannel.open(Paths.get(path,"save.png"), StandardOpenOption.READ);
                FileChannel outChannel = FileChannel.open(Paths.get(path,"copy-2.png"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);

        ){
            //2. buffer
            MappedByteBuffer inBuffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outBuffer = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());


            //3. 直接对缓冲区的数据读写操作
            byte[] temp = new byte[inBuffer.limit()];
            inBuffer.get(temp);
            outBuffer.put(temp);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    
/**
     * 通道之间的传输
     */
    @Test
    public void test3(){
        try(

                //1. channel
                FileChannel inChannel = FileChannel.open(Paths.get(path,"save.png"), StandardOpenOption.READ);
                FileChannel outChannel = FileChannel.open(Paths.get(path,"copy-3.png"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE_NEW);

        ){
            //3. 直接对通道操作 以下2选1
            outChannel.transferFrom(inChannel, 0, inChannel.size());
//            inChannel.transferTo(0,inChannel.size(),outChannel);


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
  • 分散读取:从Channel中的读取的数据分散到多个Buffer中去。(按照缓冲区的顺序,依次填满)
  • 聚集写入:多个Buffer中的数据聚集到Channel中。

字符集Charset

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

        CharBuffer charBuffer = CharBuffer.allocate(1024);
        charBuffer.put("编码器解码器");
        charBuffer.flip();
        ByteBuffer byteBuffer = ce.encode(charBuffer);

        for (int i = 0; i < 12; i++) {
            System.out.println(byteBuffer.get(i));
        }

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


        System.out.println("---------StandardCharsets.UTF_8---------");
        Charset cs2 = StandardCharsets.UTF_16;
        cs2 = Charset.forName("GBK");
        byteBuffer.flip();
        CharBuffer charBuffer2 = cs2.decode(byteBuffer);
        System.out.println(charBuffer2.toString());
    }

选择器 Selector

创建:

Selector selector = Selector.open();

向选择器注册通道

ssChannel.register(selector, SelectionKey.OP_ACCEPT);

SelectionKey:表示SelectableChannnel和Selector之间的注册关系。每次向选择器注册通道就会选择一个事件(选择键)。选择键包含两个表示为整数的操作集。操作集的每一位都表示该键的通道所支持的一类可选择操作。

方法描述方法体
int interestOps();获取感兴趣事件集合无(abstract)
int readyOps获取通道已经准备就绪的操作的集合无(abstract)
SelectableChannel channel();获取注册通道无(abstract)
Selector selector();返回选择器无(abstract)
boolean isReadable()检测Channel中读事件是否就绪return (readyOps() & OP_READ) != 0;
boolean isWritable()检测Channel中写事件是否就return (readyOps() & OP_WRITE) != 0;
boolean isConnectable()检测Channel中连接是否就绪return (readyOps() & OP_CONNECT) != 0;
boolean isAcceptable()检测Channel中接收是否就绪return (readyOps() & OP_ACCEPT) != 0;

ps:

public static final int OP_ACCEPT = 1 << 4;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_WRITE = 1 << 2;
public static final int OP_READ = 1 << 0;
//若注册时不止监听一个事件,可以用‘|’连接
int interestSet = SelectionKey.OP_ACCEPT | SelectionKey.OP_CONNECT;

demo:

@Test
    public void client(){

        try (
                //1. get channel
                SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 9898));
//                FileChannel fileChannel = FileChannel.open(Paths.get(path,"save.png"), StandardOpenOption.READ);


            ) {
            //1.2切换非阻塞式
            socketChannel.configureBlocking(false);
            //2. buffer
            ByteBuffer buf = ByteBuffer.allocate(1024);

            //3.   upload file

//            while (fileChannel.read(buf)!=-1){
//                buf.flip();
//                socketChannel.write(buf);
//                buf.clear();
//            }

            //4.   upload data
            buf.put(new Date().toString().getBytes());
            buf.flip();
            socketChannel.write(buf);
            buf.clear();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    @Test
    public void server() {
        SocketChannel sChannel = null;
        SocketChannel selectChannel = null;
        final InetSocketAddress local = new InetSocketAddress(9898);
        try (
                //1. get ServerSocket channel
                ServerSocketChannel ssChannel = ServerSocketChannel.open();
                //2. 获取选择器
                Selector selector = Selector.open();
//                FileChannel fileChannel = FileChannel.open(Paths.get(path,"server-copy.png"),StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        ) {
            //1.2切换非阻塞式
            ssChannel.configureBlocking(false);
            //1.3 bind port
            ssChannel.bind(local);
            //2.3 注册选择器
            ssChannel.register(selector, SelectionKey.OP_ACCEPT );

            //2.4 轮询时获取选择器上已经就绪的事件
            while (selector.select()>0){
                //2.5获取当前选择器中所有注册的选择键
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey sk = iterator.next();
                    if (sk.isAcceptable()){
                        //3. get socket channel
                        sChannel = ssChannel.accept();
                        //3.2 切换成非阻塞式
                        sChannel.configureBlocking(false);
                        //3.3 将该通道注册到选择器上
                        sChannel.register(selector,SelectionKey.OP_READ);
                    }else if (sk.isReadable()){
                        //4 获取当前选择器上都就绪状态的通道
                        selectChannel = (SocketChannel) sk.channel();
                        //4.2 读取数据
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        int len=0;
                        while ((len=selectChannel.read(buf))!=-1){
                            buf.flip();
                            System.out.println(new String(buf.array(), 0, len));
                            buf.clear();
                        }
                    }

                    iterator.remove();
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if (sChannel!=null){
                try {
                    sChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (selectChannel!=null){
                try {
                    selectChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

    }

DatagramChannel

demo

@Test
    public void send(){
        try (
                //1. get channel
                DatagramChannel dc = DatagramChannel.open();
                Scanner sc = new Scanner(System.in);
        ){
            //1.2 非阻塞式
            dc.configureBlocking(false);
            //2 buffer
            ByteBuffer buf = ByteBuffer.allocate(1024);
            //3. send data
            while (sc.hasNext()){
                String s = sc.nextLine();
                buf.put((new Date().toString()+":\n"+s).getBytes());
                dc.send(buf,new InetSocketAddress("127.0.0.1",9898));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Test
    public void receive(){
        try (
                //1. get channel
                DatagramChannel dc = DatagramChannel.open();
                //2 get selector
                Selector selector = Selector.open();
                Scanner sc = new Scanner(System.in);
        ){
            //1.2 非阻塞式
            dc.configureBlocking(false);
            //1.3 bind listen port
            dc.bind(new InetSocketAddress(9898));
            //2.2
            dc.register(selector, SelectionKey.OP_READ);
            //2.4 轮询时获取选择器上已经就绪的事件
            while (selector.select()>0){
                //2.5获取当前选择器中所有注册的选择键
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()){
                    SelectionKey sk = iterator.next();
                    if (sk.isReadable()){
                        //3 buffer
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        //3.2 读取数据
                        dc.receive(buf);
                        buf.flip();
                        System.out.println(new String(buf.array(), 0, buf.limit()));
                        buf.clear();

                    }

                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

管道Pipe

  • NIO 管道是2个线程之间的单向数据连接。
  • Pipe有一个source通道和一个sink通道。数据会被写到sink通道,而从source通道读取
@Test
    public void test1() throws IOException {
        Pipe pipe = Pipe.open();
        ByteBuffer buf = ByteBuffer.allocate(1024);
        try(
                Pipe.SinkChannel sinkChannel = pipe.sink();
                Pipe.SourceChannel sourceChannel = pipe.source();
                ) {
            buf.put("通过管道发送数据".getBytes());
            buf.flip();
            sinkChannel.write(buf);

            // read  通过管道接收数据
            buf.flip();
            sourceChannel.read(buf);
            System.out.println(new String(buf.array(),0,buf.limit()));


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

设计实现:HTTP异步客户端(未完成)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值