欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java笔记-IO、NIO学习

程序员文章站 2024-02-16 10:33:10
...

IO 流

结构图

java笔记-IO、NIO学习

概念

流是一个抽象概念,可以抽象成一个数据传输的管道,是相对于程序来说的,按照方向来分:程序从外部读取信息时的管道就是输入流(InputStream的子类),把数据传输到外部时就是输出流(OutputStream的子类)。按照流的基本单位来分:传输过程中,传输数据的最基本单位是字节的流为字节流(通常以stream结尾),基本单位是字符的流为字符流(通常以reader和writer结尾)

字符流和字节流、输入流与输出流

[https://blog.csdn.net/zhaoyanjun6/category_6684545.html]:

同步、异步、阻塞、非阻塞

**同步:**在发出一个功能调用之后,在没有得到结果之前,该调用就不会返回。也就是说调用就必须是一件一件是的来做,只能等前一件事做完才能开始下一件事。通俗来说:调用不返回结果,我死等结果

**异步:**当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。通俗来说:调用后不需要管结果是否咋样,给结果以后会在完成后通知我。例如ajax。

**阻塞:**调用结果返回前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行),函数只有在得到结果之后才会返回。与同步不同,针对的对象不一样。同步一般指的是调用者。阻塞一般指的是被调用者

**非阻塞:**指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

Linux 五中IO模型

阻塞I/O(blocking I/O)
非阻塞I/O (nonblocking I/O)
I/O复用(select 和poll) (I/O multiplexing)
信号驱动I/O (signal driven I/O (SIGIO))
5)异步I/O (asynchronous I/O (the POSIX aio_functions))

参考:https://www.cnblogs.com/chaser24/p/6112071.html

BIO(同步阻塞IO)

就是传统的java.io包,它是基于流模型实现的,交互的方式是同步、阻塞方式。也就是说在读入输入流或者输出流时,在读写动作完成之前,线程会一直阻塞在那里,它们之间的调用顺序是可靠的线性顺序。它的有点就是代码比较简单、直观;缺点就是 IO 的效率和扩展性很低,容易成为应用性能瓶颈

NIO(同步非阻塞IO)

是 Java 1.4 引入的 java.nio 包,提供了 Channel(通道)、Selector(选择)、Buffer(缓冲) 等新的抽象,可以构建多路复用的、同步非阻塞 IO 程序,同时提供了更接近操作系统底层高性能的数据操作方式。与传统IO最大的区别就是传统IO是面向流的,而NIO面向的是缓冲区。

Channel

概述:

类似于Stream,不同的是Stream是单向的,而Channel是双向的 ,既可以进行读操作,也可以进行写操作

常用类

  • FileChannel(文件通道,一般用来处理文件)
  • DatagramChannel(数据报通道,一般用来处理UDP发送的数据报)
  • SocketChannel(TCP服务端通道)
  • ServerSocketChannel(TCP客户端通道)
package com.learn.javaNIO;


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;

/**
 * 1.通道(channel):用于源节点与目标节点的连接。在NIO中负责缓冲区中数据的传输。channel本身不存在数据,
 *  因此需要配合缓冲区进行传输
 *
 * 2.通道的主要实现
 *  java.nio.channels.Channel接口:
 *  FileChannel
 *  SocketChannel
 *  ServerSocketChannel
 *  DatagramChannel
 *
 *  3.获取通道
 *    1.java针对支持通道的类提供了getChannel()方法
 *      本地IO:
 *          FileInputStream/FileOutputStream
 *          RandomAccessFile
 *
 *      网络IO:
 *          Socket
 *          ServerSocket
 *          DatagramSocket
 *   2.在JDK1.7后中的NIO2中针对各个通道提供了静态方法open()
 *   3.在JDK1.7中NIO2中Files工具的newByteChannel()
 *
 * 4.通道直接的数据传输
 *  transferFrom()
 *  transferTo()
 *
 * 5.放散(Scatter) 与 聚集(Gather)
 *  分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中,按照缓冲区的顺序,从
 *                          Channel中读取的数据依次将Buffer填满
 *  聚集写入(Gathering writes):将多个缓冲区的数据聚集道通道中,按照缓冲区的顺序,
 *                          写入position和limit之间的数据到Channel
 *
 * 6.字符集:Charset
 *  编码:字符串->字节数组
 *  解码: 字节数组 -> 字符串
 */
public class ChannelTest {

    private ByteBuffer buffer;

    @Test
    public void test1() throws IOException {
        //利用通道完成文件的复制
        FileInputStream fis = new FileInputStream("E:\\learn_source\\java\\source\\class_fx_zj\\src\\1.txt");
        FileOutputStream fos = new FileOutputStream("E:\\learn_source\\java\\source\\class_fx_zj\\src\\2.txt");

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

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

        //将通道中的数据存入缓冲区中
        while ((inChannel.read(buffer)) != -1){
            buffer.flip();//切换成读取模式
            //将缓冲区的数据写入通道中
            outChannel.write(buffer);
            buffer.clear();
        }
        if(outChannel != null){
            outChannel.close();
        }
        if(inChannel != null){
            inChannel.close();
        }
    }

    /**
     * 使用直接缓冲区完成文件的复制
     */
    @Test
    public void test2() throws Exception{
        FileChannel inChannel = FileChannel.open(Paths.get("E:\\learn_source\\java\\source\\class_fx_zj\\src\\1.txt"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("E:\\learn_source\\java\\source\\class_fx_zj\\src\\3.txt"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);

        //内存映射文件(只有byteBuffer支持),与allocateDirect一样
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

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

    }

    @Test
    public void test3() throws IOException {
        FileChannel inChannel = FileChannel.open(Paths.get("E:\\learn_source\\java\\source\\class_fx_zj\\src\\1.txt"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("E:\\learn_source\\java\\source\\class_fx_zj\\src\\5.txt"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);
        //直接缓冲区
        //inChannel.transferTo(0,inChannel.size(),outChannel);
        outChannel.transferFrom(inChannel,0,inChannel.size());

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

    @Test
    public void test4() throws Exception{
        RandomAccessFile raf = new RandomAccessFile("E:\\learn_source\\java\\source\\class_fx_zj\\src\\1.txt","rw");

        //获取通道
        FileChannel fileChannel = raf.getChannel();

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

        //分散读取
        ByteBuffer[] bufs = {buffer,buffer1};
        fileChannel.read(bufs);

        for (ByteBuffer buf : bufs) {
            buf.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 raf1 = new RandomAccessFile("E:\\learn_source\\java\\source\\class_fx_zj\\src\\6.txt","rw");
        FileChannel channel = raf1.getChannel();
        channel.write(bufs);

    }

    /**
     * 字符集
     */
    @Test
    public void test5(){
        SortedMap<String, Charset> charsetMap = Charset.availableCharsets();
        Set<Map.Entry<String, Charset>> entries = charsetMap.entrySet();
        //查看所支持的字符集
        for (Map.Entry<String, Charset> entry : entries) {
            System.out.println(entry.getKey()+"="+entry.getValue());
        }
    }

    /**
     * 字符集使用
     */
    @Test
    public void test6() throws CharacterCodingException {
        Charset cs1 = Charset.forName("GBK");
        //获取编码器
        CharsetEncoder encoder = cs1.newEncoder();
        //获取解码器
        CharsetDecoder decoder = cs1.newDecoder();
        //分配一个指定大小的缓冲区
        CharBuffer buffer = CharBuffer.allocate(1024);
        buffer.put("中国加油,武汉加油");
        buffer.flip();
        //使用编码器进行编码
        ByteBuffer byteBuffer = encoder.encode(buffer);
        for (int i = 0; i < byteBuffer.limit(); i++) {
            System.out.println(byteBuffer.get());
        }
        System.out.println("====================");
        //使用解码器进行解码
        byteBuffer.flip();
        CharBuffer buffer2 = decoder.decode(byteBuffer);
        System.out.println(buffer2.toString());
        System.out.println("====================");
        //按照UTF-8解码
        byteBuffer.flip();
        Charset charset = Charset.forName("UTF-8");
        CharBuffer charBuffer = charset.decode(byteBuffer);
        System.out.println(charBuffer.toString());
    }
}

Buffer

package com.learn.javaNIO;

import org.junit.Test;

import java.nio.ByteBuffer;

/**
 * 1、缓冲区:在java NIO总负责数据的存取。缓冲区本质上就是数组,用于存储不同的数据
 * 更具数据类型不同(boolean除外),提供了相应类型的缓冲区
 * ByteBuffer
 * CharBuffer
 * ShortBuffer
 * IntBuffer
 * LongBuffer
 * FloatBuffer
 * DoubleBuffer
 *
 * 2、缓冲区存取数据的两个核心方法:
 *      put():存入数据到缓冲区中
 *      get():获取缓冲区中的数据
 *
 * 3、缓冲区中的四个核心属性
 *      private int mark = -1;//标记,可以记录当前position的位置,可以通过reset()恢复到mark的位置
 *      private int position= 0;位置,表示缓冲区中正在操作的数据位置
 *      private int limit;界限,表示缓冲区总可以操作数据的大小。(limit后的数据不能进行读写)
 *      private int capacity;容量,标识缓冲区中最大存储数据的容量。一旦申明就不能更改
 *  0 <= mark <= position <= limit <= capacity
 *
 *
 * 4.直接缓冲区与非直接缓冲区:
 *      非直接缓冲区:通过allocate()方法分配缓冲区,将缓冲区建立在jvm中
 *      直接缓冲区:通过allocateDirect()方法分配直接缓冲区,将缓冲区建立在物理内存中,可以提高效率,但会占用更多的资源
 *
 *
 */
public class BufferTest {

    @Test
    public void test01(){
        //分配一个指定带下的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        System.out.println("--------allocate()----------");
        System.out.println(buffer.position());//0
        System.out.println(buffer.capacity());//1024
        System.out.println(buffer.limit());//1024

        //2.利用put方法存入数据到缓冲区中(写数据模式)
        String str = "abcde";
        buffer.put(str.getBytes());
        System.out.println("--------put()----------");
        System.out.println(buffer.position());//5
        System.out.println(buffer.capacity());//1024
        System.out.println(buffer.limit());//1024

        //3.切换成读取数据模式,调用flip()方法
        buffer.flip();
        System.out.println("--------flip()----------");
        System.out.println(buffer.position());//0
        System.out.println(buffer.capacity());//1024
        System.out.println(buffer.limit());//5


        //4.利用get()读取缓冲区的数据
        byte[] dist = new byte[buffer.limit()];
        buffer.get(dist);
        System.out.println(new String(dist,0,dist.length));
        System.out.println("--------get()----------");
        System.out.println(buffer.position());//5
        System.out.println(buffer.capacity());//1024
        System.out.println(buffer.limit());//5

        //5.rewind():重新回到读写模式的最初状态
        buffer.rewind();
        System.out.println("--------rewind()----------");
        System.out.println(buffer.position());//0
        System.out.println(buffer.capacity());//1024
        System.out.println(buffer.limit());//5

        //6.clear()清空缓冲区,但是缓冲区的数据依然存在。只是处在“被遗忘”状态
        buffer.clear();
        System.out.println("--------clear()------------");
        System.out.println(buffer.position());//0
        System.out.println(buffer.capacity());//1024
        System.out.println(buffer.limit());//1024

        System.out.println((char)buffer.get());//a
    }

    @Test
    public void test2(){
        String str = "abcdefg";
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(str.getBytes());
        buffer.flip();
        byte[] dist = new byte[buffer.limit()];
        buffer.get(dist,0,2);
        System.out.println(new String(dist,0,dist.length));
        //在此处标记一个mark
        buffer.mark();
        buffer.get(dist,2,2);
        System.out.println(new String(dist,2,2));
        System.out.println(buffer.position());
        //此时position为4,使用reset恢复到mark标记位置
        buffer.reset();
        System.out.println(buffer.position());
    }

    @Test
    public void test3(){
        //分配直接缓冲区
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
        //判断是否是直接缓冲区 可以调用isDirect()
        System.out.println(buffer.isDirect());//true
    }
}

Selector

package com.learn.javaNIO;


import org.junit.Test;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 选择器测试(NIO多路复用)
 *  Selector:
 *      可以注册到很多个Channel上,监听各个Channel上发生的事件,并且能够根据事件情况决定Channel读写。
 *      这样,通过一个线程管理多个Channel,就可以处理大量网络连接了。
 *
 */
public class SelectorTest {

    @Test
    public  void testServer() throws Exception{
        //通过Selector.open()方法来创建一个Selector对象
        Selector selector = Selector.open();
        //创建一个服务端channel通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置为异步,要是用selector监听通道,必须将通道设置为非阻塞,所以FileChannel不能用Selector监听,因为其不能设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        /**
         * 将通道注册到selector上
         *      serverSocketChannel.register()注册方法的返回值,可以用来表示当前通道当前的状态,
         *          用在注册方法的参数是,可以用来表示触发当前监听的
         *
         *      register(Selector sel, int ops, Object att):注册通道方法,返回一个SelectionKey对象
         *         sel: 要注册的选择器对象
         *         ops:SelectionKey.OP_CONNECT:    连接就绪事件,表示客户端与服务器的连接已经建立成功
         *              SelectionKey.OP_ACCEPT:  接收连接事件,表示服务器监听到了客户连接,服务器可以接收这个连接了
         *              SelectionKey.OP_READ: 读 就绪事件,表示通道中已经有了可读的数据,可以执行读操作了
         *              SelectionKey.OP_WRITE:  写 就绪事件,表示已经可以向通道写数据了
         *         att: 附加的对象(可选值),一般用来放一个缓冲区
         *
         *     返回的SelectionKey包含了以下属性
         *          interest集合:selectionKey.interestOps()
         *          ready集合:selectionKey.interestOps(),可以用来判断当前处在哪个阶段,也可用以下四个方法判断
         *                     isConnectable()
         *                     isAcceptable()
         *                     isReadable()
         *                     isWritable()
         *          Channel:
         *          Selector:
         *          附加的对象(可选):
         *
         */
        //serverSocketChannel注册时需要使用SelectionKey.OP_ACCEPT,其他事件无效且会报非法参数异常
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

        //绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(9999));
        while(true){
            int select = selector.select();
            if(select == 0){
                continue;
            }
            //拿到所有注册的keys
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = keys.iterator();
            while (iterator.hasNext()){
                SelectionKey next = iterator.next();
                if(next.isAcceptable()){//接受就绪状态
                    ServerSocketChannel channel = (ServerSocketChannel)next.channel();
                    SocketChannel socketChannel = channel.accept();
                    socketChannel.configureBlocking(false);
                    //注册客户端接受的channel
                    socketChannel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
                }
                if(next.isConnectable()){//连接就绪状态
                    System.out.println("isConnectable is true");
                }
                if(next.isReadable()){//读就绪状态
                    ByteBuffer buf = (ByteBuffer)next.attachment();
                    SocketChannel socketChannel = (SocketChannel) next.channel();
                    while (socketChannel.read(buf) > 0){
                        buf.flip();
                        System.out.println(new String(buf.array(),0,buf.limit()));
                        buf.clear();
                    }
                }
                if(next.isWritable()){//写就绪状态
                    ByteBuffer buf = (ByteBuffer)next.attachment();
                    SocketChannel socketChannel = (SocketChannel) next.channel();
                    while (buf.hasRemaining()){
                        socketChannel.write(buf);
                    }
                }
                iterator.remove();
            }
        }
    }


    @Test
    public void testClient(){
        //使用try-with-resource自动关闭socketChannel
        try (
            //创建socketChannel,并绑定到点前主机的9999端口号
            SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress(InetAddress.getLocalHost(), 9999));
        ){
            //设置为非阻塞
            socketChannel.configureBlocking(false);
            if(socketChannel.finishConnect()){
                socketChannel.write(ByteBuffer.wrap("nextLine".getBytes()));
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

AIO(异步非阻塞IO)

是 Java 1.7 之后引入的包,是 NIO 的升级版本,提供了异步非堵塞的 IO 操作方式,所以人们叫它 AIO(Asynchronous IO),异步 IO 是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作

  • AsynchronousSocketChannel
  • AsynchronousServerSocketChannel
  • AsynchronousFileChannel
  • AsynchronousDatagramChannel