JavaSE@拓展补遗@笔记10@NIO流

概述

之前学习的IO都叫做BIO阻塞IO.

NIO是非阻塞IO流。能够在高并发的情况下提高读写效率。

NIO分为三大知识点Buffer缓冲区、Channel通道、Selector选择器

一、Buffer缓冲区

1、概述

Buffer是缓冲区数组,用来代替之前的普通数组。

分类

ByteBuffer   用来代替之前的byte[]
CharBuffer 
DoubleBuffer 
FloatBuffer
IntBuffer 
LongBuffer 
ShortBuffer

2、ByteBuffer的创建方式

代码演示

//在堆中创建缓冲区:allocate(int capacity)*

ByteBuffer buffer1 = ByteBuffer.allocate(1024);

//在系统内存创建缓冲区:allocateDirect(int capacity)

ByteBuffer buffer2 = ByteBuffer.allocateDirect(1024);

//通过普通数组创建缓冲区:wrap(byte[] arr)

byte[] arr = new byte[1024];
ByteBuffer buffer3 = ByteBuffer.wrap(arr);

3、常用方法

put(byte b) : 给数组添加元素

//ByteBuffer的方法
//创建长度为10的缓冲区数组
ByteBuffer b = ByteBuffer.allocate(10);
​
//- put(byte b) : 给数组添加元素
​
//添加元素(默认从头往后逐个添加)
b.put((byte)10);
b.put((byte)20);
b.put((byte)30);
//添加数组
byte[] arr = {11,22,33};
b.put(arr);
​
//把缓冲区数组变成普通数组打印内容
System.out.println(Arrays.toString(b.array()));  
                    //[10, 20, 30, 11, 22, 33, 0, 0, 0, 0]

capacity() :获取容量,容量是一个定值。

//- capacity() :获取容量,容量是一个定值。数组一旦创建长度不能改变,长度就是数组的容量
int capacity = b.capacity();
System.out.println("容量是" +  capacity);

limit() : 限制。limit可以指定一个索引,从limit开始后面的位置不能操作。

//ByteBuffer的方法
//创建长度为10的缓冲区数组
ByteBuffer b = ByteBuffer.allocate(10);
​
//limit()  :限制.
//获取当前限制
int limit = b.limit();
System.out.println("默认的limit:" + limit);   //默认情况下limit=capacity
​
//修改限制为3(从3索引开始后面的位置就不能被操作了)
b.limit(3);
​
//添加元素
b.put((byte)10);
b.put((byte)20);
b.put((byte)30);
b.put((byte)40);  //操作了3索引就报错了

position() :位置。位置代表将要存放的元素的索引,每次添加元素position会往后移动。位置不能小于0,也不能大于limit。position只会自动完后走,不会自动往前走。

//ByteBuffer的方法
//创建长度为10的缓冲区数组
ByteBuffer b = ByteBuffer.allocate(10);
​
//position() :位置。位置默认是0 每次存放元素位置就会向后移动
//获取当前位置
int position = b.position();
System.out.println("当前的位置" + position);
​
//添加元素
b.put((byte)10);
b.put((byte)20);
​
//修改位置为4
b.position(4);
//添加元素
b.put((byte)30);
​
//打印
System.out.println(Arrays.toString(b.array()));
                            //[10, 20, 0, 0, 30, 0, 0, 0, 0, 0]

mark() : 标记。当调用缓冲区的reset()重置方法时,会将缓冲区的position重置为mark的位置。

//ByteBuffer的方法
//创建长度为10的缓冲区数组
ByteBuffer b = ByteBuffer.allocate(10);
​
//mark() :标记    reset():重置
//默认情况下缓冲区是没有标记的
​
b.put((byte)10);
b.put((byte)20);
//记录标记(标记当前位置)
b.mark();
​
b.put((byte)30);
b.put((byte)40);
​
//重置
b.reset();
​
b.put((byte)50);
​
//打印
System.out.println(Arrays.toString(b.array()));
                            //[10, 20, 50, 40, 0, 0, 0, 0, 0, 0]

clear():还原缓冲区的各个指针。 只移动指针不会清空数据。

  • 将position设置为初始状态

  • 将限制limit设置为初始状态

  • 丢弃标记mark

//ByteBuffer的方法
//创建长度为10的缓冲区数组
ByteBuffer b = ByteBuffer.allocate(10);
​
//clear()  :还原指针的状态,不清空数据
b.put((byte)10);
b.put((byte)20);
​
//设置限制
b.limit(4);
​
System.out.println(b);   //[pos=2 lim=4 cap=10]
​
//还原
b.clear();
​
System.out.println(b);  //[pos=0 lim=10 cap=10]
​
//打印数组的内容
System.out.println(Arrays.toString(b.array()));
                //[10, 20, 0, 0, 0, 0, 0, 0, 0, 0]数据还在

flip():切换读写状态。在读写数据之间要调用这个方法。

  • 将limit设置为当前position位置

  • 将当前position设置为初始位置

  • 丢弃mark标记

//ByteBuffer的方法
//创建长度为10的缓冲区数组
ByteBuffer b = ByteBuffer.allocate(10);
​
//flip() :读写切换方法
b.put((byte)10);
b.put((byte)20);
b.put((byte)30);
System.out.println(b);  //[pos=3 lim=10 cap=10]
//切换
b.flip();
System.out.println(b);  //[pos=0 lim=3 cap=10]

2、Channel通道

1、概述

通道相当于之前的IO流,通道可以读写数据,通道不区分输入和输出的方向,通道类型既可以读也可以写。

2、分类

  • FileChannel:从文件读取数据的
  • DatagramChannel:读写UDP网络协议数据
  • SocketChannel:读写TCP网络协议数据
  • ServerSocketChannel:可以监听TCP连接

3、FileChannel基本使用

使用FileChannel完成文件的复制

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
​
public class Test02_FileChannel完成文件复制 {
    public static void main(String[] args) throws Exception {
        //创建输入流
        FileInputStream fis = new FileInputStream("C:\\Users\\jin\\Desktop\\timg.jpg");
        //创建输出流
        FileOutputStream fos = new FileOutputStream("day13\\复制.jpg");
​
        //通过IO流获取Channel通道对象
        FileChannel c1 = fis.getChannel();
        FileChannel c2 = fos.getChannel();
​
        //在通道中使用的数组是缓冲区数组
        ByteBuffer buffer = ByteBuffer.allocate(1024);
​
        //循环
        while(c1.read(buffer) != -1){
            //切换
            buffer.flip();
            //写出
            c2.write(buffer);
            //清空
            buffer.clear();
        }
​
        //关闭资源
        fos.close();
        fis.close();
​
    }
}

4、FileChannel结合MappedByteBuffer实现读写

MappedByteBuffer是ByteBuffer的子类。他也是一个缓冲数组。能够把硬盘中的数据一次映射到内存中。

可以减少硬盘和内存之间的IO次数,能够提高效率。

import java.io.FileNotFoundException;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
​
public class Test03_高效复制 {
    public static void main(String[] args) throws IOException {
        //指向源文件
        //r代表read 是对这个文件进行只读操作
        RandomAccessFile f1 = new RandomAccessFile("C:\\Users\\jin\\Desktop\\timg.jpg","r");
        //指向目标文件
        //rw代表readwrite
        RandomAccessFile f2 = new RandomAccessFile("day13\\复制.jpg","rw");
        //获取通道
        FileChannel c1 = f1.getChannel();
        FileChannel c2 = f2.getChannel();
        //获取源文件大小
        long size = c1.size();
        //获取映射数组
        //对源文件进行只读操作,从文件的0位置开始读取size个字节
        MappedByteBuffer map1 = c1.map(FileChannel.MapMode.READ_ONLY, 0, size);
​
        MappedByteBuffer map2 = c2.map(FileChannel.MapMode.READ_WRITE, 0, size);
​
        //把map1中的数据移动到map2中
        for(int i=0; i < size; i++){
            //从数组map1中获取字节
            byte b = map1.get();
            //把字节存放到数组map2中
            map2.put(b);
        }
​
        //关闭资源
        f1.close();
        f2.close();
    }
}

5、网络编程收发信息

客户端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
​
public class Test客户端 {
    public static void main(String[] args) throws IOException {
​
        //创建客户端对象
        SocketChannel s = SocketChannel.open();
        //指定连接的服务器
        s.connect(new InetSocketAddress("192.168.171.32",8888));
​
        //缓冲区数组
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //把数据放在缓冲区数组
        buffer.put("你好呀~".getBytes());
​
        //切换(如果不切换的话,会输出数组中后的空格反而没有数据)
        buffer.flip();
​
        //调用输出方法把数组中的数据发出去
        s.write(buffer);
​
        //关流
        s.close();
​
    }
}

服务器端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
​
public class Test服务端 {
    public static void main(String[] args) throws IOException {
        //创建服务端对象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定端口号
        ssc.bind(new InetSocketAddress(8888));
        //等待客户端连接
        SocketChannel s = ssc.accept();
        //准备数组
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //读取数据
        int len = s.read(buffer);
​
        //打印数据
        System.out.println(new String(buffer.array(),0,len));
​
        //服务器不需要关闭资源
    }
}

6、accept阻塞问题

服务器端

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
​
public class Test服务端非阻塞 {
​
    public static void main(String[] args) throws IOException, InterruptedException {
        //创建服务端对象
        ServerSocketChannel ssc = ServerSocketChannel.open();
        //绑定端口号
        ssc.bind(new InetSocketAddress(8888));
        //设置非阻塞
        ssc.configureBlocking(false);
​
        //循环查看有没有客户来连接
        while(true) {
            //等待客户端连接
            SocketChannel s = ssc.accept();
​
            //判断有没有客户端的连接
            if(s != null) {
                //准备数组
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //读取数据
                int len = s.read(buffer);
                //打印数据
                System.out.println(new String(buffer.array(), 0, len));
                //非阻塞
                break;
            }else{
                //没有客户端的连接
                System.out.println("服务器执行别的业务代码");
                Thread.sleep(3000);
            }
        }
​
        //服务器不需要关闭资源
​
    }
}

三、Selector选择器

1、多路复用的概念

之前的代码一个端口需要一个新的线程,如果端口很多导致程序效率低。

多路复用的意思是把多个服务器端口交给一个选择器对象去管理,提高效率。

2、Selector介绍

Selector是一个选择器,可以用一个线程处理多个事件。可以注册到多个Channel上,帮多个Channel去处理事件。用一个线程处理了之前多个线程的事务,就给系统减轻负担,提高效率。

select()        :选择器连接客户端的方法    
                阻塞问题:
                    在没有客户端连接的情况下是方法阻塞的。
                    在有客户端连接且客户端没有被处理时的情况下方法是非阻塞的。
                    在有客户端连接且客户端已经被梳理时的情况下方法又变回阻塞的。
selectedKeys()  :返回一个集合,集合里面装的是被访问过的服务器。

3、Selector方法的演示

import com.sun.source.doctree.StartElementTree;
​
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Set;
​
public class Test服务器 {
    public static void main(String[] args) throws IOException {
        //创建服务器对象
        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        //绑定端口
        ssc1.bind(new InetSocketAddress(7777));
        //设置非阻塞(Selector的使用必须让服务器非阻塞)
        ssc1.configureBlocking(false);
​
        //创建服务器对象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //绑定端口
        ssc2.bind(new InetSocketAddress(8888));
        //设置非阻塞
        ssc2.configureBlocking(false);
​
        //创建选择器对象
        Selector s = Selector.open();
​
        //把选择器注册到服务器上,选择器就可以管理服务器(OP_ACCEPT表示让选择器去管理accept这个方法)
        ssc1.register(s, SelectionKey.OP_ACCEPT);
        ssc2.register(s, SelectionKey.OP_ACCEPT);
​
        //获取集合
        //选择器会把被访问的服务器对象放在这个集合中
        Set<SelectionKey> set = s.selectedKeys();
​
        System.out.println("集合中的对象个数是:" + set.size()); // 个数 0 
​
        while (true) {
            //选择器连接客户端的方法
            s.select();
            System.out.println("集合中的对象个数是:" + set.size()); 
                                                        //连接了几个服务器个数就是几
        }
    }
}

4、Selector管理多个ServerSocketChannel的问题

在客户端访问服务器的时候,选择器会把服务器对象放在Set集合中,但是在使用完之后没有把服务器从集合中取出。下次在连接别的服务器的时候,已经被使用过的服务器对象会出现空指针异常。

解决办法:在没次用完一个服务器之后就从集合中删除。不能使用集合的删除方法,因为会有并发修改异常,必须使用迭代器的删除方法。

import com.sun.source.doctree.StartElementTree;
​
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
​
public class Test服务器 {
    public static void main(String[] args) throws IOException, InterruptedException {
        //创建服务器对象
        ServerSocketChannel ssc1 = ServerSocketChannel.open();
        //绑定端口
        ssc1.bind(new InetSocketAddress(7777));
        //设置非阻塞(Selector的使用必须让服务器非阻塞)
        ssc1.configureBlocking(false);
​
        //创建服务器对象
        ServerSocketChannel ssc2 = ServerSocketChannel.open();
        //绑定端口
        ssc2.bind(new InetSocketAddress(8888));
        //设置非阻塞
        ssc2.configureBlocking(false);
​
        System.out.println("ssc1" + ssc1);
        System.out.println("ssc2" + ssc2);
        //创建选择器对象
        Selector s = Selector.open();
​
        //把选择器注册到服务器上,选择器就可以管理服务器(OP_ACCEPT表示让选择器去管理accept这个方法)
        ssc1.register(s, SelectionKey.OP_ACCEPT);
        ssc2.register(s, SelectionKey.OP_ACCEPT);
​
        //获取集合
        //选择器会把被访问的服务器对象放在这个集合中
        Set<SelectionKey> set = s.selectedKeys();
​
        System.out.println("集合中的对象个数是:" + set.size());
​
        while (true) {
            System.out.println("集合中的对象个数是:" + set.size());
​
            //选择器连接客户端的方法
            s.select();
            //遍历集合获取集合中的服务器对象
            //for (SelectionKey key : set) {
            //使用迭代器
            Iterator<SelectionKey> it = set.iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                 //获取里面的通道对象
                SelectableChannel channel = key.channel();
                //ServerSocketChannel --> AbstractSelectableChannel  -->  SelectableChannel
                //向下转型
                ServerSocketChannel ssc = (ServerSocketChannel) channel;
                //使用服务端对象接受客户端数据
                //获取客户端对象
                SocketChannel sc = ssc.accept();
                //准备数组
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //读取数据
                int len = sc.read(buffer);
                //打印数据
                System.out.println(new String(buffer.array(),0,len));
                //集合删除元素
                //set.remove(key);
                //使用迭代器删除当前元素
                it.remove();
            }
            //使用服务器对象接受数据
        }
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值