zoukankan      html  css  js  c++  java
  • 使用NIO提升性能

    NIO是New I/O的简称,与旧式的基于流的I/O方法相对,从名字看,它表示新的一套Java I/O标准。

    具有以下特性:

      传统Java IO,它是阻塞的,低效的。那么Java NIO和传统Java IO有什么不同?带来了什么?

    (1)面向块的I/O

      传统JavaIO是面向流的I/O。流I/O一次处理一个字节。NIO则是面向块的I/O,每次操作都是以数据块为单位。它们的差距就好象两个人吃饭,一个人一粒一粒的吃,另一个人狼吞虎咽,快慢显而易见。

      NIO中引入了缓冲区(Buffer)的概念,缓冲区作为传输数据的基本单位块,所有对数据的操作都是基于将数据移进/移出缓冲区而来;读数据的时候从缓冲区中取,写的时候将数据填入缓冲区。尽管传统JavaIO中也有相应的缓冲区过滤器流(BufferedInputStream等),但是移进/移出的操作是由程序员来包装的,它本质是对数据结构化和积累达到处理时的方便,并不是一种提高I/O效率的措施。NIO的缓冲区则不然,对缓冲区的移进/移出操作是由底层操作系统来实现的。

      通常一次缓冲区操作是这样的:某个进程需要进行I/O操作,它执行了一次读(read)或者写(write)的系统调用,向底层操作系统发出了请求,操作系统会按要求把数据缓冲区填满或者排干。说起来简单,其实很复杂。但至少我们知道了这事是由操作系统干的,比我们代码级的实现要高效的多。

      除了效率上的差别外,缓冲区在数据分析和处理上也带来的很大的便利和灵活性。

     (2)非阻塞的I/O + 就绪性选择

      传统JavaIO是基于阻塞I/O模型的:当发起一个I/O请求时,如果数据没有准备好(read时无可读数据,write时数据不可写入),那么线程便会阻塞,直到数据准备好,导致线程大部分的时间都在阻塞。

      而非阻塞I/O则允许线程在有数据的时候处理数据,没有数据的时候干点别的,提高了资源利用率。

      就绪性选择通常是建立在非阻塞的基础上,并且更进一步,它把检查哪些I/O请求的数据准备好这个任务交给了底层操作系统,操作系统会去查看并返回结果集合,这样我们只需要关心那些准备好进行操作的IO通道。关于就绪性选择的过程会在后面详述。

      NIO提供的Socket可以用非阻塞的方式工作,并且支持就绪性选择,减少了资源消耗和CPU在线程间的切换,在管理线程效率上比传统Socket高。

    (3)文件锁定和内存映射文件等操作系统特性

      NIO同时带来了很多当今操作系统大都支持的特性。

      文件锁定是多个进程协同工作的情况下,要协调进程间对共享数据的访问必不可少的工具。

      内存映射利用虚拟内存技术提供对文件的高速缓存,使读取磁盘文件就像从内存中读取一样高效,但是却不会有内存泄漏的危险,因为在内存中不会存在文件的完整拷贝。

      此外还有一些其他的特性,后面再详述。

     (4)为所有的原始类型提供(Buffer)缓存支持

     (5)使用Java.nio.charset.Charset作为字符集编码解码解决方案

     (6)增加通道(Channel)对象,作为新的原始I/O抽象

     (7)提供了基于Selector的异步网络I/O。

    为什么要使用NIO?

      对于文件I/O, 在我看来使用IO和NIO是区别不大的,Java1.4开始原始IO也根据NIO重新实现过了,提供了对于NIO特性的支持。即使是流,也会比以前更加高效。企业级应用软件中涉及I/O的部分多半是读写文件的功能性需求,很少有在并发上的要求,那么JavaIO包已经很胜任了。

      对于网络I/O,传统的阻塞式I/O,一个线程对应一个连接,采用线程池的模式在大部分场景下简单高效。当连接数茫茫多时,并且数据的移动非常频繁,NIO无疑是更好的选择。

      NIO标榜的是高速、可伸缩的I/O,因为它更亲近操作系统。当需求很平凡,没有太高的效率要求的时候,你看不出它的好,反而觉得NIO代码实现复杂,不易理解。选择与否全看使用的场景,这点就看使用者的权衡了。

     NIO的Buffer类族和Channel

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

     文件复制示例如下:

        public static void nioCopyFile(String resource, String destination){
            try {
                FileInputStream fis = new FileInputStream(resource);
                FileOutputStream fos = new FileOutputStream(destination);
                FileChannel readChannel = fis.getChannel();
                FileChannel writeChannel = fos.getChannel();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                int len;
                while((len=readChannel.read(buffer))!=-1){
                    buffer.flip();
                    writeChannel.write(buffer);
                    buffer.clear();
                }
                readChannel.close();
                writeChannel.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

     文件映射到内存

      NIO提供了一种将文件映射到内存的方法进行I/O操作,它可以比常规的基于流的I/O快很多。这个操作主要由FileChannel.map()方法实现,比如:

    MappredByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);

      以上代码将文件的前1024个字节映射到内存中。map()方法返回一个MappredByteBuffer,它是ByteBuffer的子类。因此,可以像使用ByteBuffer那样使用它。

        public static void nioCopyFile(String resource, String destination){
            try {
    
                RandomAccessFile fis = new RandomAccessFile(resource, "rw");
                RandomAccessFile fos = new RandomAccessFile(destination, "rw");
                FileChannel readChannel = fis.getChannel();            
                FileChannel writeChannel = fos.getChannel();
                MappedByteBuffer mbb = readChannel.map(FileChannel.MapMode.READ_WRITE, 0, readChannel.size());
                writeChannel.write(mbb);
                readChannel.close();
                writeChannel.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

    或者如下写法:

        public static void nioCopyFile(String resource, String destination){
            try {
                File res = new File(resource);
                File dest = new File(destination);
                if(!dest.exists())
                    dest.createNewFile();
                FileInputStream fis = new FileInputStream(res);
                FileOutputStream fos = new FileOutputStream(dest);
                FileChannel readChannel = fis.getChannel();            
                FileChannel writeChannel = fos.getChannel();
                MappedByteBuffer mbb = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size());
                writeChannel.write(mbb);
                readChannel.close();
                writeChannel.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }      

    或者如下写法:

        public static void nioCopyFile(String resource, String destination){
            try {
                FileInputStream fis = new FileInputStream(resource);
                FileOutputStream fos = new FileOutputStream(destination);
                FileChannel readChannel = fis.getChannel();            
                FileChannel writeChannel = fos.getChannel();
                MappedByteBuffer mbb = readChannel.map(FileChannel.MapMode.READ_ONLY, 0, readChannel.size());
                writeChannel.write(mbb);
                readChannel.close();
                writeChannel.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }      

    处理结构化数据

      NIO还提供了处理结构化数据的方法,称之为散射(Scattering)和聚集(Gathering)。散射指将数据读入一组Buffer中,而不仅仅是一个。聚集与之相反,指将数据写入一组Buffer中。

      假设有文本文件,格式为“书名作者”,现通过聚集写操作创建该文件和散射读文件:

        public static void readAndWrite(){
            try {
                //聚集写操作
                ByteBuffer bookBuf = ByteBuffer.wrap("java性能优化技巧".getBytes("utf-8"));
                ByteBuffer autBuf = ByteBuffer.wrap("葛一鸣".getBytes("utf-8"));
                int booklen = bookBuf.limit();  //记录书名长度
                int authlen = autBuf.limit();    //记录作者长度
                ByteBuffer[] bufs = new ByteBuffer[]{bookBuf, autBuf};
                File file = new File("D:\book.txt");
                if(!file.exists())
                    file.createNewFile();  //文件不存在则创建文件
                FileOutputStream fos = new FileOutputStream(file);
                FileChannel fc = fos.getChannel();
                fc.write(bufs);
                fos.close();
                fc.close();
                //散射读操作
                ByteBuffer b1 = ByteBuffer.allocate(booklen);
                ByteBuffer b2 = ByteBuffer.allocate(authlen);
                ByteBuffer[] buffs = new ByteBuffer[]{b1,b2};
                FileInputStream fin = new FileInputStream(file);
                FileChannel fic = fin.getChannel();
                fic.read(buffs);
                fin.close();
                fic.close();
                String bookName = new String(b1.array(), "utf-8");
                String authName = new String(b2.array(), "utf-8");
                System.out.println(bookName+" "+authName);;
            } catch (UnsupportedEncodingException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

     直接内存访问

      NIO的Buffer还提供了一个可以直接访问系统物理内存的类——DirectBuffer。DirectBuffer继承自ByteBuffer,但和普通的ByteBuffer不同。普通的ByteBuffer仍然在JVM堆上分配空间,其最大内存,受到最大堆的限制。而DirectBuffer直接分配在物理内存中,并不占用堆空间。使用DirectBuffer是一个更接近系统底层的方法,所以,它的速度比普通的ByteBuffer更快。

    申请DirectBuffer的方法如下:

    ByteBuffer b = ByteBuffer.allocateDirect(500);

    虽然访问速度上有优势,但是创建和销毁DirectBuffer的花费却远比ByteBuffer高。因此在需要频繁创建Buffer的场合,不宜使用DirectBuffer,但是如果能将DirectBuffer进行复用,那么,在读写频繁的情况下,它完全可以大幅改善系统性能。

      将DirectBuffer应用于真实系统中,不可避免地还需要对DirectBuffer进行监控。下面是一段可用于DirectBuffer监控的代码,增强DirectBuffer的可用性:

        //这段代码用于监控DirectBuffer的使用情况
        public void monDirectBuffer() throws ClassNotFoundException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException{
            Class c = Class.forName("java.nio.Bits");  //通过反射取得私有数据
            Field maxMemory = c.getDeclaredField("maxMemory");
            maxMemory.setAccessible(true);
            Field reservedMemory = c.getDeclaredField("reservedMemory");
            reservedMemory.setAccessible(true);
            synchronized(c){
                Long maxMemoryValue = (Long) maxMemory.get(null); //总大小
                Long reservedMemoryValue = (Long) reservedMemory.get(null);  //剩余大小
                System.out.println("maxMemoryValue:"+maxMemoryValue);
                System.out.println("reservedMemoryValue:"+reservedMemoryValue);
            }
        }
  • 相关阅读:
    伪类样式
    div 文字超出边框后,省略号显示
    关于常用的 meta
    js数组去重
    异步二进制文件下载
    JJWT现前后端分离身份验证
    ApachePOI快速入门
    axios兼容ie7
    vue解决跨域问题
    log4j模板
  • 原文地址:https://www.cnblogs.com/gaopeng527/p/4902618.html
Copyright © 2011-2022 走看看