Java的io流,是一个很容易混淆的概念,里面涉及了很深层次的类继承,并且应用了著名的装饰模式,首先,我们来看下io流的具体类提及其应用和概念;
首先,流是什么?流是什么?流来自java.io包,
形象的比喻——水流 ,文件======程序 ,文件和程序之间连接一个管道,水流就在之间形成了,自然也就出现了方向:可以流进,也可以流出.便于理解,这么定义流: 流就是一个管道里面有流水,这个管道连接了文件和程序。
根据流的方向划分,我们分为流进流和流出流:
流进流:
public abstract class InputStream
extends Object
implements Closeable
此抽象类是表示字节输入流的所有类的超类。
需要定义 InputStream
子类的应用程序必须总是提供返回下一个输入字节的方法。
这个类是所有流入流的超类,他提供了从程序从数据源读取数据的方式:
read()
从输入流中读取数据的下一个字节。
int read(byte[] b)
从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
int read(byte[] b, int off, int len)
将输入流中最多 len 个数据字节读入 byte 数组。
其中辅助这个读取方法的另外两个方法是:
int available()
返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
long skip(long n)
跳过和丢弃此输入流中数据的 n 个字节。
相对而言:流出流正好相反:
public abstract class OutputStreamextends Objectimplements Closeable, Flushable此抽象类是表示输出字节流的所有类的超类。输出流接受输出字节并将这些字节发送到某个接收器。
需要定义 OutputStream 子类的应用程序必须始终提供至少一种可写入一个输出字节的方法。
void write(byte[] b)
将 b.length 个字节从指定的 byte 数组写入此输出流。
void write(byte[] b, int off, int len)
将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。
abstract void write(int b)
将指定的字节写入此输出流
正好,这个两个接口提供了java 的基本输入输出功能;
字节流在传输的时候,更多的是方便我们传输二进制的文件,如下:
package org.corey.servlet;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class IODemo {
/**
* @param args
*/
public static void main(String[] args) {
File srcFile = new File("c:/a.jpg");
File tgtFile = new File("d:/a.jpg");
try {
FileInputStream fin = new FileInputStream(srcFile);
byte[] bts = new byte[fin.available()];
fin.read(bts, 0, fin.available() - 1);
fin.close();
FileOutputStream fos = new FileOutputStream(tgtFile);
fos.write(bts);
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
在此接口的功能基础上,根据数据来源的不同以及和数据流向的不同,扩展了几个类:分别是:
InputStream:
AudioInputStream, ByteArrayInputStream, FileInputStream, FilterInputStream, InputStream, ObjectInputStream, PipedInputStream, SequenceInputStream, StringBufferInputStream
OutputStream:
ByteArrayOutputStream, FileOutputStream, FilterOutputStream, ObjectOutputStream, OutputStream, PipedOutputStream
诸如:
package org.corey.servlet;
import java.io.ByteArrayInputStream;
import java.io.File;
public class IODemo {
/**
* @param args
*/
public static void main(String[] args) {
try {
byte[] buffer=new byte[]{1,2,3,4,5,6};
ByteArrayInputStream bts=new ByteArrayInputStream(buffer);
int byteValue;
while((byteValue=bts.read())!=-1){
System.out.println(byteValue);
}
} catch (Exception e) {
System.out.println("create the fin is error");
e.printStackTrace();
}
}
}
package org.corey.servlet;
import java.io.ByteArrayOutputStream;
public class IODemo {
/**
* @param args
*/
public static void main(String[] args) {
try {
byte[] buffer = new byte[] { 1, 2, 3, 4, 5, 6 };
byte[] targetBuffer;
ByteArrayOutputStream bos = new ByteArrayOutputStream(10);
bos.write(buffer);
targetBuffer=bos.toByteArray();
int arrayIndex=0;
while(arrayIndex<buffer.length){
System.out.println(targetBuffer[arrayIndex]);
arrayIndex++;
}
} catch (Exception e) {
System.out.println("create the fin is error");
e.printStackTrace();
}
}
}
基于字符形式的io:
以上接口操作的方法,都是基于字节的,我们知道,一个字符串,经过解码以后,可以解码成为二进制位,二进制位更抽象的表示成为字节数组,而java是unicode编码的,所以在java里面还有两个基于字符的流入流和流出流;方便我们操纵一个文本数据的io流进流出,
同样,操纵字符流的流出流和流入流也有两个对应的接口,并且提供了基本的io方法
字符流入流:
public abstract class Readerextends Objectimplements Readable, Closeable用于读取字符流的抽象类。子类必须实现的方法只有 read(char[], int, int) 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
判断此流是否支持 mark() 操作。
int read()
读取单个字符。
int read(char[] cbuf)
将字符读入数组。
abstract int read(char[] cbuf, int off, int len)
将字符读入数组的某一部分。
int read(CharBuffer target)
试图将字符读入指定的字符缓冲区。
辅助方法有:
long skip(long n)
跳过字符。
字符流出流:
public abstract class Writerextends Objectimplements Appendable, Closeable, Flushable写入字符流的抽象类。子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
基本的输出方法有:
void write(char[] cbuf)
写入字符数组。
abstract void write(char[] cbuf, int off, int len)
写入字符数组的某一部分。
void write(int c)
写入单个字符。
void write(String str)
写入字符串。
void write(String str, int off, int len)
写入字符串的某一部分。
package org.corey.servlet;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
public class IODemo {
/**
* @param args
*/
public static void main(String[] args) {
File srcFile = new File("c:/a.txt");
File tgtFile = new File("d:/a.jpg");
try {
FileReader fReader = new FileReader(srcFile);
char[] cbuf=new char[256];
int endIndex=fReader.read(cbuf);
String fStr=new String(cbuf,0,endIndex);
System.out.println(fStr);
} catch (Exception e) {
System.out.println("create the fin is error");
e.printStackTrace();
}
}
}
文本文件完整的输出
有人会问,如果,我取得的io流是基于字节的,那么我可不可以把这些字节组装起来,变成基于字符的呢,应该可以把,我大不了把几个字节组装起来(即利用编码方式)
确实,在字节流的io操作于字符流的io操作之间是有桥梁的,那就是inputStreamReader和OutputStreamWriter
下面我们就来看看经典的装饰器模式的应用:
利用层次化对象动态和透明地添加单个对象的能力的做法叫作“装饰器”(Decorator)方案——“方案”属于本书第16章的主题(注释①)。装饰器方案规定封装于初始化对象中的所有对象都拥有相同的接口,以便利用装饰器的“透明”性质——我们将相同的消息发给一个对象,无论它是否已被“装饰”。这正是在Java IO库里存在“过滤器”(Filter)类的原因:抽象的“过滤器”类是所有装饰器的基础类(装饰器必须拥有与它装饰的那个对象相同的接口,但装饰器亦可对接口作出扩展,这种情况见诸于几个特殊的“过滤器”类中)。
子类处理要求大量子类对每种可能的组合提供支持时,便经常会用到装饰器——由于组合形式太多,造成子类处理变得不切实际。Java IO库要求许多不同的特性组合方案,这正是装饰器方案显得特别有用的原因。但是,装饰器方案也有自己的一个缺点。在我们写一个程序的时候,装饰器为我们提供了大得多的灵活性(因为可以方便地混合与匹配属性),但它们也使自己的代码变得更加复杂。原因在于Java IO库操作不便,我们必须创建许多类——“核心”IO类型加上所有装饰器——才能得到自己希望的单个IO对象。
FilterInputStream和FilterOutputStream(这两个名字不十分直观)提供了相应的装饰器接口,用于控制一个特定的输入流(InputStream)或者输出流(OutputStream)。它们分别是从InputStream和OutputStream衍生出来的。此外,它们都属于抽象类,在理论上为我们与一个流的不同通信手段都提供了一个通用的接口。事实上,FilterInputStream和FilterOutputStream只是简单地模仿了自己的基础类,它们是一个装饰器的基本要求。
简单的一句话就是,因为一个程序语言的io设计是很复杂的,因为他要处理的情况很多,就如java一样,我们就感觉类很多,我们很难记住,哪么java采用的方法是装饰模式,装饰模式很经典(可查询资料),意思就是可以动态的为固有类增加心的功能,java io系统就采用了这种方式,给我们本来很普通的io流增加了缓存,数据类型的输入输出等等,所有的增加心功能的类都继承于
public class FilterOutputStream
extends OutputStream
此类是过滤输出流的所有类的超类。这些流位于已存在的输出流(基础 输出流)之上,它们将已存在的输出流作为其基本数据接收器,但可能直接传输数据或提供一些额外的功能。
FilterOutputStream
类本身只是简单地重写那些将所有请求传递给所包含输出流的 OutputStream
的所有方法。FilterOutputStream
的子类可进一步地重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段。
很简单的图形比喻:
、、
正常流 |
Filter流给这个管道增加的新功能
|
代码如下:
package org.corey.servlet;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileOutputStream;
public class IODemo {
/**
* @param args
*/
public static void main(String[] args) {
try {
File targetFile = new File("c:/b.txt");
FileOutputStream fos = new FileOutputStream(targetFile);
DataOutputStream dos = new DataOutputStream(fos);
dos.writeFloat( 2.3f );
dos.writeInt(5);
dos.close();
fos.close();
FileInputStream fis = new FileInputStream(targetFile);
DataInputStream dis = new DataInputStream(fis);
float floatValue=dis.readFloat();
int intValue=dis.readInt();
System.out.println(floatValue);
System.out.println(intValue);
dis.close();
fis.close();
} catch (Exception e) {
System.out.println("create the fin is error");
e.printStackTrace();
}
}
}
另外还有
缓冲区io功能类:
public class BufferedOutputStream
extends FilterOutputStream
该类实现缓冲的输出流。通过设置这种输出流,应用程序就可以将各个字节写入底层输出流中,而不必针对每次字节写入调用底层系统。
格式化io功能类:
public class PrintStream
extends FilterOutputStream
implements Appendable, Closeable
PrintStream 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。它还提供其他两项功能。与其他输出流不同,PrintStream 永远不会抛出 IOException;而是,异常情况仅设置可通过 checkError 方法测试的内部标志。另外,为了自动刷新,可以创建一个 PrintStream;这意味着可在写入 byte 数组之后自动调用 flush 方法,可调用其中一个 println 方法,或写入一个换行符或字节 ('/n')。
PrintStream 打印的所有字符都使用平台的默认字符编码转换为字节。在需要写入字符而不是写入字节的情况下,应该使用 PrintWriter 类。
不过这里要注意,对应的,不要轻易认为FilterRader也有一个子类叫做BufferedReader;
Bufferedreader是直接继承与Reader的;同样BufferedWriter也是直接继承Writer的;