一、NIO简介

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35241080/article/details/82557841

NIO简介
javaNIO是有java1.4之后引入的一个新的IO API,可以提完标准的IO API,NIO与IO有相同的作用和目的,但使用方式完全不一样,NIO面向缓冲区、基于通道的IO操作,将以更加高效的方式进行文件读写操作。

IO NIO
面向流 面向缓冲区
阻塞 非阻塞
无 选择器

NIO:核心在于通道(channel)和缓冲区(buffer)。

通道表示打开到io设备(例如:文件、套接字)的连接若需要使用NIO系统,需要获取用于连接设备的通道以及用于容纳数据的缓存区,然后操作缓存区,对数据进行处理。

简单说,channel负责传输,buffer负责存储

一、缓冲区(Buffer):在 Java NIO 中负责数据的存取。缓冲区就是数组。用于存储不同数据类型的数据

1.根据数据类型不同(boolean 除外),提供了相应类型的缓冲区:

ByteBuffer

CharBuffer

ShortBuffer

IntBuffer

LongBuffer

FloatBuffer

DoubleBuffer

上述七种缓冲区的管理方式几乎一致,通过 allocate() 获取缓冲区

  ByteBuffer buf = ByteBuffer.allocate(1024);

2.缓冲区存取数据的两个核心方法:

put() : 存入数据到缓冲区中

get() : 获取缓冲区中的数据

 @Test
    public void test2(){
      ByteBuffer bf = ByteBuffer.allocate(1024);//创建一个缓冲区
      bf.put("abc".getBytes());//往缓冲区放入数据
      bf.flip();              //切换操作模式
      byte[] dst = new byte[bf.limit()];//创建一个以缓冲区中存放数据大小相同的字节数组
      bf.get(dst,0,dst.length);//从0的位置获取的数组长度的位置数据到dst数组中
      System.out.println(new String(dst));//字节数据转换为字符窜打印
      //abc
    }

3.缓冲区中的四个核心属性:

capacity : 容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变。

limit : 界限,表示缓冲区中可以操作数据的大小。(limit 后数据不能进行读写)

position : 位置,表示缓冲区中正在操作数据的位置。

mark : 标记,表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置

0 <= mark <= position <= limit <= capacity

    public abstract class Buffer {
        private int mark = -1;//标记
        private int position = 0;//当前位置
        private int limit;      //可操作空间大小
        private int capacity;   //容量
    }

操作演示:

        @Test
        public void test3(){
            ByteBuffer bf = ByteBuffer.allocate(1024);
            System.out.println("---------未放入数据时---------");
            System.out.println("bf.position()="+bf.position());
            System.out.println("bf.limit()="+bf.limit());
            System.out.println("bf.capacity()="+bf.capacity());
            System.out.println("---------放入数据abcd---------");
            bf.put("abcd".getBytes());
            System.out.println("bf.position()="+bf.position());
            System.out.println("bf.limit()="+bf.limit());
            System.out.println("bf.capacity()="+bf.capacity());
            System.out.println("---------flip()切换操作模式---------");
            bf.flip();
            System.out.println("bf.position()="+bf.position());
            System.out.println("bf.limit()="+bf.limit());
            System.out.println("bf.capacity()="+bf.capacity());
            System.out.println("---------mark()的同时读取两个字节的数据---------");
            bf.mark();
            bf.get(new byte[2],0,2);
            System.out.println("bf.position()="+bf.position());
            System.out.println("bf.limit()="+bf.limit());
            System.out.println("bf.capacity()="+bf.capacity());
            System.out.println("---------reset()到上一次mark()位置---------");
            bf.reset();
            System.out.println("bf.position()="+bf.position());
            System.out.println("bf.limit()="+bf.limit());
            System.out.println("bf.capacity()="+bf.capacity());
        }
    /**
    ---------未放入数据时---------
    bf.position()=0
    bf.limit()=1024
    bf.capacity()=1024
    ---------放入数据abcd---------
    bf.position()=4
    bf.limit()=1024
    bf.capacity()=1024
    ---------flip()切换操作模式---------
    bf.position()=0
    bf.limit()=4
    bf.capacity()=1024
    ---------mark()的同时读取两个字节的数据---------
    bf.position()=2
    bf.limit()=4
    bf.capacity()=1024
    ---------reset()到上一次mark()位置---------
    bf.position()=0
    bf.limit()=4
    bf.capacity()=1024
    */

4.直接缓冲区与非直接缓冲区:

非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在 JVM 的内存中

直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在物理内存中。可以提高效率
这里写图片描述
这里写图片描述

 @Test//判断缓冲区类型
    public void test1(){
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      ByteBuffer buffer2 = ByteBuffer.allocateDirect(1024);
    }

5.其它方法

isDirect();//判断是否是直接缓冲区

flip();切换操作模式

reset();恢复到mark的位置

rewind() : 可重复读

clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态

  @Test//判断缓冲区类型
    public void test1(){
      ByteBuffer buffer = ByteBuffer.allocate(1024);
      ByteBuffer buffer2 = ByteBuffer.allocateDirect(1024);
      System.out.println(buffer.isDirect());//false
      System.out.println(buffer2.isDirect());//true

    }
    @Test
        public void test1(){
            String str = "abcde";

            //1. 分配一个指定大小的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);

            System.out.println("-----------------allocate()----------------");
            System.out.println(buf.position());
            System.out.println(buf.limit());
            System.out.println(buf.capacity());

            //2. 利用 put() 存入数据到缓冲区中
            buf.put(str.getBytes());

            System.out.println("-----------------put()----------------");
            System.out.println(buf.position());
            System.out.println(buf.limit());
            System.out.println(buf.capacity());

            //3. 切换读取数据模式
            buf.flip();

            System.out.println("-----------------flip()----------------");
            System.out.println(buf.position());
            System.out.println(buf.limit());
            System.out.println(buf.capacity());

            //4. 利用 get() 读取缓冲区中的数据
            byte[] dst = new byte[buf.limit()];
            buf.get(dst);
            System.out.println(new String(dst, 0, dst.length));

            System.out.println("-----------------get()----------------");
            System.out.println(buf.position());
            System.out.println(buf.limit());
            System.out.println(buf.capacity());

            //5. rewind() : 可重复读
            buf.rewind();

            System.out.println("-----------------rewind()----------------");
            System.out.println(buf.position());
            System.out.println(buf.limit());
            System.out.println(buf.capacity());

            //6. clear() : 清空缓冲区. 但是缓冲区中的数据依然存在,但是处于“被遗忘”状态
            buf.clear();

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

            System.out.println((char)buf.get());
        }

二、通道Channel

1.通道channel介绍

有java.nio.channels包定义的。channel表示IO源于目标打开的连接,Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。

这里写图片描述
这里写图片描述
这里写图片描述

2.通道的主要实现类

java.nio.channels.Channel 接口:

    |--FileChannel:用于读取、写入、映射和操作文件的通道,用于本地文件操作。

    |--SocketChannel:通过TCP读写网络中的数据。

    |--ServerSocketChannel:可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel。

    |--DatagramChannel:通过UDP读写网络中的数据通道。

3.获取通道

3.1 Java 针对支持通道的类提供了 getChannel() 方法

    本地 IO:用于本地数据操作

            FileInputStream/FileOutputStream

            RandomAccessFile
    //以FileInputStream为例
    FileInputStream fisChannel = new FileInputStream("需要读取的文件");
    FileChannel fisChannel = fis.getChannel();//获取通道
    网络IO:用于网络传输数据

            Socket

            ServerSocket

            DatagramSocket

3.2使用JDK 1.7中的NIO.2 针对各个通道提供的静态方法open();

调用示例
  FileChannel fis = FileChannel.open(Paths.get("d:/tomcat.zip"), StandardOpenOption.READ);
    //源码:OpenOption... options--可变形参
    public static FileChannel open(Path path, OpenOption... options)
            throws IOException
        {
            Set<OpenOption> set = new HashSet<OpenOption>(options.length);
            Collections.addAll(set, options);
            return open(path, set, NO_ATTRIBUTES);
        }
3.3在 JDK 1.7 中的 NIO.2 的 Files 工具类的 newByteChannel()
  //源码
    public static SeekableByteChannel newByteChannel(Path path, OpenOption... options)
            throws IOException
        {
            Set<OpenOption> set = new HashSet<OpenOption>(options.length);
            Collections.addAll(set, options);
            return newByteChannel(path, set);
        }
        //FileChannel关系
    public abstract class FileChannel
        extends AbstractInterruptibleChannel
        implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel

    //示例:可知返回是SeekableByteChannel类型,SeekableByteChannelFileChannel的父接口
    SeekableByteChannel newByteChannel = Files.newByteChannel(Paths.get("d:/tomcat.zip"), StandardOpenOption.READ);

4.通道之间的数据传输

transferFrom():从哪里来,输出管道中的数据从哪里来

transferTo():到哪里去,读取管道的数据要输出到哪里去

示例代码一会会有专门一篇记录

5.分散(Scatter)与聚集(Gather)

 分散读取(Scattering Reads):将通道中的数据分散到多个缓冲区中

聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中
@SuppressWarnings("resource")
        @Test
        public void test5() throws IOException{
            //1.创建操作对象,指定操作类型
            RandomAccessFile ra1 = new RandomAccessFile("1.txt", "rw");
            //2.获取通道
            FileChannel channel = ra1.getChannel();
            //3.创建缓冲区
            ByteBuffer by1 = ByteBuffer.allocate(100);
            ByteBuffer by2 = ByteBuffer.allocate(100);
            ByteBuffer by3 = ByteBuffer.allocate(1024);
            ByteBuffer[] buf = {by1,by2,by3};
            //4.将通道中的数据读入缓冲区
            channel.read(buf);
            //5.操作指令转换,准备从0开始写数据模式
            for (ByteBuffer byteBuffer : buf) {
                byteBuffer.flip();
            }
            //打印每个分散数据值
            System.out.println("-- "+new String(buf[0].array(),0,buf[0].limit()));
            System.out.println("-- "+new String(buf[1].array(),0,buf[1].limit()));
            System.out.println("-- "+new String(buf[2].array(),0,buf[2].limit()));
            //6.创建输出对象,指定输出数据位置,操作指令
            RandomAccessFile ra2 = new RandomAccessFile("2.txt", "rw");
            //7.获取通道
            FileChannel channel2 = ra2.getChannel();
            //8.写出数据
            channel2.write(buf);
            //9.关闭通道
            channel.close();
            channel2.close();
        }

6.字符集:Charset

编码:字符串 -> 字节数组

解码:字节数组  -> 字符串

查看NIO中支持的字符集

        @Test
        public void test5(){
            //查看NIO中支持的字符集
            Map<String, Charset> map = Charset.availableCharsets();

            Set<Entry<String, Charset>> set = map.entrySet();

            for (Entry<String, Charset> entry : set) {
                System.out.println(entry.getKey() + "=" + entry.getValue());
            }
        }

编码与解码演示

 @Test
        public void test6() throws CharacterCodingException{
            String str = "java从入门到放弃";
            Charset charset = Charset.forName("GBK");
            CharsetEncoder en = charset.newEncoder();
            CharsetDecoder de = charset.newDecoder();
            CharBuffer buffer = CharBuffer.allocate(1024);
            buffer.put(str);
            buffer.flip();
            ByteBuffer encode = en.encode(buffer);
            for(int i = 0;i<encode.limit();i++){
                //encode.get():调用一次position位置向后移动一次
                System.out.println(encode.get());
            }
            //切换指令准备解码,由此时get到末尾的position指向首位
            encode.flip();
            //2.同一对象,相同编码格式
            CharBuffer decode = de.decode(encode);
    //      System.out.println("同一对象解码器="+new String(decode.array(),0,decode.limit()));
            System.out.println("同一对象解码器="+decode.toString());
            //2.不同对象,相同编码格式
            Charset charset2 = Charset.forName("GBK");
            CharsetDecoder de2 = charset2.newDecoder();
            encode.flip();
            CharBuffer decode2 = de2.decode(encode);
            System.out.println("不同对象,同一格式解码器="+decode2.toString());
            //3.不同对象,不同编码格式
            //切换position位置
            encode.flip();
        /*  Charset charset3= Charset.forName("utf-8");
            CharsetDecoder de3 = charset3.newDecoder();
            CharBuffer decode3 = de3.decode(encode);
            System.out.println("不同象,不同格式解码器="+decode3.toString());*/
            Charset charset3= Charset.forName("utf-8");
            CharBuffer decode3 = charset3.decode(encode);
            System.out.println("不同对象,不同格式解码器="+decode3.toString());
            /**
    同一对象解码器=java从入门到放弃
    不同对象,同一格式解码器=java从入门到放弃
    不同对象,不同格式解码器=java�����ŵ�����

             */
        }

来源于尚硅谷官网NIO视频

猜你喜欢

转载自blog.csdn.net/qq_35241080/article/details/82557841
今日推荐