`
lxy2330
  • 浏览: 468527 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

NIO学习系列:核心概念及基本读写

 
阅读更多

NIO学习系列:缓冲区内部实现机制
http://zhangshixi.iteye.com/blog/681704

NIO学习系列:连网和异步IO
http://zhangshixi.iteye.com/blog/683767

NIO学习系列:缓冲区更多特性及分散/聚集IO
http://zhangshixi.iteye.com/blog/684544

NIO学习系列:文件锁定和字符集
http://zhangshixi.iteye.com/blog/685022

 

1.    引言
   I/O流或者输入/输出流指的是计算机与外部世界或者一个程序与计算机的其余部分的之间的接口。新的输入/输出(NIO)库是在JDK 1.4中引入的。NIO弥补了原来的I/O的不足,它在标准Java代码中提供了高速的、面向块的I/O。
   原来的I/O库与NIO最重要的区别是数据打包和传输的方式的不同,原来的 I/O 以 的方式处理数据,而 NIO 以 的方式处理数据。
   面向流的I/O系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的I/O通常相当慢。
   NIO与原来的I/O有同样的作用和目的,但是它使用块I/O的处理方式。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的I/O缺少一些面向流的I/O所具有的优雅性和简单性。

 

2.    从一个例子开始
   下面我们从一个简单的使用IO和NIO读取一个文件中的内容为例,来进入NIO的学习之旅。
   使用IO来读取指定文件中的前1024字节并打印出来:

Java代码
  1. /**  
  2.  * 使用IO读取指定文件的前1024个字节的内容。  
  3.  * @param file 指定文件名称。  
  4.  * @throws java.io.IOException IO异常。  
  5.  */   
  6. public   void  ioRead(String file)  throws  IOException {  
  7.     FileInputStream in = new  FileInputStream(file);  
  8.     byte [] b =  new   byte [ 1024 ];  
  9.     in.read(b);  
  10.     System.out.println(new  String(b));  
  11. }  
  12.   
  13. /**  
  14.  * 使用NIO读取指定文件的前1024个字节的内容。  
  15.  * @param file 指定文件名称。  
  16.  * @throws java.io.IOException IO异常。  
  17.  */   
  18. public   void  nioRead(String file)  throws  IOException {  
  19.     FileInputStream in = new  FileInputStream(file);  
  20.     FileChannel channel = in.getChannel();  
  21.   
  22.     ByteBuffer buffer = ByteBuffer.allocate(1024 );  
  23.     channel.read(buffer);  
  24.     byte [] b = buffer.array();  
  25.     System.out.println(new  String(b));  
  26. }  
/**
 * 使用IO读取指定文件的前1024个字节的内容。
 * @param file 指定文件名称。
 * @throws java.io.IOException IO异常。
 */
public void ioRead(String file) throws IOException {
    FileInputStream in = new FileInputStream(file);
    byte[] b = new byte[1024];
    in.read(b);
    System.out.println(new String(b));
}

/**
 * 使用NIO读取指定文件的前1024个字节的内容。
 * @param file 指定文件名称。
 * @throws java.io.IOException IO异常。
 */
public void nioRead(String file) throws IOException {
    FileInputStream in = new FileInputStream(file);
    FileChannel channel = in.getChannel();

    ByteBuffer buffer = ByteBuffer.allocate(1024);
    channel.read(buffer);
    byte[] b = buffer.array();
    System.out.println(new String(b));
}

   从上面的例子中可以看出,NIO以通道Channel和缓冲区Buffer为基础来实现面向块的IO数据处理。下面将讨论并学习NIO 库的核心概念以及从高级的特性到底层编程细节的几乎所有方面。


3.    核心概念:通道和缓冲区
   1)    概述:
   通道和缓冲区是NIO中的核心对象,几乎在每一个I/O操作中都要使用它们。
   通道Channel是对原I/O包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个Channel对象。
   缓冲区Buffer实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中;同样地,从通道中读取的任何数据都要读到缓冲区中。

   2)    缓冲区:
   Buffer是一个容器对象,它包含一些要写入或者刚读出的数据。在NIO中加入Buffer对象,体现了新库与原I/O的一个重要区别。在面向流的I/O中,您将数据直接写入或者将数据直接读到Stream对象中。
   在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问NIO中的数据,您都是将它放到缓冲区中。
   缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
   最常用的缓冲区类型是ByteBuffer。 一个ByteBuffer可以在其底层字节数组上进行get/set操作(即字节的获取和设置)。
   ByteBuffer不是NIO中唯一的缓冲区类型。事实上,对于每一种基本Java类型都有一种缓冲区类型:
   ByteBuffer
   CharBuffer
   ShortBuffer
   IntBuffer
   LongBuffer
   FloatBuffer
   DoubleBuffer
   每一个Buffer类都是Buffer接口的一个实例。 除了ByteBuffer, 每一个Buffer类都有完全一样的操作,只是它们所处理的数据类型不一样。因为大多数标准I/O操作都使用ByteBuffer,所以它具有所有共享的缓冲区操作以及一些特有的操作。
   下面的UseFloatBuffer列举了使用类型化的缓冲区FloatBuffer的一个应用例子:

Java代码
  1. /**  
  2.  * 使用 float 缓冲区。  
  3.  * @version 1.00 2010-5-19, 10:30:59  
  4.  * @since 1.5  
  5.  * @author ZhangShixi  
  6.  */   
  7. public   class  UseFloatBuffer {  
  8.   
  9.     public   static   void  main(String[] args) {  
  10.         // 分配一个容量为10的新的 float 缓冲区   
  11.         FloatBuffer buffer = FloatBuffer.allocate(10 );  
  12.         for  ( int  i =  0 ; i < buffer.capacity(); i++) {  
  13.             float  f = ( float ) Math.sin(((( float ) i) /  10 ) * ( 2  * Math.PI));  
  14.             buffer.put(f);  
  15.         }  
  16.         // 反转此缓冲区   
  17.         buffer.flip();  
  18.   
  19.         // 告知在当前位置和限制之间是否有元素   
  20.         while  (buffer.hasRemaining()) {  
  21.             float  f = buffer.get();  
  22.             System.out.println(f);  
  23.         }  
  24.     }  
  25. }  
/**
 * 使用 float 缓冲区。
 * @version 1.00 2010-5-19, 10:30:59
 * @since 1.5
 * @author ZhangShixi
 */
public class UseFloatBuffer {

    public static void main(String[] args) {
        // 分配一个容量为10的新的 float 缓冲区
        FloatBuffer buffer = FloatBuffer.allocate(10);
        for (int i = 0; i < buffer.capacity(); i++) {
            float f = (float) Math.sin((((float) i) / 10) * (2 * Math.PI));
            buffer.put(f);
        }
        // 反转此缓冲区
        buffer.flip();

        // 告知在当前位置和限制之间是否有元素
        while (buffer.hasRemaining()) {
            float f = buffer.get();
            System.out.println(f);
        }
    }
}

 

   3)    通道:
   Channel是对原I/O包中的流的模拟,可以通过它读取和写入数据。拿NIO与原来的I/O做个比较,通道就像是流。
   正如前面提到的,所有数据都通过Buffer对象来处理。您永远不会将字节直接写入通道中,相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节,而是将数据从通道 读入缓冲区,再从缓冲区获取这个字节。
通道与流的不同之处在于通道是双向的。而流只是在一个方向上移动(一个流必须是InputStream或者OutputStream的子类), 而通道可以用于读、写或者同时用于读写。
   因为它们是双向的,所以通道可以比流更好地反映底层操作系统的真实情况。特别是在UNIX模型中,底层操作系统通道是双向的。


4.    从理论到实践:NIO中的读和写
   1)    概述:
   读和写是I/O的基本过程。从一个通道中读取很简单:只需创建一个缓冲区,然后让通道将数据读到这个缓冲区中。写入也相当简单:创建一个缓冲区,用数据填充它,然后让通 道用这些数据来执行写入操作。

   2)    从文件中读取:
如果使用原来的I/O,那么我们只需创建一个FileInputStream并从它那里读取。而在NIO中,情况稍有不同:我们首先从FileInputStream获取一个FileChannel对象,然后使用这个通道来读取数据。
   在NIO系统中,任何时候执行一个读操作,您都是从通道中读取,但是您不是直接从通道读取。因为所有数据最终都驻留在缓冲区中,所以您是从通道读到缓冲区中。
   因此读取文件涉及三个步骤:
   (1) 从FileInputStream获取Channel。
   (2) 创建Buffer。
   (3) 将数据从Channel读到Buffer 中。
   现在,让我们看一下这个过程。

Java代码
  1. // 第一步是获取通道。我们从 FileInputStream 获取通道:    
  2. FileInputStream fin = new  FileInputStream(  "readandshow.txt"  );  
  3. FileChannel fc = fin.getChannel();  
  4. // 下一步是创建缓冲区:    
  5. ByteBuffer buffer = ByteBuffer.allocate( 1024  );  
  6. // 最后,需要将数据从通道读到缓冲区中:    
  7. fc.read( buffer );  
// 第一步是获取通道。我们从 FileInputStream 获取通道: 
FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();
// 下一步是创建缓冲区: 
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
// 最后,需要将数据从通道读到缓冲区中: 
fc.read( buffer );

   您会注意到,我们不需要告诉通道要读多少数据到缓冲区中。每一个缓冲区都有复杂的内部统计机制,它会跟踪已经读了多少数据以及还有多少空间可以容纳更多的数据。我们将在缓冲区内部细节中介绍更多关于缓冲区统计机制的内容。

   3)    写入文件:
   在 NIO 中写入文件类似于从文件中读取。

Java代码
  1. // 首先从 FileOutputStream 获取一个通道:    
  2. FileOutputStream fout = new  FileOutputStream(  "writesomebytes.txt"  );  
  3. FileChannel fc = fout.getChannel();  
  4. // 下一步是创建一个缓冲区并在其中放入一些数据,这里,用message来表示一个持有数据的数组。    
  5. ByteBuffer buffer = ByteBuffer.allocate( 1024  );  
  6. for  ( int  i= 0 ; i<message.length; ++i) {  
  7.      buffer.put( message[i] );  
  8. }  
  9. buffer.flip();  
  10. // 最后一步是写入缓冲区中:    
  11. fc.write( buffer );  
// 首先从 FileOutputStream 获取一个通道: 
FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();
// 下一步是创建一个缓冲区并在其中放入一些数据,这里,用message来表示一个持有数据的数组。 
ByteBuffer buffer = ByteBuffer.allocate( 1024 );
for (int i=0; i<message.length; ++i) {
     buffer.put( message[i] );
}
buffer.flip();
// 最后一步是写入缓冲区中: 
fc.write( buffer );

    注意在这里同样不需要告诉通道要写入多数据。缓冲区的内部统计机制会跟踪它包含多少数据以及还有多少数据要写入。

   4)    读写结合:
   下面的示例将展示使用读写结合,将一个文件的所有内容拷贝到另一个文件中。

Java代码
  1. /**  
  2.  * 将一个文件的所有内容拷贝到另一个文件中。  
  3.  *   
  4.  * CopyFile.java 执行三个基本操作:  
  5.  * 首先创建一个 Buffer,然后从源文件中将数据读到这个缓冲区中,然后将缓冲区写入目标文件。  
  6.  * 程序不断重复 — 读、写、读、写 — 直到源文件结束。  
  7.  *   
  8.  * @version 1.00 2010-5-19, 10:49:46  
  9.  * @since 1.5  
  10.  * @author ZhangShixi  
  11.  */   
  12. public   class  CopyFile {  
  13.   
  14.     public   static   void  main(String[] args)  throws  Exception {  
  15.         String infile = "C://copy.sql" ;  
  16.         String outfile = "C://copy.txt" ;  
  17.   
  18.         // 获取源文件和目标文件的输入输出流   
  19.         FileInputStream fin = new  FileInputStream(infile);  
  20.         FileOutputStream fout = new  FileOutputStream(outfile);  
  21.   
  22.         // 获取输入输出通道   
  23.         FileChannel fcin = fin.getChannel();  
  24.         FileChannel fcout = fout.getChannel();  
  25.   
  26.         // 创建缓冲区   
  27.         ByteBuffer buffer = ByteBuffer.allocate(1024 );  
  28.   
  29.         while  ( true ) {  
  30.             // clear方法重设缓冲区,使它可以接受读入的数据   
  31.             buffer.clear();  
  32.   
  33.             // 从输入通道中将数据读到缓冲区   
  34.             int  r = fcin.read(buffer);  
  35.   
  36.             // read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1   
  37.             if  (r == - 1 ) {  
  38.                 break ;  
  39.             }  
  40.   
  41.             // flip方法让缓冲区可以将新读入的数据写入另一个通道   
  42.             buffer.flip();  
  43.   
  44.             // 从输出通道中将数据写入缓冲区   
  45.             fcout.write(buffer);  
  46.         }  
  47.     }  
  48. }  
/**
 * 将一个文件的所有内容拷贝到另一个文件中。
 * 
 * CopyFile.java 执行三个基本操作:
 * 首先创建一个 Buffer,然后从源文件中将数据读到这个缓冲区中,然后将缓冲区写入目标文件。
 * 程序不断重复 — 读、写、读、写 — 直到源文件结束。
 * 
 * @version 1.00 2010-5-19, 10:49:46
 * @since 1.5
 * @author ZhangShixi
 */
public class CopyFile {

    public static void main(String[] args) throws Exception {
        String infile = "C://copy.sql";
        String outfile = "C://copy.txt";

        // 获取源文件和目标文件的输入输出流
        FileInputStream fin = new FileInputStream(infile);
        FileOutputStream fout = new FileOutputStream(outfile);

        // 获取输入输出通道
        FileChannel fcin = fin.getChannel();
        FileChannel fcout = fout.getChannel();

        // 创建缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            // clear方法重设缓冲区,使它可以接受读入的数据
            buffer.clear();

            // 从输入通道中将数据读到缓冲区
            int r = fcin.read(buffer);

            // read方法返回读取的字节数,可能为零,如果该通道已到达流的末尾,则返回-1
            if (r == -1) {
                break;
            }

            // flip方法让缓冲区可以将新读入的数据写入另一个通道
            buffer.flip();

            // 从输出通道中将数据写入缓冲区
            fcout.write(buffer);
        }
    }
}

后续: 在下一篇文章中,会具体介绍缓冲区Buffer的内部实现机制,以理解缓冲区如何能够内部地管理自己的资源。有兴趣的可以共同学习、讨论。

分享到:
评论

相关推荐

    NIO学习系列:连网和异步IO

    首先,我们了解NIO的核心概念。NIO不同于传统的IO模型(即BIO,Blocking IO),它引入了通道(Channel)和缓冲区(Buffer)的概念,允许进行非阻塞的读写操作。在BIO中,读写操作通常是阻塞的,当数据未准备好时,...

    NIO学习系列:缓冲区更多特性及分散/聚集IO

    在Java的NIO(New Input/Output)框架中,缓冲区(Buffer)是核心概念之一。本篇文章将深入探讨NIO中的缓冲区特性以及分散/聚集IO操作,这对于理解和优化Java程序的I/O性能至关重要。 缓冲区是NIO中处理数据的主要...

    NIO学习系列:文件锁定和字符集

    在Java的NIO(New Input/Output)库中,文件锁定和字符集是两个重要的概念。文件锁定用于在多线程或分布式系统中确保对文件的独占访问,而字符集则是处理文本数据时用于编码和解码字符的关键组件。在本篇中,我们将...

    nio demo for nio学习笔记(体系结构以及模块介绍)

    在学习NIO时,首先需要理解Channel、Buffer、Selector的基本概念和使用方法,然后通过实例来熟悉它们的交互过程。例如,可以通过创建一个简单的服务器,使用ServerSocketChannel监听连接,SocketChannel处理客户端...

    java NIO学习系列 笔记

    NIO的核心概念包括通道(Channel)和缓冲区(Buffer),这两个组件使得数据以块的形式进行传输,从而提高了性能。 1. **通道(Channel)**: - 通道是数据传输的双向路径,可以将数据从源读取到目标,也可以将数据...

    Java NIO文件操作深度解析:高效读写与代码实现

    本文将详细介绍Java NIO的基本概念、核心组件以及如何使用NIO进行文件操作。 Java NIO提供了一种高效、灵活的文件操作方式,通过Buffer、Channel、Paths和Files等核心组件,可以实现复杂的文件读写操作。NIO的异步...

    Java NIO系列教程(一) Java NIO 概述

    ### Java NIO 系列教程(一):Java NIO 概述 ...通过本文的学习,相信您已经对Java NIO的核心概念有了初步的了解。在未来的学习过程中,继续深入理解这些概念将有助于您更好地掌握Java NIO技术。

    JAVA NIO学习网站

    在Java传统IO中,数据的读写都是通过流来完成,而NIO则引入了通道(Channel)和缓冲区(Buffer)的概念,提供了一种非阻塞的I/O操作方式,极大地提高了Java进行并发I/O处理的能力。 首先,我们来看下NIO的核心组件...

    nio_proxy:基本的 nio 代理

    总结,"nio_proxy"项目涉及了Java NIO的核心概念,包括Channel、Buffer和Selector,以及如何利用这些组件构建一个基本的代理服务。理解并掌握这些知识点对于进行高性能的Java网络编程至关重要。

    nio学习文档及代码

    学习NIO,首先应了解其基本概念,然后通过实践操作通道、缓冲区和选择器,最后可以尝试将NIO应用于实际项目,例如构建一个简单的服务器,以巩固和提升技能。 总结来说,本资料包为学习NIO提供了一个良好的起点,...

    NIO与传统IO的区别共9页.pdf.zip

    在Java编程领域,IO(Input/Output)是处理数据输入和输出的核心机制,而NIO(Non-blocking Input/Output)是Java提供的一种更高效的IO模型。这篇9页的PDF文档,"NIO与传统IO的区别共9页.pdf",很可能详细对比了这两...

    NIO学习总结

    同时,NIO也是Java NIO.2(新I/O API,JDK 7引入)的基础,包括Path、Files、AsynchronousChannelGroup等高级API,都依赖于NIO的基本概念和技术。 总之,NIO的出现为Java开发者提供了更多的可能性,让我们能够以更...

    apache nio 很好的学习资料

    本学习资料将深入探讨Apache NIO的核心概念、组件以及其在实际开发中的应用。 首先,我们要理解NIO的基本理念。传统的BIO模型在进行I/O操作时,如果一个线程正在读取或写入数据,那么这个线程会一直被阻塞,直到I/O...

    NIO学习资料大全

    NIO的核心概念包括通道(Channels)、缓冲区(Buffers)、选择器(Selectors)以及多路复用器(Selector)。以下是对这些核心概念的详细解释: 1. **通道(Channels)**:在NIO中,数据读取和写入都是通过通道进行...

    NIO学习总结经典

    《NIO学习总结经典》这篇文章主要探讨了Java的New IO(NIO)...总的来说,《NIO学习总结经典》这篇博客提供了关于Java NIO的全面概述,涵盖了核心概念、用法和实践技巧,是Java开发者提升I/O处理能力的重要参考资料。

    Servlet API 和 NIO: 最终组合在一起

    解决这些问题通常需要深入理解NIO的工作原理,包括缓冲区(Buffer)、通道(Channel)、选择器(Selector)等核心概念,并且需要对Servlet容器的内部机制有一定的了解。 在提供的文件列表中,"src"目录可能包含了源...

Global site tag (gtag.js) - Google Analytics