zoukankan      html  css  js  c++  java
  • Java IO

    江苏 无锡 缪小东< xmlnamespace prefix ="o" ns ="urn:schemas-microsoft-com:office:office" />

           本篇主要讲述IO相关的内容,主要包括:与IO相关的简单的历史背景知识;Java IO的简单分类;与IO设计相关的两个模式;同时列举几个简单的例子;分析其中几个实现的源代码;最后给大家一些简单扩展的例子。治学先治史,下面我们先从简单的历史开始吧!

     

    一、      历史背景

           “对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务。

    ――Think in Java

           无论是系统、还是语言的设计中IO的设计都是异常复杂的。面临的最大的挑战一般是如何覆盖所有可能的因素,我们不仅仅要考虑文件、控制台、网络、内存等不同的种类,而且要处理大量的不同的读取方式,如:顺序读取、随机读取,二进制读取、字符读取,按行读取、按字符读取……

           Linux是第一个将设备抽象为文件的操作系统,在Linux中所有的外部设备都可以用读取文件的方法读取,这样编程人员就可以以操作文件的方法操作任何设备。C++IO方面也做了一些改进――引进了流的概念,我们可以通过cincout读写一些对象。Java语言在IO设计方面取得较大的成功,它是完全面向对象的,主要采用装饰器模式避免大量的类,包括了最大的可能性,提供了较好的扩展机制……

           Java库的设计者通过创建大量类来攻克这个难题。事实上,JavaIO系统采用了如此多的类,以致刚开始会产生不知从何处入手的感觉(具有讽刺意味的是,JavaIO设计初衷实际要求避免过多的类)。” 上面一段来自《Think in Java》,确实很多初学者刚刚学习javaIO时会比较茫然,不过等我们知道装饰器模式(Decorator)的用意、场景及其在JavaIO包中的使用,你可能会真正领会整个IOFrameWork

    二、IO的分类

        Java IO一般包含两个部分:1.java.io包中堵塞型IO;2.java.nio包中的非堵塞型IO,通常称为New IO。学过操作系统的朋友都知道系统运行的瓶颈一般在于IO操作,一般打开某个IO通道需要大量的时间,同时端口中不一定就有足够的数据,这样read方法就一直等待读取此端口的内容,从而浪费大量的系统资源。有人也许会提出使用java的多线程技术啊!但是在当前进程中创建线程也是要花费一定的时间和系统资源的,因此不一定可取。Java New IO的非堵塞技术主要采用了Observer模式,就是有一个具体的观察者和=监测IO端口,如果有数据进入就会立即通知相应的应用程序。这样我们就避免建立多个线程,同时也避免了read等待的时间。不过本篇主要讲述java的堵塞型IO,就是我们通常应用的那个包。

        打开你的java.io包你可以看到Java的IO包含大量的类和接口(JDK1.6中包含83个类或者接口),如此众多的类和接口似乎无从下手。下面就将IO简单地分类。Java的IO主要包含三个部分:1.流式部分――IO的主体部分;2.非流式部分――主要包含一些辅助流式部分的类,如:File类、RandomAccessFile类和FileDescriptor等类;3.文件读取部分的与安全相关的类,如:SerializablePermission类。以及与本地操作系统相关的文件系统的类,如:FileSystem类和Win32FileSystem类和WinNTFileSystem类。

           流式部分可以概括为:两个对应一个桥梁。两个对应指:1.字节流(Byte Stream)和字符流(Char Stream)的对应;2.输入和输出的对应。一个桥梁指:从字节流到字符流的桥梁。对应于输入和输出为InputStreamReaderOutputStreamWriter

           在流的具体类中又可以具体分为:1.介质流(Media Stream或者称为原始流Raw Stream)――主要指一些基本的流,他们主要是从具体的介质上,如:文件、内存缓冲区(Byte数组、Char数组、StringBuffer对象)等,读取数据;2.过滤流(Filter Stream)――主要指所有FilterInputStream/FilterOutputStreamFilterReader/FilterWriter的子类,主要是对其包装的类进行某些特定的处理,如:缓存等。

     

    三、IO中的流

           流具有最基本的特点:“One dimension , one direction . 即流是一维的,同时流是单向的。关于维和我们通常说的一维长度,二维平面,三维空间,四维时空……是同一个概念,流就是一维的。单向就是只可以一个方向(按顺序从头至尾依次)读取,不可以读到某个位置,再返回前面某个位置。流的概念和实际水流的概念基本一致,水只可以从高向低一个方向流动。我们某时在目地喝了一口水,下次在同一个地点喝水已经不是当时的那片水了。

           流的这种特性在JMSJava Message Service)的API设计中得到了体现。JMSJ2EE平台下面向消息中间件的一个标准。(关于中间件技术有机会和大家探讨)JMS中有五种具体类型的消息,这些消息一般分为两类:1.流式的消息――包含ByteMessageStreamMessage2.非流式的消息――包含TextMessageObjectMessageMapMessage。我们在明白IO中流的特点后,基本可以明白JMS API设计者的意图。

           可能有些场合我们需要在文件中随机插入数据、在流中来来回回地执行某些操作,这时候我们绝对不可以使用流相关的对象。很幸运JDK的设计者为我们设计了一个单独的类RandomAccessFile,它可以完成打开、关闭文件、以基本数据类型的方式读取数据、读取下一个行、以UTF等格式读取数据、写入各种类型的数据、比较特殊的是他可以通过文件指针的seek方法让文件指针移到某个位置,可以通过getFilePointer()方法得到当前指针的位置、可以通过length()方法得到当前文件的容量、通过getFD()得到FileDescriptor对象,通过getChannel()方法得到FileChannel对象,从而和New IO整合。

           下面比较简单的分析IO中的各个对象吧!

    3.1 IO中的输入字节流

           下面是IO中输入字节流的继承图。

    o        InputStream

    o        ByteArrayInputStream

    o        FileInputStream

    o        FilterInputStream

    o        BufferedInputStream

    o        DataInputStream

    o        LineNumberInputStream

    o        PushbackInputStream

    o        ObjectInputStream

    o        PipedInputStream

    o        SequenceInputStream

    o        StringBufferInputStream

    在上面的关系图中可以看出:1.InputStream是所有的输入字节流的父类,它是一个抽象类。2. ByteArrayInputStreamStringBufferInputStreamFileInputStream是三种基本的介质流,它们分别将Byte数组、StringBuffer、和本地文件中读取数据。PipedInputStream是从与其它线程共用的管道中读取数据,与Piped相关的知识会用专门的一小节讲解。3. ObjectInputStream和所有FilterInputStream的子类都是装饰流(装饰器模式的主角)。下表列出了这些流的功能及如何使用它们(具体使用在讲解完装饰器模式后会举几个例子)。

    基本输入字节流:

    功能

    如何构造

    怎样使用

    ByteArrayInputStream

    将内存中的Byte数组适配为一个InputStream

    从内存中的Byte数组创建该对象(2种方法)

    一般作为数据源,会使用其它装饰流提供额外的功能,一般都建议加个缓冲功能。

    StringBufferInputStream

    将内存中的字符串适配为一个InputStream

    从一个String对象创建该对象。底层的实现使用StringBuffer。该类被Deprecated。主要原因是StringBuffer不应该属于字节流,所以推荐使用StringReader

    一般作为数据源,同样会使用其它装饰器提供额外的功能。

    FileInputStream

    最基本的文件输入流。主要用于从文件中读取信息。

    通过一个代表文件路径的StringFile对象或者FileDescriptor对象创建。

    一般作为数据源,同样会使用其它装饰器提供额外的功能。

    PipedInputStream

    读取从对应PipedOutputStream写入的数据。在流中实现了管道的概念。

    利用对应的PipedOutputStream创建。

    在多线程程序中作为数据源,同样会使用其它装饰器提供额外的功能。

    SequenceInputStream

    2个或者多个InputStream 对象转变为一个InputStream.

    使用两个InputStream 或者内部对象为InputStream Enumeration对象创建该对象。

    一般作为数据源,同样会使用其它装饰器提供额外的功能。

    FilterInputStream

    给其它被装饰对象提供额外功能的抽象类

    主要子类见下表

     

    装饰、输入字节流:

    功能

    如何构造

    怎样使用

    DataInputStream

    一般和DataOutputStream配对使用,完成基本数据类型的读写。

    利用一个InputStream构造。

    提供了大量的读取基本数据类新的读取方法。

    BufferedInputStream

    使用该对象阻止每次读取一个字节都会频繁操作IO。将字节读取一个缓存区,从缓存区读取。

    利用一个InputStream、或者带上一个自定义的缓存区的大小构造。

    使用InputStream的方法读取,只是背后多一个缓存的功能。设计模式中透明装饰器的应用。

    LineNumberInputStream

    跟踪输入流中的行号。可以调用getLineNumber( )setLineNumber(int)方法得到和设置行号。

    利用一个InputStream构造。

    紧紧增加一个行号。可以象使用其它InputStream一样使用。

    PushbackInputStream

    可以在读取最后一个byte 后将其放回到缓存中。

    利用一个InputStream构造。

    一般仅仅会在设计compilerscanner 时会用到这个类。在我们的java语言的编译器中使用它。很多程序员可能一辈子都不需要。

    3.2 IO中的输出字节流

           下面是IO中输出字节流的继承图。

      • OutputStream
        • ByteArrayOutputStream
        • FileOutputStream
        • FilterOutputStream
          • BufferedOutputStream
          • DataOutputStream
          • PrintStream
        • ObjectOutputStream
        • PipedOutputStream

    在上面的关系图中可以看出:1.OutputStream是所有的输出字节流的父类,它是一个抽象类。2. ByteArrayOutputStreamFileOutputStream是两种基本的介质流,它们分别向Byte数组、和本地文件中写入数据。PipedOutputStream是向与其它线程共用的管道中写入数据, 3. ObjectOutputStream和所有FilterOutputStream的子类都是装饰流。下表列出了输出字节流的功能及如何使用它们。

    功能

    如何构造

    怎样使用

    ByteArrayOutputStream

    在内存中创建一个buffer。所有写入此流中的数据都被放入到此buffer中。

    无参或者使用一个可选的初始化buffer的大小的参数构造。

    一般将其和FilterOutputStream套接得到额外的功能。建议首先和BufferedOutputStream套接实现缓冲功能。通过toByteArray方法可以得到流中的数据。(不通明装饰器的用法)

    FileOutputStream

    将信息写入文件中。

    使用代表文件路径的StringFile对象或者 FileDescriptor对象创建。还可以加一个代表写入的方式是否为append的标记。

    一般将其和FilterOutputStream套接得到额外的功能。

    PipedOutputStream

    任何写入此对象的信息都被放入对应PipedInputStream 对象的缓存中,从而完成线程的通信,实现了“管道”的概念。具体在后面详细讲解。

    利用PipedInputStream构造

    在多线程程序中数据的目的地的。一般将其和FilterOutputStream套接得到额外的功能。

    FilterOutputStream

    实现装饰器功能的抽象类。为其它OutputStream对象增加额外的功能。

    见下表

    见下表

           装饰输出字节流:

    功能

    如何构造

    怎样使用

    DataOutputStream

    通常和DataInputStream配合使用,使用它可以写入基本数据类新。

    使用OutputStream构造

    包含大量的写入基本数据类型的方法。

    PrintStream

    产生具有格式的输出信息。(一般地在java程序中DataOutputStream用于数据的存储,即J2EE中持久层完成的功能,PrintStream完成显示的功能,类似于J2EE中表现层的功能)

    使用OutputStream和一个可选的表示缓存是否在每次换行时是否flush的标记构造。还提供很多和文件相关的构造方法。

    一般是一个终极(“final”)的包装器,很多时候我们都使用它!

    BufferedOutputStream

    使用它可以避免频繁地向IO写入数据,数据一般都写入一个缓存区,在调用flush方法后会清空缓存、一次完成数据的写入。

    从一个OutputStream或者和一个代表缓存区大小的可选参数构造。

    提供和其它OutputStream一致的接口,只是内部提供一个缓存的功能。

     

    3.3字节流的输入与输出的对应

           3.1节讲过输入与输出的对应,下图表示字节流部分的输入与输出的对应关系。 

     Java IO 全攻略---之一 - master24 - master24

     

           上图中蓝色的为主要的对应部分,红色的部分就是不对应部分。我习惯上称之为“不入流”部分。紫色的虚线部分代表这些流一般要搭配使用。从上面的图中可以看出Java IO中的字节流是极其对称的。

    “存在及合理”我们看看这些字节流中不太对称的几个类吧!

    1.         LineNumberInputStream主要完成从流中读取数据时,会得到相应的行号,至于什么时候分行、在哪里分行是由改类主动确定的,并不是在原始中有这样一个行号。在输出部分没有对应的部分,我们完全可以自己建立一个LineNumberOutputStream,在最初写入时会有一个基准的行号,以后每次遇到换行时会在下一行添加一个行号,看起来也是可以的。好像更不入流了。

    2.         PushbackInputStream的功能是查看最后一个字节,不满意就放入缓冲区。主要用在编译器的语法、词法分析部分。输出部分的BufferedOutputStream几乎实现相近的功能。

    3.         StringBufferInputStream已经被Deprecated,本身就不应该出现在InputStream部分,主要因为String应该属于字符流的范围。已经被废弃了,当然输出部分也没有必要需要它了!还允许它存在只是为了保持版本的向下兼容而已。

    4.         SequenceInputStream可以认为是一个工具类,将两个或者多个输入流当成一个输入流依次读取。完全可以从IO包中去除,还完全不影响IO包的结构,却让其更“纯洁”――纯洁的Decorator模式。

    5.         PrintStream也可以认为是一个辅助工具。主要可以向其他输出流,或者FileInputStream写入数据,本身内部实现还是带缓冲的。本质上是对其它流的综合运用的一个工具而已。一样可以踢出IO包!System.outSystem.out就是PrintStream的实例!

    蓝色的部分是IO字节流的主要组成部分,存在极强的对称关系。关于搭配使用的三对类补充一下:ObjectInputStream/ObjectOutputStreamDataInputStream/DataOutputStream主要是要求写对象/数据和读对象/数据的次序要保持一致,否则轻则不能得到正确的数据,重则抛出异常(一般会如此)PipedInputStream/PipedOutputStream在创建时一般就一起创建,调用它们的读写方法时会检查对方是否存在,或者关闭!道理极其简单――对方都不在了,怎么交互啊!


    3.4 字节流与字符流

           从上面我们可以看出IO中的字节流是极其复杂的,存在大量的类,到目前为止还没有真正使用它们,使用它们应该也是极其复杂的吧!JDK1.1SunIO库进行了重大的改进。看到ReaderWriter类时,大多数人的第一个感觉(不要太相信感觉哦!感觉也许会欺骗你的!)就是它们是用来替换原来的InputStreamOutputStream类。有新的类,干吗还使用旧的呢!?但实情并非如此。尽管Sun不建议使用原始的流库中的某些功能,但原来的流依然得到了保留,不仅为了保持向后兼容,主要原因是新库不是旧库的替代,而是对旧库的增强。从以下两点可以明显地看出:

    (1) 在老式的类层次结构里加入了新的类,这表明 Sun公司没有放弃老式流库的意图。

    (2) 在许多情况下,新库中类的使用需要联合老结构中的类。为达到这个目的,需要使用一些“桥”类,如:InputStreamReader将一个InputStream转换成ReaderOutputStreamWriter将一个OutputStream转换成Writer

    那么Sun为什么在Java 1.1里添加了ReaderWriter层次,最重要的原因便是国际化(Internationalization――i18n)的需求。老式IO流层次结构只支持8位字节流,不能很好地控制16位的Unicode字符。Java本身支持UnicodeSun又一致吹嘘其支持Unicode,因此有必要实现一个支持Unicode的流的层次结构,所以出现了ReaderWriter层次,以提供对所有IO操作中的Unicode的支持。除此之外,新库也对速度进行了优化,可比旧库更快地运行。

    8位的字节流和16位的字符流的对应关系,可以从ByteInputStream/ByteOutputStreamCharArrayInputStream/CharArrayOutputStream的对应关系中看出端倪。(还没看出来啊!赶紧去看看Java的基本数据类型)。

    因此在JavaIO体系中存在字节流和字符流的对应关系。下面就看看字符流吧!

    3.5 IO中的输入字符流

           下面是IO中输入字符流的继承图。

      • Reader
        • BufferedReader
          • LineNumberReader
        • CharArrayReader
        • FilterReader
          • PushbackReader
        • InputStreamReader
          • FileReader
        • PipedReader
        • StringReader

    在上面的关系图中可以看出:1.Reader是所有的输入字符流的父类,它是一个抽象类。2.CharReaderStringReader是两种基本的介质流,它们分别将Char数组、String中读取数据。PipedReader是从与其它线程共用的管道中读取数据。3. BufferedReader很明显就是一个装饰器,它和其子类负责装饰其它Reader对象。4.FilterReader是所有自定义具体装饰流的父类,其子类PushbackReaderReader对象进行装饰,会增加一个行号。5.InputStreamReader是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader可以说是一个达到此功能、常用的工具类,在其源代码中明显使用了将FileInputStream转变为Reader的方法。我们可以从这个类中得到一定的技巧。

           Reader中各个类的用途和使用方法基本和InputStream中的类使用一致。后面会有ReaderInputStream的对应关系。

    3.6 IO中的输出字符流

    下面是IO中输出字符流的继承图。

      • Writer
        • BufferedWriter
        • CharArrayWriter
        • FilterWriter
        • OutputStreamWriter
          • FileWriter
        • PipedWriter
        • PrintWriter
        • StringWriter

    在上面的关系图中可以看出:1.Writer是所有的输出字符流的父类,它是一个抽象类。2. CharArrayWriterStringWriter是两种基本的介质流,它们分别向Char数组、String中写入数据。PipedWriter是向与其它线程共用的管道中写入数据, 3. BufferedWriter是一个装饰器为Writer提供缓冲功能。4.PrintWriterPrintStream极其类似,功能和使用也非常相似。5.OutputStreamWriterOutputStreamWriter转换的桥梁,它的子类FileWriter其实就是一个实现此功能的具体类(具体可以研究一下Source Code)。功能和使用和OutputStream极其类似,后面会有它们的对应图。

    3.7字符流的输入与输出的对应

    下图为字符流的输入与输出的对应关系图:

     

     

           对应关系和字节流的输入输出基本一致,不必多说了吧!在下面的源代码阅读部分会仔细研究一些!

    3.8字节流和字符流的对应

           JavaIO中存在输入、输出的对应和字节流和字符流的对应,下面就看看字节流和字符流的对应吧!

    3.8.1输入的对应

           下图是IO中字节输入流与字符输入流的对应图:

     

     

           蓝色的表示对应的部分,红色的表示不对应的部分。至于为什么不对应还是你自己多看看源代码、多考虑考虑吧!还要强调一点就是即使对应,它们的继承关系也是不太对应的。

     

    3.8.2输出的对应

    下图是IO中字节输出流与字符输出流的对应图:

     

     

           不多说了!等讲述了AdapterDecorator模式会基本明白IO架构的!通过几个实例一般就可以使用了!


  • 相关阅读:
    LeetCode 227. Basic Calculator II
    LeetCode 224. Basic Calculator
    LeetCode 103. Binary Tree Zigzag Level Order Traversal
    LeetCode 102. Binary Tree Level Order Traversal
    LeetCode 106. Construct Binary Tree from Inorder and Postorder Traversal
    LeetCode 105. Construct Binary Tree from Preorder and Inorder Traversal
    LeetCode 169. Majority Element
    LeetCode 145. Binary Tree Postorder Traversal
    LeetCode 94. Binary Tree Inorder Traversal
    LeetCode 144. Binary Tree Preorder Traversal
  • 原文地址:https://www.cnblogs.com/daichangya/p/12959539.html
Copyright © 2011-2022 走看看