一流对象简介
Java的io流用来处理数据,实现数据和流对象之间的转换。
Java的IO分类:根据处理数据的类型,流对象包括字符流和字节流;从流向来分,可以分为输入流和输出流。字节流的基类包括InputStream,OutputStream,是两个抽象类,其他以字节为导向的流都继承自这两个基类。字符流的两个基类:Reader,Writer。
字符流与字节流的区别:
1、字节流进行数据的读取时,读到一个字节就返回一个字节,字符流使用字节流读到一个或者多个字节时,先去查指定的编码表,将查到的字符返回。一个字符可能对应多个字节,例如:中文对应的字节数是两个,在UTF-8码表中是3个字节。
2、字节流可以处理所有类型的数据,如图片,mp3,avi等等;但是字符流只能处理字符数据。因此,在处理纯文本数据时可以优先考虑使用字符流,除此之外都用字节流。
二类的继承关系
流对象的类继承关系如下图所示:
在JDK1.5中,对象序列化流ObjectInputStream和ObjectOutputStream也分别继承自InputStream和OutputStream。
InputStream有七个直接的具体子类:
1、 ByteArrayInputStream
2、 FileInputStream
3、 ObjectInputStream
4、 PipedInputStream
5、 SequenceInputStream
6、 StringBufferInputStream
7、 FilterInputStream:四个具体子类:
(1) BufferedInputStream
(2)DataInputStream
(3)LineNumberInputStream
(4)PushbackInputStream
字符流
以上的流类根据输入流的源的类型,可以将这些流类分成两种:原始流类和链接流处理器.
输入流:
先谈谈输入流,输入流中跟数据源直接接触的类有:FileInputStream和ByteArrayInputStream,他们分别实现了从文件或者内存中的字节数组读入数据到输入流。
其他的输入流处理类都是装饰类(Decorator模式),下面对他们进行一下简单介绍:
BufferedInputStream: 提供了缓冲功能。
DataInputStream: 允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。
PipedInputStream: 允许以管道的方式来处理流。当连接到一个PipedOutputStream后,它会读取后者输出到管道的数据。
PushbackInputStream: 允许放回已经读取的数据。
SequenceInputStream: 能对多个inputstream进行顺序处理。
输出流:
基本上每个输入流类都有一个相应的输出流类,提供相应的输出流处理。
同样,跟数据目的地直接接触的类有:FileOutputStream和ByteArrayOutputStream,前者实现了把数据流写入文件的功能,后者实现了一个输出流,其中的数据被写入一个 byte 数组。缓冲区会随着数据的不断写入而自动增长。可使用 toByteArray() 和 toString() 获取数据。
下面对其它的装饰类做一下简单介绍:
BufferedOutputStream: 提供了缓冲功能的输出流,在写出完成之前要调用flush来保证数据的输出。
DataOutputStream: 数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中。然后,应用程序可以使用数据输入流将数据读入。
PipedOutputStream: 允许以管道的方式来处理流。可以将管道输出流连接到管道输入流来创建通信管道。管道输出流是管道的发送端。通常,数据由某个线程写入 PipedOutputStream 对象,并由其他线程从连接的 PipedInputStream 读取。
PrintStream: 为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。我们经常用到的System.out或者System.err都是PrintStream。
字符流处理类概述:
输入流:
跟数据源直接接触的类:
CharArrayReader: 从内存中的字符数组中读入数据,以对数据进行流式读取。
StringReader:从内存中的字符串读入数据,以对数据进行流式读取。
FileReader:从文件中读入数据。注意这里读入数据时会根据JVM的默认编码对数据进行内转换,而不能指定使用的编码。所以当文件使用的编码不是JVM默认编码时,不要使用这种方式。要正确地转码,使用InputStreamReader。
装饰类:
BufferedReader:提供缓冲功能,可以读取行:readLine();
LineNumberReader: 提供读取行的控制:getLineNumber()等方法。
InputStreamReader: 字节流通向字符流的桥梁:它使用指定的 charset 读取字节并将其解码为字符。
输出流:
根数据目的相关的类:
CharArrayWriter:把内存中的字符数组写入输出流,输出流的缓冲区会自动增加大小。输出流的数据可以通过一些方法重新获取。
StringWriter: 一个字符流,可以用其回收在字符串缓冲区中的输出来构造字符串。
FileWriter:把数据写入文件。
装饰类:
BufferedWriter:提供缓冲功能。
OutputStreamWriter:字符流通向字节流的桥梁:可使用指定的 charset 将要写入流中的字符编码成字节。
PrintWriter: 向文本输出流打印对象的格式化表示形式。
二其他与流相关的对象
File
File类也是java.io包中的一个基类,实现了Serializable和Comparable<File>两个接口。File类与数据操作和流对象紧密相关,在使用中通常与流对象一起出现。因为数据通常以文件的形式存在的,File就是IO体系中可以用于操作文件的流对象。
File对文件系统中的文件和文件夹进行对象的封装,可以通过对象的思想来操作文件以及文件夹。
其中的主要方法包括:
1.1 构造函数:File (String filename):将一个字符串路径(相对路径或者绝对路径)封装成File对象,该路径可以存在也可以不存在。此外,还有File(String parent, String child)和File(File parent, String child)两个构造函数。
1.2 由于不同系统中目录分隔符可能不同,File提供了separator属性可以获取跨平台的目录分隔符。例如:
File file=new File(“C:”+File.separator+”11”+File.separator+”test.txt”);
1.3 其他常用方法:
boolean createNewFile() throws IOException;如果表示的文件不存在,就建立一个.
boolean delete(),删除空文件夹;
public String getName(): 返回文件或目录名.
public String getParent(): 返回父路径名.或null.
public File getParentFile(): 以File对象的时候返回父对象.
public String getPath(): 将该抽象路径转会为路径字符串.
public long length():返回文件的字节长度. 如果是目录的话,长度不定.
三字符流
3.1 Reader
Reader是读取字符流的抽象类,该类实现两个接口:Readable和Closeable;因此其子类必须实现的方法是:
1. int read(char[], int off, int len) throws IOException——Readable接口
2. void close() throws IOException;——Closeable接口
字段Object lock: 在当前流中用来同步操作的object.
方法详解:
public int read(CharBuffer target):
返回值: 添加到buffer中的字符数,或者当到达文件尾时返回-1.
public int read() : 读取一个字符,返回读到的那个字符,如果读到流的末尾,则返回-1.
public int read(char[] cbuf): 将读到的字符存入指定的数组中,返回的是读到的字符个数,也就是数组中的元素个数,如果读到流的末尾,返回-1.
3.2 Writer
Writer实现的接口为Closeable,Flushable,Appendable三个接口,因此Writer类中的方法包括:无返回值的抽象方法close()和flush();以及三个返回值为Writer的具体方法append()(重载)。此外还包括针对不同类型数据的输出方法write(),可写的数据类型包括int, char和String。
3.3字符流缓冲区
字符流的缓冲区:用于提高对流的操作效率,其原理就是对数组进行封装,对应对象为BufferedWriter和BufferedReader;需要注意的地方在于,使用缓冲区对象时,首先要有流对象存在,缓冲内部就是在使用流对象的方法,只是加入了数组对数据进行了临时存储,提高数据操作的效率。
3.3.1 BufferedReader
继承自Reader,负责从字符输入流中读取数据,缓存数据以便提高读的效率.
BufferedReader可以用来包装任何读操作开销大的Reader.
构造方法:
public BufferedReader(Reader in, int sz):
public BufferedReader(Reader in):
public int read() : 读取单字符.
public int read(char[] cbuf, int off, int len):
public String readLine(): 读取一行数据,遇到如下情况认为行结束: 遇到换行("\n")或回车("\r") ,或则回车("\r")紧跟换行("\n").
返回值: 返回此行的内容,或者null(如果到达了末尾).
public void reset();
public void close();
3.3.2 BufferedWriter
下面是一个使用了File、BufferedWriter和BufferedReader的例子,目标:在E盘根目录下建立一个新的buff.txt文件,并写入数据到buff.txt中;然后再用BufferedReader将数据读取并输出。
String path="E:/buf.txt";
File file=new File(path);
if(!file.exists()){ //如果文件不存在则通过File创建
file.createNewFile();
}
BufferedWriter bw=new BufferedWriter(new FileWriter(file)); //创建一个新的 BufferedWriter对象
bw.write("测试BufferedWriter");//数据写入缓冲区
bw.flush();//对缓冲区的数据进行刷新,将数据写入目的地
bw.close();//关闭缓冲区
//读取缓冲区对象
BufferedReader br=new BufferedReader(new FileReader(file));
String line;
while((line=br.readLine())!=null){
System.out.println(line);
}
br.close();
3.4 转换流
转换流逝字节流和字符流之间的桥梁,从这种流对象中可以对读取到的字节数据进行指定编码表的编码转换。当字节和字符之间有转换动作时,或者流操作的数据需要进行表码表的指定时使用。
InputStreamReader(字节到字符)和OutputStreamWriter(字符到字节),这两个流对象是字符流体系中的成员,用于把一个以字节为导向的流转换成一个以字符为导向的流。
InputStreamReader类是从字节流到字符流的桥梁:读入字节,并根据指定的编码方式将其转换为字符流,使用的编码方式可以由名称指定,也可以是平台可以接受的缺省编码方式。InputStreamReader中的read()方法已经融入了编码表,在底层调用字节流的read方法时将获取的一个或者多个字节数据进行临时存储,并去查指定的编码表,如果编码表没有指定则查询默认编码表。转换刘流已经完成了编码转换的动作,对于直接操作的文本文件的FileReader就不必再次重新定义,只要继承该转换流获取方法就可以直接操作文本文件中的字符数据了。
OutputStreamWriter 将多个字符写入到一个输出流,根据指定的字符编码将多个字符转换为字节。每个 OutputStreamWriter合并它自己的CharToByteConverter, 因而是从字符流到字节流的桥梁。
下面是一个简单的例子,用于从键盘输入一个字符并判断输入是否是数字。
String s = null;
InputStreamReader re = new InputStreamReader(System.in);
BufferedReader br = new BufferedReader(re);
try {
s = br.readLine();
System.out.println("s= " + Integer.parseInt(s));
br.close();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (NumberFormatException e)//将字符串转换成数值类型,当输入不能转换为数值时抛出异常,打印提示信息。
{
e.printStackTrace();
System.out.println("输入的不是数字 ");
}
四. 字节流
抽象基类:InputStream,OutputStream
字节流可以操作任何数据,字符流使用的数组是字符数组char[],而字节流使用的数组是字节数组byte[]。
FileIntputStream、FileOutputStream、BufferedInputStream和BufferedOutputStream最常用的四个子类。使用方法如下:
3.1示例1——输出内容到指定文件
String path="E:/buf.txt";
FileOutputStream fos=new FileOutputStream(file);
String str="faew";
byte[] ss = new byte[str.length()];
for(int i=0;i<str.length();i++){
ss[i]=(byte) str.charAt(i);
}
fos.write(ss);//直接将数据写入指定的文件
fos.close();
3.2 示例2——拷贝一个图片
BufferedInputStream bfis=new BufferedInputStream(new FileInputStream("F:/1.png"));
BufferedOutputStream bfos=new BufferedOutputStream(new FileOutputStream("F:/2.png"));
int by=0;
while((by=bfis.read())!=-1){
bfos.write(by);
}
bfis.close();
bfos.close();
五. Java IO的一般使用原则
(一)、按数据来源(去向)分类:
1 、是文件: FileInputStream, FileOutputStream, (字节流 )FileReader, FileWriter( 字符 )
2 、是 byte[]: ByteArrayInputStream, ByteArrayOutputStream(字节流 )
3 、是 Char[]: CharArrayReader, CharArrayWriter(字符流 )
4 、是 String: StringBufferInputStream, StringBufferOuputStream (字节流 ) StringReader, StringWriter(字符流 )
5 、网络数据流: InputStream, OutputStream,(字节流 ) Reader, Writer( 字符流 )
(二)、按是否格式化输出分:
1 、要格式化输出: PrintStream, PrintWriter
(三)、按是否要缓冲分:
1 、要缓冲: BufferedInputStream, BufferedOutputStream (字节流 ) , BufferedReader, BufferedWriter(字符流 )
(四)、按数据格式分:
1 、二进制格式(只要不能确定是纯文本的) : InputStream, OutputStream及其所有带 Stream 结束的子类
2 、纯文本格式(含纯英文与汉字或其他编码方式); Reader, Writer及其所有带 Reader, Writer 的子类
(五)、按输入输出分:
1 、输入: Reader, InputStream类型的子类
2 、输出: Writer, OutputStream类型的子类
(六)、特殊需要:
1 、从 Stream到 Reader,Writer 的转换类: InputStreamReader, OutputStreamWriter
2 、对象输入输出: ObjectInputStream, ObjectOutputStream
3 、进程间通信: PipeInputStream, PipeOutputStream, PipeReader, PipeWriter
4 、合并输入: SequenceInputStream
5 、更特殊的需要: PushbackInputStream, PushbackReader, LineNumberInputStream, LineNumberReader
在不考虑特殊需要的情况下,决定使用哪个类以及它的构造进程的一般准则如下:
首先,考虑最原始的数据格式是什么
第二,是输入还是输出
第三,是否需要转换流
第四,数据来源(去向)
第五,是否要缓冲:(需要注意的是 readLine()是否有定义,有什么比 read, write 更特殊的输入或输出方法)
第六,是否要格式化输出
六. 序列化
序列化的过程就是把对象写入字节流和从字节流中读取对象。将对象状态转换成字节流之后,可以用java.io包中的各种字节流类将其保存到文件中、或者通过网络连接将对象数据发送到另一主机。对象的序列化功能在RMI、Socket、JMS和EJB中都有应用。
Java序列化机制包括序列化和反序列化。序列化过程将数据分解成字节流,以便存储在文件中或在网络上传输;反序列化是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。恢复数据需要有恢复数据的对象实例。ObjectOutputStream中的序列化过程与字节流连接,包括对象类型和版本信息。反序列化时,JVM用头信息声称对象实例,然后将对象字节流中的数据复制到对象数据成员中。
java.io包中有两个序列化对象的类:ObjectOutputStream负责将对象写入字节流,ObjectInputStream从字节流重构对象。
1. ObjectOutputStream类继承自OutputStream并实现了ObjectOutput,ObjectStreamConstants两个接口。WriteObject()方法是最重要的方法,用于对象的序列化。如果对象包含其他对象的引用,则WriteObject()方法递归序列化这些对象,每个ObjectOutputStream维护序列化的对象引用表,防止发送同一对象的多个拷贝。由于writeObject()可以序列化整组交叉引用的对象,因此同一ObjectOutputStream实例可能不小心被请求序列化同一对象。这时,进行反引用序列化,而不是再次写入对象字节流。
2. ObjectInputStream与ObjectOutputStream相似。它扩展InputStream接口。ObjectInputStream中的readObject()方法从字节流中反序列化对象。每次调用readObject()方法都返回流中下一个Object。对象字节流并不传输类的字节码,而是包括类名及其签名。readObject()收到对象时,JVM装入头中指定的类。如果找不到这个类,则readObject()抛出ClassNotFoundException,如果需要传输对象数据和字节码,则可以用RMI框架。ObjectInputStream的其余方法用于定制反序列化过程。
下面的代码是应用ObjectInputStream和ObjectOutputStream的一个例子。
FileOutputStream f = new FileOutputStream("tmp");
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject("Today111");
s.writeObject(new Date());
s.flush();
FileInputStream in = new FileInputStream("tmp");
ObjectInputStream ss = new ObjectInputStream(in);
String today = (String)ss.readObject();
Date date = (Date)ss.readObject();
System.out.println(today); //输出结果: Today111
System.out.println(date); //输出结果:Mon Mar 26 02:34:26 GMT 2012
(系统当前时间)
Java IO中的两个设计模式:装饰者模式和适配器模式。
1、装饰者模式:在由 InputStream,OutputStream,Reader和Writer代表的等级结构内部,有一些流处理器可以对另一些流处理器起到装饰作用,形成新的,具有改善了的功能的流处理器。装饰者模式是Java I/O库的整体设计模式。这样的一个原则是符合装饰者模式的。
2、适配器模式:在由InputStream,OutputStream,Reader和Writer代表的等级结构内部,有一些流处理器是对其它类型的流源的适配。这就是适配器模式的应用。
适配器模式应用到了原始流处理器的设计上面,构成了I/O库所有流处理器的起点。
Decorator模式简介
Decorator模式又名包装器(Wrapper),它的主要用途在于给一个对象动态的添加一些额外的职责。与生成子类相比,它更具有灵活性。
有时候,我们需要为一个对象而不是整个类添加一些新的功能,比如,给一个文本区添加一个滚动条的功能。我们可以使用继承机制来实现这一功能,但是这种方法不够灵活,我们无法控制文本区加滚动条的方式和时机。而且当文本区需要添加更多的功能时,比如边框等,需要创建新的类,而当需要组合使用这些功能时无疑将会引起类的爆炸。
我们可以使用一种更为灵活的方法,就是把文本区嵌入到滚动条中。而这个滚动条的类就相当于对文本区的一个装饰。这个装饰(滚动条)必须与被装饰的组件(文本区)继承自同一个接口,这样,用户就不必关心装饰的实现,因为这对他们来说是透明的。装饰会将用户的请求转发给相应的组件(即调用相关的方法),并可能在转发的前后做一些额外的动作(如添加滚动条)。通过这种方法,我们可以根据组合对文本区嵌套不同的装饰,从而添加任意多的功能。这种动态的对对象添加功能的方法不会引起类的爆炸,也具有了更多的灵活性。
以上的方法就是Decorator模式,它通过给对象添加装饰来动态的添加新的功能。
Component为组件和装饰的公共父类,它定义了子类必须实现的方法。
ConcreteComponent是一个具体的组件类,可以通过给它添加装饰来增加新的功能。
Decorator是所有装饰的公共父类,它定义了所有装饰必须实现的方法,同时,它还保存了一个对于Component的引用,以便将用户的请求转发给Component,并可能在转发请求前后执行一些附加的动作。
ConcreteDecoratorA和ConcreteDecoratorB是具体的装饰,可以使用它们来装饰具体的Component.
JAVA IO包中的Decorator模式
JDK提供的java.io包中使用了Decorator模式来实现对各种输入输出流的封装。以下将以java.io.OutputStream及其子类为例,讨论一下Decorator模式在IO中的使用。
首先来看一段用来创建IO流的代码:
以下是代码片段:
try {
OutputStream out = new DataOutputStream(new FileOutputStream("test.txt"));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
这段代码对于使用过JAVA输入输出流的人来说再熟悉不过了,我们使用 DataOutputStream封装了一个FileOutputStream.这是一个典型的Decorator模式的使用,FileOutputStream相当于Component,DataOutputStream就是一个Decorator.将代码改成如下,将会更容易理解:
以下是代码片段:
try {
OutputStream out = new FileOutputStream("test.txt");
out = new DataOutputStream(out);
} catch(FileNotFoundException e) {
e.printStatckTrace();
}
由于FileOutputStream和DataOutputStream有公共的父类OutputStream,因此对对象的装饰对于用户来说几乎是透明的。下面就来看看OutputStream及其子类是如何构成Decorator模式的:
OutputStream是一个抽象类,它是所有输出流的公共父类,其源代码如下:
以下是代码片段:
public abstract class OutputStream implements Closeable, Flushable {
public abstract void write(int b) throws IOException;
……
}
它定义了write(int b)的抽象方法。这相当于Decorator模式中的Component类。
ByteArrayOutputStream,FileOutputStream和 PipedOutputStream 三个类都直接从OutputStream继承,以ByteArrayOutputStream为例:
以下是代码片段:
public class ByteArrayOutputStream extends OutputStream {
protected byte buf[];
protected int count;
public ByteArrayOutputStream() {
this(32);
}
public ByteArrayOutputStream(int size) {
if (size〈 0) {
throw new IllegalArgumentException("Negative initial size: "
+ size);
}
buf = new byte[size];
}
public synchronized void write(int b) {
int newcount = count + 1;
if (newcount〉 buf.length) {
byte newbuf[] = new byte[Math.max(buf.length〈〈 1, newcount)];
System.arraycopy(buf, 0, newbuf, 0, count);
buf = newbuf;
}
buf[count] = (byte)b;
count = newcount;
}
……
}
它实现了OutputStream中的write(int b)方法,因此我们可以用来创建输出流的对象,并完成特定格式的输出。它相当于Decorator模式中的ConcreteComponent类。
接着来看一下FilterOutputStream,代码如下:
以下是代码片段:
public class FilterOutputStream extends OutputStream {
protected OutputStream out;
public FilterOutputStream(OutputStream out) {
this.out = out;
}
public void write(int b) throws IOException {
out.write(b);
}
……
}
同样,它也是从OutputStream继承。但是,它的构造函数很特别,需要传递一个OutputStream的引用给它,并且它将保存对此对象的引用。而如果没有具体的OutputStream对象存在,我们将无法创建 FilterOutputStream.由于out既可以是指向FilterOutputStream类型的引用,也可以是指向 ByteArrayOutputStream等具体输出流类的引用,因此使用多层嵌套的方式,我们可以为ByteArrayOutputStream添加多种装饰。这个FilterOutputStream类相当于Decorator模式中的Decorator类,它的write(int b)方法只是简单的调用了传入的流的write(int b)方法,而没有做更多的处理,因此它本质上没有对流进行装饰,所以继承它的子类必须覆盖此方法,以达到装饰的目的。
BufferedOutputStream 和 DataOutputStream是FilterOutputStream的两个子类,它们相当于Decorator模式中的 ConcreteDecorator,并对传入的输出流做了不同的装饰。以BufferedOutputStream类为例:
以下是代码片段:
public class BufferedOutputStream extends FilterOutputStream {
……
private void flushBuffer() throws IOException {
if (count〉 0) {
out.write(buf, 0, count);
count = 0;
}
}
public synchronized void write(int b) throws IOException {
if (count〉= buf.length) {
flushBuffer();
}
buf[count++] = (byte)b;
}
……
}
这个类提供了一个缓存机制,等到缓存的容量达到一定的字节数时才写入输出流。首先它继承了FilterOutputStream,并且覆盖了父类的write(int b)方法,在调用输出流写出数据前都会检查缓存是否已满,如果未满,则不写。这样就实现了对输出流对象动态的添加新功能的目的。
总 结
在java.io包中,不仅OutputStream用到了Decorator设计模式,InputStream,Reader,Writer等都用到了此模式。而作为一个灵活的,可扩展的类库,JDK中使用了大量的设计模式,比如在 Swing包中的MVC模式,RMI中的Proxy模式等等。对于JDK中模式的研究不仅能加深对于模式的理解,而且还有利于更透彻的了解类库的结构和组成。