Java基础_IO流(BIO和NIO)

目录

1:写在文章之前

2:什么是BIO(Block IO-阻塞IO)

2.1:代码案例(多个客户端对1个服务端)

2.2:代码案例(多个客户端对应多个服务端)

3:什么是NIO(New IO-非阻塞IO)

3.1:Buffers(缓冲区)

3.2:Channels(通道)

3.3:Selector(选择器)


1:写在文章之前

上一张的IO了解了IO流的字节流和字符流,主要操作读写文件。

我们在网络通信中经常会用到BIO和NIO,主要处理网络数据,以下就是BIO和NIO的特征。

BIONIO
面向流面向缓冲
阻塞IO非阻塞IO
选择器

2:什么是BIO(Block IO-阻塞IO)

BIO:同步阻塞IO

同步:客户端跟服务器经过TCP链接后。客户端发数据后服务端立马能接受数据。

阻塞:1个客户端线程都会对应1个服务端线程。客户端线程发数据服务端线程接收数据。如果客户端线程还没有发送数据呢,服务端的对应的接受线程会阻塞。适合连接数目较小的框架。

结构如下:

2.1:代码案例(多个客户端对1个服务端)

客户端可以启动多个,但是服务端只有一个线程。只会处理连接到的第一个客户端,其他的客户端发送的数据服务端不会接受。

/**
 * @date :8/30/21 4:16 PM
 * 服务端只有一个线程提供服务
 * 只能处理一个客户端
 * 服务端代码
 */
public class Service1 {
    public static void main(String[] args) {

        try {
            System.out.println("服务端启动");
            //补充:端口范围0-65535,0-1023为系统占用端口
            //服务端指定端口

            //1:建立连接
            ServerSocket serverSocket = new ServerSocket(9998);
            //2:监听客户端
            Socket socket = serverSocket.accept();

            //3:获取IO流,通过scoket获取IO流
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int a=0;

            //4:输出数据
            while ((a = inputStream.read(bytes)) != -1) {
                String string = new String(bytes, 0, a, "UTF-8");
                System.out.println("服务端获取信息:" + string);
            }

            System.out.println("结束");

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //此处不关闭
        }


    }
}


=====================以下是客户端代码===================


/**
 * @author :huyiju
 * @date :8/30/21 4:12 PM
 * 客户端代码 发送数据
 */
public class BIO2 {
    public static void main(String[] args) throws IOException {
        System.out.println("客户端多发启动13");
        String host = "127.0.0.1";
        int port = 9998;

        Socket socket = new Socket(host, port);
        OutputStream outputStream = socket.getOutputStream();
        Scanner scanner=new Scanner(System.in);
        
        while (true){
            System.out.print("请说:");
            String msg=scanner.nextLine();
            outputStream.write(msg.getBytes("UTF-8"));
            outputStream.flush();//此处刷新流 便于下次重新写数据
            //socket.close();//不要关闭 模拟多次请求
        }
    }
}

结果分析:客户端1和2都发送数据,但是服务端只能接收到第一个客户端的数据。验证了阻塞同步是BIO的特性

2.2:代码案例(多个客户端对应多个服务端)

在上边的案例。知道了BIO的同步。我们要是处理多个客户端的话,服务端可以自己手写,也可以使用线程池。此处手写代码,不是用线程池。

/**
 * @author :huyiju
 * @date :8/30/21 4:16 PM
 * 服务端代码
 * 实现多对多
 * 线程的1对1
 * 缺点客户端有多少请求,服务端就会有多少线程的响应
 * 通信密集的时候 线程太多会崩的
 */
public class ServiceDuo {
    public static void main(String[] args) {

        try {
            System.out.println("多个服务端启动");
            //补充:端口范围0-65535,0-1023为系统占用端口
            //服务端指定端口

            //1:建立连接,接受多个客户端信息。
            ServerSocket serverSocket = new ServerSocket(9998);
            //2:监听客户端,多个客户端就有多个服务端
            while (true){
                Socket socket = serverSocket.accept();
                new ScoketThreadRun(socket).start();//多线程传递scoket
            }


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


    }
}

class ScoketThreadRun extends Thread {

    Socket socket;
    public ScoketThreadRun(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        //3:获取IO流
        InputStream inputStream = null;
        try {
            inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int a;
            //4:输出数据
            while ((a = inputStream.read(bytes)) != -1) {
                String string = new String(bytes, 0, a, "UTF-8");
                System.out.println("服务端获取信息:" + string);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


=====================以下是客户端代码===================


/**
 * @author :huyiju
 * @date :8/30/21 4:12 PM
 * 客户端代码 发送数据
 */
public class BIO2 {
    public static void main(String[] args) throws IOException {
        System.out.println("客户端多发启动13");
        String host = "127.0.0.1";
        int port = 9998;

        Socket socket = new Socket(host, port);
        OutputStream outputStream = socket.getOutputStream();
        Scanner scanner=new Scanner(System.in);
        
        while (true){
            System.out.print("请说:");
            String msg=scanner.nextLine();
            outputStream.write(msg.getBytes("UTF-8"));
            outputStream.flush();//此处刷新流 便于下次重新写数据
            //socket.close();//不要关闭 模拟多次请求
        }
    }
}

结果展示可以看到服务端通过Socket socket = serverSocket.accept();创建多个socket,然后交给不同的线程处理。

 

3:什么是NIO(New IO-非阻塞IO)

NIO:同步非阻塞IO

同步:客户端跟服务器经过TCP链接后。客户端发数据后服务端立马能接受数据。

阻塞:多个个客户端线程都会对应1个服务端选择器(selector)。客户端可以有很多通过缓冲区(buffer)发送数据到通道(channel)。说人话就是客户端不管多少个线程,数据都是先用缓冲区封装,通过指定的通道发送到选择器。如果选择器看到有数据,才会创建服务端线程处理数据。没有数据线程不阻塞,只要一个人看着就行。这个人就是选择器。有数据的时候。选择器摇人。

Java NIO 由以下几个核心部分组成:

Channels(客户端通过通道给服务器传递数据,相当于高速公路)

Buffers(缓冲区是货车,负责按容量运送数据)

Selectors(收费站,看看那个高速公路的有货车发送数据,就指定线程处理数据)

结构如下

3.1:Buffers(缓冲区)

作用:相当于货车按照指定的容量装数据,并且能够读取数据。

以下是Java NIO里关键的Buffer实现:

ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer

这些Buffer覆盖了你能通过IO发送的基本数据类型:byte, short, int, long, float, double 和 char。

代码案例:

public class Buffer1 {
    @Test
    public  void ByteBUffer1(){
        //1:创建缓冲区byte 长度是10
        ByteBuffer byteBuffered=ByteBuffer.allocate(10);

        System.out.println(byteBuffered.put("123456".getBytes()));
        System.out.println(byteBuffered.put("b".getBytes()));
        System.out.println(byteBuffered.put("a".getBytes()));

        System.out.println("position位置:"+byteBuffered.position());//随着读取数据 数据下标
        System.out.println("界限:"+byteBuffered.limit());//数据实际长度不变  position<=limit<=capacity
        System.out.println("容量:"+byteBuffered.capacity());
        System.out.println("没有flip获取第一个数据会乱码:"+(char) byteBuffered.get());

        System.out.println("----------------flip让数据可读------------------------");
        byteBuffered.flip();
        System.out.println("位置:"+byteBuffered.position());
        System.out.println("界限:"+byteBuffered.limit());
        System.out.println("容量:"+byteBuffered.capacity());
        System.out.println("flip获取第一个数据:"+(char) byteBuffered.get());
        System.out.println("flip获取第一个数据:"+(char) byteBuffered.get());
        System.out.println("flip获取第一个数据:"+(char) byteBuffered.get());
        System.out.println("position位置:"+byteBuffered.position());//随着读取数据 数据下标
        System.out.println("界限:"+byteBuffered.limit());//数据实际长度不变  position<=limit<=capacity
        System.out.println("容量:"+byteBuffered.capacity());


        System.out.println("---------------清除数据-----------------");
        System.out.println(byteBuffered.clear());
        System.out.println(byteBuffered.put("jj".getBytes()));

        System.out.println(byteBuffered.position());//2
        System.out.println(byteBuffered.limit());//10
        System.out.println(byteBuffered.capacity());//10
        byteBuffered.flip();
        System.out.println("位置:"+byteBuffered.position());//0
        System.out.println("界限:"+byteBuffered.limit());//2
        System.out.println("容量:"+byteBuffered.capacity());//10

    }


    @Test
    public  void ByteBUffer2(){
        //1:创建缓冲区byte 长度是10
        ByteBuffer byteBuffered=ByteBuffer.allocate(10);

        System.out.println(byteBuffered.isDirect());

        ByteBuffer byteBuffered1=ByteBuffer.allocateDirect(10);

        System.out.println(byteBuffered1.isDirect());

    }
}

3.2:Channels(通道)

作用:将数据放到channel。然后通过buffer运送数据。

Channel和Buffer有好几种类型。下面是JAVA NIO中的一些主要Channel的实现:

FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel

代码案例

/**
 * @author :huyiju
 * @date :8/30/21 9:38 PM
 * 通过channel
 * 读文件 写文件 等操作
 */
public class Channel1 {
    public static void main(String[] args) {

    }

    /**
     * 用文件缓冲区把数据写入文件
     */
    @Test
    public void write() {
        try {
            int size = 2;
            //1:输出流 写入文件
            FileOutputStream outputStream = new FileOutputStream("/Users/huyiju/Desktop/log/huyiju.txt");
            //2:字节流得到通道
            FileChannel channel = outputStream.getChannel();
            //3:分配缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(size);
            //4:讲述数据写入缓冲区
            String msg = "1234567890";
            byte[] b = msg.getBytes();

            int num = 0;

            System.out.println("数据长度" + b.length + "  缓冲区长度:" + size);
            if (b.length < size) {

                byteBuffer.put(b);
                byteBuffer.flip();
                //将数据写入缓冲区
                channel.write(byteBuffer);
                System.out.println("写入结束1");

            } else {
                while (num < b.length) {
                    System.out.println("起始位置" + num + "  数据长度:" + size);
                    byteBuffer.clear();

                    if (b.length-num<size){
                        byteBuffer.put(b, num, (b.length-num));
                        byteBuffer.flip();
                        //将数据写入缓冲区
                        channel.write(byteBuffer);
                        System.out.println("写入结束22");
                        break;
                    }else {
                        byteBuffer.put(b, num, size);
                        num = num + 2;
                        byteBuffer.flip();
                        //将数据写入缓冲区
                        channel.write(byteBuffer);
                        System.out.println("写入结束2");
                    }

                }
            }
            channel.close();


            //System.out.println(byteBuffer.put("hello word".getBytes()));

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

    }


    /**
     * 从文件读取数据
     */
    @Test
    public void read() {
        try {
            //1:输出流 写入文件
            FileInputStream fileInputStream = new FileInputStream("/Users/huyiju/Desktop/log/huyiju.txt");
            //2:字节流得到通道
            FileChannel channel = fileInputStream.getChannel();
            //3:分配缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(8);
            //4:讲述数据写入缓冲区

            //将数据写入缓冲区
            while (true) {
                byteBuffer.clear();//先清空缓冲区

                int flag = channel.read(byteBuffer);
                if (flag == -1) {
                    break;
                }
                byteBuffer.flip();
                String string = new String(byteBuffer.array(), 0, byteBuffer.limit());
                System.out.println(string);
            }

            channel.close();
            System.out.println("读取结束");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /**
     * 从文件读取数据 然后写入文件
     */
    @Test
    public void readAndWrite() {
        try {
            //1:输出流 写入文件
            FileInputStream fileInputStream = new FileInputStream("/Users/huyiju/Desktop/log/huyiju.txt");
            FileOutputStream fileOutputStream = new FileOutputStream("/Users/huyiju/Desktop/log/huyiju_new.txt");

            //2:字节输入数据流得到不同的通道
            FileChannel channelin = fileInputStream.getChannel();
            FileChannel channelout = fileOutputStream.getChannel();

            //3:分配缓冲区
            ByteBuffer byteBuffer = ByteBuffer.allocate(4);
            //4:讲述数据写入缓冲区


            //将数据写入缓冲区
            while (true) {
                byteBuffer.clear();//先清空缓冲区

                int flag = channelin.read(byteBuffer);//数据量大 按照缓冲区读 然后写
                if (flag == -1) {
                    break;
                }
                byteBuffer.flip();
                channelout.write(byteBuffer);
            }


            channelin.close();
            channelout.close();
            System.out.println("先读数据 在写数据");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


    /**
     * 从文件读取数据 然后写入文件
     */
    @Test
    public void transTo() {
        try {
            //1:原通道
            FileInputStream fileInputStream = new FileInputStream("/Users/huyiju/Desktop/log/huyiju.txt");
            FileChannel fileChannelIn = fileInputStream.getChannel();

            //2:复制通道
            FileOutputStream fileOutputStream = new FileOutputStream("/Users/huyiju/Desktop/log/huyiju_new1.txt");
            FileChannel fileChannelOut = fileOutputStream.getChannel();


            //3:复制通道
            FileOutputStream fileOutputStream2 = new FileOutputStream("/Users/huyiju/Desktop/log/huyiju_new2.txt");
            FileChannel fileChannelOut2 = fileOutputStream2.getChannel();


            //3:复制数据 transferFrom(来源通道)
            //fileChannelOut.transferFrom(fileChannelIn,fileChannelIn.position(),fileChannelIn.size());

            //3.1:复制数据 transferTo(来源通道,目标通道)
            fileChannelIn.transferTo(fileChannelIn.position(), fileChannelIn.size(), fileChannelOut2);

            fileChannelIn.close();
            fileChannelOut.close();
            fileChannelOut2.close();
            System.out.println("复制数据");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }


}

3.3:Selector(选择器)

作用:Selector可以根据不同的事件选择不同的选择器,要使用selector必须将channel注册到选择器上。采用多路复用。

此处代码采用,bytebuffer(缓冲区)、ServerSocketChannel(网络通道)、Selector实现网络通信。

此处代码采用多个客户端启动,服务端一个,然后查看运行结果

/**
 * @date :8/31/21 12:09 AM
 * NIO 非阻塞通信 服务端开发
 * 可以应对多客户端,非阻塞模式
 */
public class ServiceHu {
    public static void main(String[] args) throws IOException {
        //1:获取服务端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //2:切换非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //3:绑定端口
        serverSocketChannel.bind(new InetSocketAddress(9997));

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

        //5:将服务端通道注册到选择器

        //OP_READ = 1 << 0; 读
        //OP_WRITE = 1 << 2; 写
        //OP_CONNECT = 1 << 3;链接
        //OP_ACCEPT = 1 << 4; 接收
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        System.out.println("1:服务端启动");

        //6:使用selector 轮循环事件>0
        while (selector.select() > 0) {
            System.out.println("2:客户端启动会进入选择器");
            //迭代所有的事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                System.out.println("进入迭代事件");
                //7:提取当期事件
                SelectionKey selectionKey = iterator.next();
                //8:判断事件是否是接收事件
                if (selectionKey.isAcceptable()) {
                    System.out.println("3:客户端启动会进入接受事件");
                    //9:获取客户端通道
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    //10:将客户端注入选择器 读事件
                    socketChannel.register(selector, SelectionKey.OP_READ);

                } else if (selectionKey.isReadable()) {
                    System.out.println("4:进入可读事件");

                    //11:如果是读事件 获取客户端通道
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    //12:读取数据
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int len;
                    while ((len = socketChannel.read(byteBuffer)) > 0) {
                        byteBuffer.flip();
                        System.out.println("服务端获取数据输出:" + new String(byteBuffer.array(), 0, len));
                        byteBuffer.clear();
                    }


                } else if (selectionKey.isWritable()) {

                }
                iterator.remove();//处理完毕 移除当前事件
            }

        }


    }
}


======================以下是客户端代码==================

/**
 * @date :8/30/21 11:56 PM
 * 客户端发送数据
 */
public class Client1 {
    public static void main(String[] args) throws IOException {
        //1:获取客户端通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9997));
        //2:设置通道是否堵塞false 非阻塞
        socketChannel.configureBlocking(false);
        //3:设置缓冲区容量
        ByteBuffer byteBuffer=ByteBuffer.allocate(1024);
        //4:得到输入
        Scanner scanner=new Scanner(System.in);
        //5:循环
        while (true){
            System.out.println("输入要发送数据");
            String msg=scanner.nextLine();

            byteBuffer.put(msg.getBytes());
            byteBuffer.flip();//通道可读

            socketChannel.write(byteBuffer);

            byteBuffer.clear();//重置缓冲区

        }


    }

}

运行结果省略

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值