一、输入和输出

编程语言的I/O类库中常使用这个抽象概念,它代表任何有能力产出数据的数据源对象或者是有能力接收数据的接收端对象。“流”屏蔽了实际的I/O设备中处理数据的细节。

Java类库中的I/O类分成输入和输出两部分。通过继承,任何自Inputstream或Reader派生而来的类都含有名为read()的基本方法,用于读取单个字节或者字节数组。同样,任何自OutputStream或Writer派生而来的类都含有名为write()的基本方法,用于写单个字节或字节数组。但是,我们通常不会用到这些方法,他们之所以存在是因为别的类可以使用他们,以便提供更有用的接口。因此,我们很少使用单一的类来创建流对象,而是通过叠合多个对象来提供所期望的功能(这是装饰器设计模式)。实际上,Java中“流”类库让人迷惑的主要原因在于:创建单一的结果流,却需要创建多个对象。

1、InputStream类型

InputStream的作用是用来表示那些从不同数据源产生输入的类。这些数据源包括:

  • 字节数组
  • String对象
  • 文件
  • “管道”,工作方式yu与实际管道相似,即从一端输入,从另一端输出。
  • 一个由其他种类的流组成的序列,以便我们可以将他们手机合并到一个流内。
  • 其他数据源,如Internet连接等。

每一种数据源都有相应的InputStream子类。另外,FilterInputStream也属于一种InputStream,为“装饰器(decorator)”类提供积累。其中,“装饰器”类可以把属性或有用的接口与输入流连接在一起。

功能 构造器参数和如何使用
ByteArrayInputStream 允许将内存的缓冲区当做inputStream使用 缓冲区,字节将从中取出;作为一种数据源:将其与FilterInputStream对象相连以提供有用接口
StringBufferInputStream 将String转换成InputStream 字符串。底层实现实际使用StringBuffer;作为一种数据源:将其与FilterInputStream对象相连以提供有用接口
FileInputStream 用于从文件中读取信息 字符串,表示文件名、文件或FileDescriptor对象;作为一种数据源:将其与FilterInputStream对象相连以提供有用接口
PipedInputStream 产生用于写入相关PipedOutputStream的数据。实现管道化概念 PipedOutputStream;作为多线程中的数据源:将其与FilterInputStream对象相连以提供有用接口
SequenceInputStream 将两个或多个InputStream对象转换成单一InputStream 两个InputStream对象或一个容纳InputStream对象的容器Enumeration;作为一种数据源:将其与FilterInputStream对象相连以提供有用接口
FilterInputStream 抽象类,作为“装饰器”的接口。其中装饰器为其他的InputStream类提供有用功能。 见下表

2、OutputStream类型

该类别的类决定了输入要去往的目标:字节数组(但不是String,不过可以用字节数组自己创建)、文件或管道。

另外,FilterOutputStream为“装饰器”类提供了一个基类,“装饰器”类把属性或者有用的接口与输出流连接了起来。

功能 构造器参数 如何使用
ByteArrayOutputStream 在内存中创建缓冲区。所有送往“流”的数据都要放置在此缓冲区 缓存区初始化尺寸(可选的) 用于指定数据的目的地:将其与FilterOutputStream对象相连以提供有用的接口
FileOutputStream 用于将信息写至文件 字符串,表示文件名、文件或FileDescriptor对象 指定数据的目的地:将其与FilterOutputStream对象相连以提供有用的接口
PipedOutputStream 任何写入其中的信息dou'h都会自动作为相关PipedInputStream的输出。实现管道化概念 PipedInputStream 指定用于多线程的数据的目的地:将其与FilterOutputStream对象相连以提供有用接口
FilterOutputStream 抽象类,作为装饰器的接口。其中装饰器为其他OutputStream提供有用功能。 见下表 见下表

二、Reader和Writer

尽管一些原始的“流”类库不再被使用,但是InputStream和OutputStream在以面向字节形式的I/O中仍可以提供极有价值的功能,Reader和Writer则提供兼容Unicode与面向字符的I/O功能。另外:

  • Java1.1向InputStream和OutputStream继承层次结构中添加了一些新类,所以显然这两个类是不会被取代的。
  • 有时我们必须把来自于“字节”层次结构中的类和“字符”层次结构中的类结合起来使用。为了实现这个目的,要用到“适配器”类:InputStreamReader可以把InputStream转换为Reader,而OutputStreamWriter可以把OutputStream转换为Writer。

设计Reader和Writer继承层次结构主要是为了国际化。老的I/O流继承层次结构仅支持8位字节流,并且不能很好地处理16位的Unicode字符。由于Unicode用于字符国际化(java本身的char也是16位的Unicode),所以添加Reader和Writer继承层次结构就是为了在所有的I/O操作中都支持unicode。另外,新类库的设计使得它的操作比旧类库更快。

三、新I/O:NIO

速度的提高来自于所使用的结构更接近于操作系统执行I/O的方式:通道和缓冲器。我们可以把它想象成一个煤矿,通常是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车满载煤炭而归,我们从卡车上获得煤炭。也就是我们没有和通道直接交互,我们只是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器获得数据,要么向缓冲器发送数据。

唯一直接与通道交互的缓冲器是ByteBuffer--也就是说,可以存储未加工字节的缓冲器。当我们查询JDK文档中的java.nio.ByteBuffer时,会发现他是相当基础的类:通过告知分配多少存储空间来创建一个ByteBuffer对象,并且还有一个方法选择集,用于以原始的字节形式或基本数据类型输出和读取数据。但是,没办法输出或读取对象,即使是字符串对象也不行。这种处理虽然很低级,但却正好,因为这是大多数操作系统中更有效的映射方式。

旧I/O类库中有三个类被修改了,用以产生FileChannel。这三个被修改的类是FileInputStream、FileOutputStream以及用于既读又写的RandomAccessFile。注意这些是字节操纵流,与低层的NIO性质一致。Reader和Writer这种字符模式类不能用于产生通道,但是java.nio.channels.Channels类提供了实用方法,用以在tong'dao'zho通道中产生Reader和Writer。

四、对象序列化

Java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。这一过程甚至可以通过网络进行;这意味着序列化机制能够自动弥补不同操作系统之间的差异。也就是说,可以在运行windows系统的计算机上创建一个对象,将其序列化,通过网络将它发送给一台运行Unix系统的计算机,然后在那里准确的重新组装,而却不必担心数据在不同机器上的表示会不同,也不必关心字节的顺序或者其他任何细节。

就其本身来说,对象的序列化是非常有趣的,因为利用它可以实现轻量级持久性(lightweight persistence)。持久性一位置一个对象的生存周期并不取决于程序是否正在执行;他可以生存与程序的调用之间。通过j一个序列化对象写入磁盘,然后在重新调用程序时恢复该对象,就能够实现持久性的效果。之所以称其为“轻量级”,是因为不能用某种“persistent”关键字来简单的定义一个对象,并让系统自动维护其他细节。相反,对象必须在程序中显式的序列化(serialize)和反序列化(deserialize)还原。如果需要一个更严格的持久性机制,可以考虑像Hibernate之类的工具。

对象序列化的概念加入到语言中是为了支持两种主要特性。一是java的远程方法调用RMI,它使存活于其他计算机上的对象使用起来就像是存活于本机上一样。当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值。

再者,对Java Beans来说,对象的序列化也是必须的。使用一个Bean时,一般情况下是在设计阶段对他的状态信息进行配置。这种状态信息必须保存下来,并在程序启动时进行后期恢复;这种具体工作就是由对象序列化完成的。

只要对象实现了Serializable接口(该接口仅是一个标机接口,不包含任何方法),对象的序列化处理就会非常简单。当序列化的概念被加入到语言中时,许多标准类库发生了改变,以便具备序列化特性-其中包括所有基本数据类型的封装器,所有容器类以及许多其他的东西。甚至Class对象也可以被序列化。

要序列化一个对象,首先要创建某些OutputStream对象,然后将其封装在一个ObjectOutputStream对象内。这时,只需调用writeObject()即可将对象序列化,并将其发送给outputStream(对象序列化是基于字节的,因此要使用InputStream和OutputStream继承层次结构)。要反向进行该过程(即将一个序列还原为一个对象),需要将一个InputStream封装在ObjectInputStream内,然后调用readObject()。和往常一样,我们最后获得的是一个引用,它只想一个向上转型的Object,所以必须向下转型才能直接设置他们。

对象序列化特别聪明的一个地方是它不仅保存了对象的“全景图”,而且能追踪对象内包含的所有引用,并保存那些对象;接着又能对对象内包含的每个这样的引用进行追踪;以此类推。这种情况有时被称为“对象网”,单个对象可与之建立连接,而且它还包含了对象的引用数组以及成员对象。如果必须保持一套自己的对象序列化机制,那么维护那些可追踪到所有链接的代码可能会显得非常麻烦。然后,由于java的对象序列化似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法自动维护整个对象网。

五、总结

Java I/O流类库的确能满足我们的基本需求:我们可以通过控制台、文件、内存块,甚至因特网进行读写。通过继承,我们可以创建新类型的输入和输出对象。并且通过重新定义toString()方法,我们甚至可以对流接受的对象类型进行简单扩充。当我们向一个期望收到字符串的方法传送一个对象时,会自动调用toString()方法(这是java有限的自动类型转换功能)。

在I/O流类库的文档和设计中,仍留有一些没有解决的问题。I/O流类库使我们喜忧参半。它确实能做许多事情,而且具有可移植性。但是如果我们没有理解装饰器模式,那么这种设计就不是很直觉,因此在学习和传授它的过程中,需要额外的开销。而且它并不完善。

一旦我们理解了装饰器模式,并开始在某些情况下使用该类库以利用其提供的灵活性,那么你就开始从这个设计中受益了。到那个时候,为此额外多写几行代码的开销应该不至于使人觉得太麻烦。

java编程思想-java IO系统的更多相关文章

  1. 33.JAVA编程思想——JAVA IO File类

    33.JAVA编程思想--JAVA IO File类 RandomAccessFile用于包括了已知长度记录的文件.以便我们能用 seek()从一条记录移至还有一条:然后读取或改动那些记录. 各记录的 ...

  2. java编程思想-java中的并发(二)

    二.共享受限资源 有了并发就可以同时做多件事情了.但是,两个或多个线程彼此互相干涉的问题也就出现了.如果不防范这种冲突,就可能发生两个线程同时试图访问同一个银行账户,或向同一个打印机打印,改变同一个值 ...

  3. java编程思想-java中的并发(一)

    一.基本的线程机制 并发编程使我们可以将程序划分为多个分离的.独立运行的任务.通过使用多线程机制,这些独立任务中的每一个都将由执行线程来驱动. 线程模型为编程带来了便利,它简化了在单一程序中同时jia ...

  4. java编程思想-java中的并发(四)

    五. 新类库中的构件 Java SE5的java.util.concurrent引入了大量设计用来解决并发问题的新类.学习使用它们将有助于编写出更加简单而强壮的并发程序. 1. CountDownLa ...

  5. java编程思想——java IO系统

    一.什么是IO io在本质上是单个字节的移动.而流能够说是字节移动的载体和方式,它不停的向目标处移动数据.我们要做的就是依据流的方向从流中读取数据或者向流中写入数据. 二.java中支持IO操作的库类 ...

  6. java编程思想-java集合总结-基本概念

    1.java 容器类类库的用途是"保存对象",并将其划分为两个不同的概念: 1)Collection.一个独立元素的序列,这些元素都服从一条或多条规则.List 必须按照插入的顺序 ...

  7. java编程思想-java中的并发(三)

    三.终结任务 1. 在阻塞时终结 线程状态 一个线程可以处于以下四种状态之一: 1)新建(new):当线程被创建时,他只会短暂的处于这种状态.此时,他已经分配了必须的系统资源,并执行了初始化.此刻线程 ...

  8. java编程思想-java注解

    注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便的使用这些数据. 一.定义注解 注解的定义看起来很像接口的定义.事实上,与其他任何Java接口一样, ...

  9. java 编程思想-java运算符--曾经不太明确的

    1.java 运算符 主要是逻辑运算符和按位运算符;移位运算符-name tecmint.txt 逻辑运算符:And(&&) ; OR(||);Not(!) 按位运算符:And(&am ...

随机推荐

  1. 如何在word中的第3+n页处插入页面并重新从1开始

    在插入页码时有时可能会遇到这种情况: word的第一页是文档名称 第二页是目录 第三页才开始是正文,而我们希望看到页码从第三页开始才是第1页. 在第三页的开头,插入分隔符“下一页”,如果office2 ...

  2. Windows配置mycat

    MyCat使用Mysql的通讯协议模拟成一个MySQl服务器,并建立了完整的Schema(数据库).Table(数据表).User(用户)的逻辑模型,并将这套逻辑模型映射到后端的存储节点DataNod ...

  3. C++_STL

    容器概念讲解 vector deque

  4. 使用SFTP工具下载文件

    1. 打开SFTP会话 File->Connect SFTP Session  2. cd 到文件目录下 3. get 文件名称 sftp> get catalina.out 4. lpw ...

  5. Edge Linking

    因为噪声的存在, 检测出来的edge points有很多都是不相邻的. 所以边缘检测算法通常都有最后的连接步骤: 将属于同一edge的不相邻点连接起来(TODO, 是用一条路径将它们连通, 把路径中的 ...

  6. 基于Oracle的Mybatis 批量插入

    项目中会遇到这样的情况,一次性要插入多条数据到数据库中,有两种插入方法: 方法一: Mybatis本身只支持逐条插入,比较笨的方法,就是遍历一个List,循环中逐条插入,比如下面这段代码 for(Da ...

  7. js-延迟处理函数

    <script type="text/javascript"> var i = setTimeout('check()',5000); function check() ...

  8. RFID标签

    定义: RFID无线射频识别是一种非接触式的自动识别技术,它通过射频信号自动识别目标对象并获取相关数据,识别工作无须人工干预,可工作于各种恶劣环境.RFID技术可识别高速运动物体并可同时识别多个电子标 ...

  9. java中i=i++字节码分析

    原文出处: Ticmy 1 2 int i = 0; i = i++; 结果还是0为什么? 程序的执行顺序是这样的:因为++在后面,所以先使用i,"使用"的含义就是i++这个表达式 ...

  10. 1.值得推荐的C/C++框架和库 (转)

    值得学习的C语言开源项目 - 1. Webbench Webbench是一个在linux下使用的非常简单的网站压测工具.它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的 ...