zoukankan      html  css  js  c++  java
  • 2019-04-27 java NIO的知识

    一、NIO介绍

    1、在软件系统中,由于I/O的速度要比内存速度慢,因此,I/O读写在很多场合都会成为系统的瓶颈。提升I/O速度,对提升系统整体性能有着很大的好处。

      在java标准的I/O中,提供了基于流的I/O实现,及InputStream和outputStream.这种基于流的实现以 字节为单位处理数据,并且非常容易建立各种过滤器。

      NIO是NEW I/O的简称,与旧式的基于流的I/O方法相对,表示新的一套java i/o标准。Java1.4加入到JDK中,有以下特性:

    • 为所有的原始类型提供(Buffer)缓存支持;
    • 使用Java.nio.charset.Charset作为字符集编码解码解决方案;
    • 增加通道(Channel)对象,作为新的原始I/O抽象;
    • 支持锁和内存映射文件的文件访问接口;
    • 提供了基于Selector的异步网络I/O。

      与流式的I/O不同,NIO是基于块(Block)的,他以块为基本单位处理数据,在NIO中最为重要的两个组件是缓存Buffer和通道Channel。缓存是一块连续的内存块,是NIO读写数据的中转地。通道表示缓冲数据的源头或者目的地,他用于缓冲读取或者写入数据,是访问缓冲的接口,如下图展示子通道和缓冲的关系。

      

    2、NIO的Buffer类族和Channel

      在NIO的实现中,Buffer是一个抽象类。JDK为每一种Java原生类型都创建了一个Buffer,

      除了ByteBuffer外,其他每一种Buffer都具有完全一样的操作,唯一的区别仅仅在于他们所对应的数据类型。因为ByteBuffer多用于绝大多数标准I/O操作的接口,因此他有一些特殊的方法。

      在NIO中和Buffer配合使用的还有Channel,Channel是一个双向通道,即可读,也可写,有点类似Stream,但Stream是单向的,应用程序中不能直接对Channel进行读写操作,而必须通过Buffer来进行。比如,在读一个Channel的时候,需要先将数据读入到相应的Buffer,然后再Buffer中进行读取。

    public static void niocopeFile(String resource, String destination) throws Exception{
            FileInputStream fis=new FileInputStream(resource);
            FileOutputStream fos=new FileOutputStream(destination);
            FileChannel readChannel = fis.getChannel();
            FileChannel writerChannel = fos.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int len;
            while(true){
                //为读入数据到buffer做准备
                buffer.clear();
                //讀入数据 ,,内容已存入buffer中
                len = readChannel.read(buffer);
                if(len==-1){
                    //讀取完畢
                    break;
                }
                //从 写状态 转为 读状态,flip()方法主要是在“读写切换时”调用
                buffer.flip();
                writerChannel.write(buffer);
                
            }
            readChannel.close();
            writerChannel.close();
            System.out.println("复制完");
        }

     3、Buffer的基本原理

      Buffer中有三个重要的参数:位置(position) 、容量(capactiy)和上限(limit),三者的含义如下:

      为了更好理解Buffer的工作模式,实现以下实例:

    public static void demo1(){
            ByteBuffer b=ByteBuffer.allocate(20);//15个字节大小的缓冲区
            System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
            for(int i=0;i<10;i++){
                b.put((byte)i);                    //存入10个字节数据
            }
            System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
            //重置position
            b.flip();    
            System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
            for (int i = 0; i < 5; i++) {
                System.out.print(b.get()+" ");
            }
            System.out.println();
            System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
            b.flip();    //切到写
            System.out.println("position="+b.position()+" limit="+b.limit()+" capactiy= "+b.capacity());
        }

      输出如下:

    position=0 limit=20 capactiy= 20
    position=10 limit=20 capactiy= 20
    position=0 limit=10 capactiy= 20
    0 1 2 3 4 
    position=5 limit=10 capactiy= 20
    position=0 limit=5 capactiy= 20

      Buffer的三个重置和清空方法:实际上就是对缓冲区的参数进行操作

      

    //比如用在复制该buffer的有效数据到另一个数组里面
    public
    final Buffer rewind() { position = 0; mark = -1; return this; }
    public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
    public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }

      标志缓冲区:mark()方法

    public static void demo2(){
            ByteBuffer b=ByteBuffer.allocate(20);
            for (int i = 0; i < 15; i++) {
                b.put((byte)i);
            }
            b.flip();//准备读
            for (int i = 0; i < 15; i++) {
                //在位置10的地方做mark
                if(i==10){
                    b.mark();
                    System.out.print(" set mark ");
                }
                System.out.print(b.get()+" ");
            }
            //回到mark的位置,读取后续数据
            b.reset();
            System.out.println("
    reset to mark");
            while(b.hasRemaining()){
                System.out.print(b.get()+" ");
            }
        }

     

    输出:
    0 1 2 3 4 5 6 7 8 9  set mark 10 11 12 13 14 
    reset to mark
    10 11 12 13 14 

      复制缓冲区 duplicate() 方法:可以为多方同时处理数据

    /*
         * 复制缓冲区:
         * 1、主缓冲区、副本缓冲区,都是拿同一个地方的数据
         * 2、各自维护自己的position、limit、mark
         */
        public static void demo3(){
            ByteBuffer b=ByteBuffer.allocate(20);
            for (int i = 0; i < 15; i++) {
                b.put((byte)i);
            }
            ByteBuffer copyB = b.duplicate();
            System.out.println("执行b.duplicate()后,");
            System.out.println(b);
            System.out.println(copyB);
            copyB.flip();
            System.out.println("执行copyB.flip()后,");
            System.out.println(b);
            System.out.println(copyB);
            System.out.println("想副本缓冲区插入一个数据:(byte)100");
            copyB.put((byte)100);
            System.out.println("获取主缓冲区跟副本缓冲区第一个数据");
            System.out.println("b.get(0):"+b.get(0));
            System.out.println("copyB.get(0):"+copyB.get(0));
        }

      缓冲区分片:slice()方法,
      * 1、获取主缓冲去中的一个片段,
      * 2、设置position、limit为范围,跟复制缓冲区一个概念,存储的数据本质是一样的。
      * b.position(3);
      * b.limit(10);
      * b.slice();
      
      只读缓冲区asReadOnlyBuffer() 方法
      * 创建只读缓冲区,只能读取数据,写入数据会报错。

      文件映射到内存  MappedByteBuffer 

      /*
         * NIO提供一种将文件映射到内存的方法进行I/O操作,他可以比常规的基于流的I/O快很多,FileChannal.map()方法实现
         */
        public static void demo5() throws Exception{
            RandomAccessFile raf=new RandomAccessFile("d:\student.txt","rw");
            FileChannel fc=raf.getChannel();
            //MappedByteBuffer 是ByteBuffer的子类
            //通过FileChannel将文件映射到内存中。
            MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, raf.length());
            while(mbb.hasRemaining()){
                System.out.print(mbb.get()+" ");
            }
            //通过修改Buffer,通过映射,将实际数据写到对应 的磁盘中。
            mbb.put(0,(byte)98);
            raf.close();
            
        }

      处理结构化数据:

    /*
         * 处理结构化数据
         * 1、散射:将数据读入一组Buffer中
         * 2、聚集:将数据写入一组Buffer中
         * 简单来说就是将将数据处理成Buffer数组
         */
        public static void demo6() throws Exception{
            /*
             * 聚集
            */ 
            ByteBuffer b1=ByteBuffer.wrap("java程序性能优化".getBytes());
            ByteBuffer b2=ByteBuffer.wrap(" 葛一鸣".getBytes());
            int booklen=b1.limit();
            int autlen=b2.limit();
            FileOutputStream fos=new FileOutputStream("d:\student.txt");
            ByteBuffer[] bs={b1,b2};
            FileChannel channel = fos.getChannel();
            channel.write(bs);
            fos.close();
            
            /*
             * 散射
             */
            ByteBuffer b3=ByteBuffer.allocate(booklen);
            ByteBuffer b4=ByteBuffer.allocate(autlen);
            ByteBuffer[] bs2=new ByteBuffer[]{b3,b4};
            FileInputStream fis=new FileInputStream("d:\student.txt");
            FileChannel channel2 = fis.getChannel();
            channel2.read(bs2);
            String bookname=new String(bs2[0].array(),"utf-8");
            String atuname=new String(bs2[1].array(),"utf-8");
            System.out.println(bookname+atuname);
            fis.close();
        }

      控制台输出跟文件内容展示:

  • 相关阅读:
    Protected和Default的区别
    将数组中负数放在正数前面
    java.io包和杯子测楼
    hadoop基础
    极限编程和JUnit
    接口和抽象类
    C# 中窗口AutoScaleMode属性
    计算机的自启动管理
    labview中的移位寄存器、循环隧道,自动索引隧道的区别
    发现C#winform编程中不常用的控件(一)<FlowLayoutPanel控件><拆分器控件Splitcontainer >
  • 原文地址:https://www.cnblogs.com/mathlin/p/10778856.html
Copyright © 2011-2022 走看看