- 浏览: 33079 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
July01:
最近了解到一款StratoIO打印控件,功能如下:1、Html ...
Lodop打印控件 -
yangjb:
引入jquery就可以运行了,版本不限
jquery自动及手动滑入滑出 -
nianshi:
能发可运行代码就好了
jquery自动及手动滑入滑出
Java将数据于目的地及来源之间的流动抽象化为一个流(Stream),而流当中流动的则是位数据。
14.2.1 InputStream和OutputStream
计算机中实际上数据的流动是通过电路,而上面流动的则是电流,电流的电位有低位与高位,即数字的0与1位。从程序的观点来说,通常会将数据目的地(例如内存)与来源(例如文件)之间的数据流动抽象化为一个流(Stream),而其中流动的则是位数据,如图14-1所示。
图14-1 数据的流动抽象化为流的概念
在Java SE中有两个类用来作流的抽象表示:java.io.InputStream与java.io.OutputStream。
InputStream 是所有表示位输入流的类之父类,它是一个抽象类,继承它的子类要重新定义其中所定义的抽象方法。InputStream是从装置来源地读取数据的抽象表示,例如System中的标准输入流in对象就是一个InputStream类型的实例。在Java程序开始之后,in流对象就会开启,目的是从标准输入装置中读取数据,这个装置通常是键盘或是用户定义的输入装置。
OutputStream 是所有表示位输出流的类之父类,它是一个抽象类。子类要重新定义其中所定义的抽象方法,OutputStream是用于将数据写入目的地的抽象表示。例如 System中的标准输出流对象out其类型是java.io.PrintStream,这个类是OutputStream的子类 (java.io.FilterOutputStream继承OutputStream, PrintStream再继承FilterOutputStream)。在程序开始之后,out流对象就会开启,可以通过out来将数据写至目的地装置,这个装置通常是屏幕显示或用户定义的输出装置。
范例14.4可以读取键盘输入流,in对象的read()方法一次读取一个字节的数据,读入的数据以int类型返回。所以在使用out对象将数据显示出来时,就是10进制方式。
ü 范例14.4 StreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class StreamDemo {
public static void main(String[] args) {
try {
System.out.print("输入字符: ");
System.out.println("输入字符十进制表示: " +
System.in.read());
}
catch(IOException e) {
e.printStackTrace();
}
}
}
执行结果:
输入字符: A
输入字符十进制表示: 65
字符A输入后由标准输入流in读取,A的位表示以十进制来看就是65,这是A字符的编码(查查ASCII编码表就知道了)。
一般来说,很少直接实现InputStream或OutputStream上的方法,因为这些方法比较低级,通常会实现它们的子类。这些子类上所定义的方法在进行输入/输出时更为方便。
14.2.2 FileInputStream和FileOutputStream
java.io.FileInputStream 是InputStream的子类。从开头File名称上就可以知道,FileInputStream与从指定的文件中读取数据至目的地有关。而 java.io.FileOutputStream是OutputStream的子类,顾名思义,FileOutputStream主要与从来源地写入数据至指定的文件中有关。
当建立一个FileInputStream或FileOutputStream的实例时,必须指定文件位置及文件名称,实例被建立时文件的流就会开启;而不使用流时,必须关闭文件流,以释放与流相依的系统资源,完成文件读/写的动作。
FileInputStream可以使用 read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。
范例14.5是使用FileInputStream与FileOutputStream的一个例子。程序可以复制文件,它会先从来源文件读取数据至一个byte数组中,然后再将byte数组的数据写入目的文件。
ü 范例14.5 FileStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class FileStreamDemo {
public static void main(String[] args) {
try {
byte[] buffer = new byte[1024];
// 来源文件
FileInputStream fileInputStream =
new FileInputStream(new File(args[0]));
// 目的文件
FileOutputStream fileOutputStream =
new FileOutputStream(new File(args[1]));
// available()可取得未读取的数据长度
System.out.println("复制文件:" +
fileInputStream.available() + "字节");
while(true) {
if(fileInputStream.available() < 1024) {
// 剩余的数据比1024字节少
// 一位一位读出再写入目的文件
int remain = -1;
while((remain = fileInputStream.read())
!= -1) {
fileOutputStream.write(remain);
}
break;
}
else {
// 从来源文件读取数据至缓冲区
fileInputStream.read(buffer);
// 将数组数据写入目的文件
fileOutputStream.write(buffer);
}
}
// 关闭流
fileInputStream.close();
fileOutputStream.close();
System.out.println("复制完成");
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println(
"using: java FileStreamDemo src des");
e.printStackTrace();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
程序中示范了两个read()方法,一个方法可以读入指定长度的数据至数组,另一个方法一次可以读入一个字节。每次读取之后,读取的光标都会往前进,如果读不到数据则返回–1,使用available()方法获得还有多少字节可以读取。除了使用File来建立FileInputStream、 FileOutputStream的实例之外,也可以直接使用字符串指定路径来建立。
// 来源文件FileInputStream fileInputStream = new FileInputStream(args[0]);// 目的文件FileOutputStream fileOutputStream = new FileOutputStream(args[1]);在不使用文件流时,记得使用close()方法自行关闭流,以释放与流相依的系统资源。一个执行的结果范例如下,它将FileDemo.java复制为FileDemo.txt:
java onlyfun.caterpillar.FileStreamDemo FileDemo.java FileDemo.txt
复制文件:1723字节
复制完成
FileOutputStream默认会以新建文件的方式来开启流。如果指定的文件名称已经存在,则原文件会被覆盖;如果想以附加的模式来写入文件,则可以在构建FileOutputStream实例时指定为附加模式。例如:
FileOutputStream fileOutputStream = new FileOutputStream(args[1], true);构建方法的第二个append参数如果设置为true,在开启流时如果文件不存在则会新建一个文件,如果文件存在就直接开启流,并将写入的数据附加至文件末端。
虽然我一向不喜欢使用过长的范例来作程序示范(也不喜欢看很长的范例),不过本章的范例与其他各章的比起来相对长了一些,我会在程序中多用注释解释程序的逻辑。因为解释输入/输出操作最好的方式,是呈现一个具实用性的范例,本章的范例除了练习的作用之外,日后需要某些输入/输出功能时,也可以来参考看看如何实现。
14.2.3 BufferedInputStream和BufferedOutputStream
在介绍FileInputStream和 FileOutputStream的例子中,使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度。为了减少对硬盘的存取,通常从文件中一次读入一定长度的数据,而写入时也是一次写入一定长度的数据,这可以增加文件存取的效率。
java.io.BufferedInputStream 与java.io.BufferedOutputStream可以为InputStream、OutputStream类的对象增加缓冲区功能。构建 BufferedInputStream实例时,需要给定一个InputStream类型的实例,实现BufferedInputStream时,实际上最后是实现InputStream实例。同样地,在构建BufferedOutputStream时,也需要给定一个OutputStream实例,实现 BufferedOutputStream时,实际上最后是实现OutputStream实例。
BufferedInputStream的数据成员buf是一个位数组,默认为2048字节。当读取数据来源时,例如文件,BufferedInputStream会尽量将buf填满。当使用read ()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取。当buf中的数据不足时,BufferedInputStream才会再实现给定的InputStream对象的read()方法,从指定的装置中提取数据,如图14-2所示。
图14-2 BufferedInputStream在内部有buf成员作为缓冲区
BufferedOutputStream的数据成员buf是一个位数组,默认为512字节。当使用write()方法写入数据时,实际上会先将数据写至buf中,当buf已满时才会实现给定的 OutputStream对象的write()方法,将buf数据写至目的地,而不是每次都对目的地作写入的动作。
下面将范例14.5做个改写,这次不用自行设置缓冲区,而使用BufferedInputStream和BufferedOutputStream让程序看来简单一些,也比较有效率。
ü 范例14.6 BufferedStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class BufferedStreamDemo {
public static void main(String[] args) {
try {
byte[] data = new byte[1];
File srcFile = new File(args[0]);
File desFile = new File(args[1]);
BufferedInputStream bufferedInputStream =
new BufferedInputStream(
new FileInputStream(srcFile));
BufferedOutputStream bufferedOutputStream =
new BufferedOutputStream(
new FileOutputStream(desFile));
System.out.println("复制文件:" +
srcFile.length() + "字节");
while(bufferedInputStream.read(data) != -1) {
bufferedOutputStream.write(data);
}
// 将缓冲区中的数据全部写出
bufferedOutputStream.flush();
// 关闭流
bufferedInputStream.close();
bufferedOutputStream.close();
System.out.println("复制完成");
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println(
"using: java UseFileStream src des");
e.printStackTrace();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
为了确保缓冲区中的数据一定被写出至目的地,建议最后执行flush()将缓冲区中的数据全部写出目的流中。这个范例的执行结果与范例14.5是相同的。
BufferedInputStream和 BufferedOutputStream并没有改变InputStream或 OutputStream的行为,读入或写出时的动作还是InputStream和OutputStream负责。 BufferedInputStream和BufferedOutputStream只是在操作对应的方法之前,动态地为它们加上一些额外功能(像缓冲区功能),在这里是以文件存取流为例,实际上可以在其他流对象上也使用BufferedInputStream和BufferedOutputStream 功能。
*************************************************************************************
本篇主要分析:1.如何将byte数组适配至ByteArrayInputStream,对应与IO部分的适配器模式;2.BufferedInputStream的工作原理,对应于IO的装饰器模式,会首先研究InputStream和FilterInputStream的源代码,同时会将要谈谈软件设计中的缓存相关的知识。后面专门一章分析PipedInputStream和PipedOutStream,简单谈谈管道相关的知识,以及软件架构的想法。
1 InputStream
InputStream 是输入字节流部分,装饰器模式的顶层类。主要规定了输入字节流的公共方法。
package java.io;
public abstract class InputStream implements Closeable {
private static final int SKIP_BUFFER_SIZE = 2048; //用于skip方法,和skipBuffer相关
private static byte[] skipBuffer; // skipBuffer is initialized in skip(long), if needed.
public abstract int read() throws IOException; //从输入流中读取下一个字节,
//正常返回0-255,到达文件的末尾返回-1
//在流中还有数据,但是没有读到时该方法会阻塞(block)
//Java IO和New IO的区别就是阻塞流和非阻塞流
//抽象方法哦!不同的子类不同的实现哦!
//将流中的数据读入放在byte数组的第off个位置先后的len个位置中
//放回值为放入字节的个数。
public int read(byte b[], int off, int len) throws IOException { //
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
} //检查输入是否正常。一般情况下,检查输入是方法设计的第一步
int c = read(); //读取下一个字节
if (c == -1) { return -1; } //到达文件的末端返回-1
b[off] = (byte)c; //放回的字节downcast
int i = 1; //已经读取了一个字节
try {
for (; i < len ; i++) { //最多读取len个字节,所以要循环len次
c = read(); //每次循环从流中读取一个字节
//由于read方法阻塞,
//所以read(byte[],int,int)也会阻塞
if (c == -1) { break; } //到达末尾,理所当然放回-1
b[off + i] = (byte)c; //读到就放入byte数组中
}
} catch (IOException ee) { }
return i;
//上面这个部分其实还有一点比较重要,int i = 1;在循环的外围,或许你经常见到,
//或许你只会在循环是才声明,为什么呢?
//声明在外面,增大了变量的生存周期(在循环外面),所以后面可以return返回
//极其一般的想法。在类成员变量生命周期中使用同样的理念。
//在软件设计中,类和类的关系中也是一样的。
} //这个方法在利用抽象方法read,某种意义上简单的Templete模式。
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
} //利用上面的方法read(byte[] b)
public long skip(long n) throws IOException {
long remaining = n; //方法内部使用的、表示要跳过的字节数目,
//使用它完成一系列字节读取的循环
int nr;
if (skipBuffer == null)
skipBuffer = new byte[SKIP_BUFFER_SIZE]; //初始化一个跳转的缓存
byte[] localSkipBuffer = skipBuffer; //本地化的跳转缓存
if (n <= 0) { return 0; } //检查输入参数,应该放在方法的开始
while (remaining > 0) { //一共要跳过n个,每次跳过部分,循环
nr = read(localSkipBuffer, 0, (int) Math.min(SKIP_BUFFER_SIZE, remaining));
//利用上面的read(byte[],int,int)方法尽量读取n个字节
if (nr < 0) { break; } //读到流的末端,则返回
remaining -= nr; //没有完全读到需要的,则继续循环
}
return n - remaining;//返回时要么全部读完,要么因为到达文件末端,读取了部分
}
public int available() throws IOException { //查询流中还有多少可以读取的字节
return 0;
}
//该方法不会block。在java中抽象类方法的实现一般有以下几种方式:
//1.抛出异常(java.util);2.“弱”实现。象上面这种。子类在必要的时候覆盖它。
//3.“空”实现。下面有例子。
public void close() throws IOException {}
//关闭当前流、同时释放与此流相关的资源
public synchronized void mark(int readlimit) {}
//在当前位置对流进行标记,必要的时候可以使用reset方法返回。
//markSupport可以查询当前流是否支持mark
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
//对mark过的流进行复位。只有当流支持mark时才可以使用此方法。
//看看mark、available和reset方法。体会为什么?!
public boolean markSupported() { //查询是否支持mark
return false;
} //绝大部分不支持,因此提供默认实现,返回false。子类有需要可以覆盖。
}
2 FilterInputStream
这是字节输入流部分装饰器模式的核心。是我们在装饰器模式中的Decorator对象,主要完成对其它流装饰的基本功能。下面是它的源代码:
package java.io;
//该类对被装饰的流进行基本的包裹。不增加额外的功能。
//客户在需要的时候可以覆盖相应的方法。具体覆盖可以在ByteInputStream中看到!
public class FilterInputStream extends InputStream {
protected volatile InputStream in; //将要被装饰的字节输入流
protected FilterInputStream(InputStream in) { //通过构造方法传入此被装饰的流
this.in = in;
}
//装饰器的代码特征:被装饰的对象一般是装饰器的成员变量
//上面几行可以看出。
//下面这些方法,完成最小的装饰――0装饰,只是调用被装饰流的方法而已
public int read() throws IOException {
return in.read();
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
public long skip(long n) throws IOException {
return in.skip(n);
}
public int available() throws IOException {
return in.available();
}
public void close() throws IOException {
in.close();
}
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
public synchronized void reset() throws IOException {
in.reset();
}
public boolean markSupported() {
return in.markSupported();
}
//以上的方法,都是通过调用被装饰对象in完成的。没有添加任何额外功能
//装饰器模式中的Decorator对象,不增加被装饰对象的功能。
//它是装饰器模式中的核心。更多关于装饰器模式的理论请阅读博客中的文章。
}
14.2.1 InputStream和OutputStream
计算机中实际上数据的流动是通过电路,而上面流动的则是电流,电流的电位有低位与高位,即数字的0与1位。从程序的观点来说,通常会将数据目的地(例如内存)与来源(例如文件)之间的数据流动抽象化为一个流(Stream),而其中流动的则是位数据,如图14-1所示。
图14-1 数据的流动抽象化为流的概念
在Java SE中有两个类用来作流的抽象表示:java.io.InputStream与java.io.OutputStream。
InputStream 是所有表示位输入流的类之父类,它是一个抽象类,继承它的子类要重新定义其中所定义的抽象方法。InputStream是从装置来源地读取数据的抽象表示,例如System中的标准输入流in对象就是一个InputStream类型的实例。在Java程序开始之后,in流对象就会开启,目的是从标准输入装置中读取数据,这个装置通常是键盘或是用户定义的输入装置。
OutputStream 是所有表示位输出流的类之父类,它是一个抽象类。子类要重新定义其中所定义的抽象方法,OutputStream是用于将数据写入目的地的抽象表示。例如 System中的标准输出流对象out其类型是java.io.PrintStream,这个类是OutputStream的子类 (java.io.FilterOutputStream继承OutputStream, PrintStream再继承FilterOutputStream)。在程序开始之后,out流对象就会开启,可以通过out来将数据写至目的地装置,这个装置通常是屏幕显示或用户定义的输出装置。
范例14.4可以读取键盘输入流,in对象的read()方法一次读取一个字节的数据,读入的数据以int类型返回。所以在使用out对象将数据显示出来时,就是10进制方式。
ü 范例14.4 StreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class StreamDemo {
public static void main(String[] args) {
try {
System.out.print("输入字符: ");
System.out.println("输入字符十进制表示: " +
System.in.read());
}
catch(IOException e) {
e.printStackTrace();
}
}
}
执行结果:
输入字符: A
输入字符十进制表示: 65
字符A输入后由标准输入流in读取,A的位表示以十进制来看就是65,这是A字符的编码(查查ASCII编码表就知道了)。
一般来说,很少直接实现InputStream或OutputStream上的方法,因为这些方法比较低级,通常会实现它们的子类。这些子类上所定义的方法在进行输入/输出时更为方便。
14.2.2 FileInputStream和FileOutputStream
java.io.FileInputStream 是InputStream的子类。从开头File名称上就可以知道,FileInputStream与从指定的文件中读取数据至目的地有关。而 java.io.FileOutputStream是OutputStream的子类,顾名思义,FileOutputStream主要与从来源地写入数据至指定的文件中有关。
当建立一个FileInputStream或FileOutputStream的实例时,必须指定文件位置及文件名称,实例被建立时文件的流就会开启;而不使用流时,必须关闭文件流,以释放与流相依的系统资源,完成文件读/写的动作。
FileInputStream可以使用 read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。
范例14.5是使用FileInputStream与FileOutputStream的一个例子。程序可以复制文件,它会先从来源文件读取数据至一个byte数组中,然后再将byte数组的数据写入目的文件。
ü 范例14.5 FileStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class FileStreamDemo {
public static void main(String[] args) {
try {
byte[] buffer = new byte[1024];
// 来源文件
FileInputStream fileInputStream =
new FileInputStream(new File(args[0]));
// 目的文件
FileOutputStream fileOutputStream =
new FileOutputStream(new File(args[1]));
// available()可取得未读取的数据长度
System.out.println("复制文件:" +
fileInputStream.available() + "字节");
while(true) {
if(fileInputStream.available() < 1024) {
// 剩余的数据比1024字节少
// 一位一位读出再写入目的文件
int remain = -1;
while((remain = fileInputStream.read())
!= -1) {
fileOutputStream.write(remain);
}
break;
}
else {
// 从来源文件读取数据至缓冲区
fileInputStream.read(buffer);
// 将数组数据写入目的文件
fileOutputStream.write(buffer);
}
}
// 关闭流
fileInputStream.close();
fileOutputStream.close();
System.out.println("复制完成");
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println(
"using: java FileStreamDemo src des");
e.printStackTrace();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
程序中示范了两个read()方法,一个方法可以读入指定长度的数据至数组,另一个方法一次可以读入一个字节。每次读取之后,读取的光标都会往前进,如果读不到数据则返回–1,使用available()方法获得还有多少字节可以读取。除了使用File来建立FileInputStream、 FileOutputStream的实例之外,也可以直接使用字符串指定路径来建立。
// 来源文件FileInputStream fileInputStream = new FileInputStream(args[0]);// 目的文件FileOutputStream fileOutputStream = new FileOutputStream(args[1]);在不使用文件流时,记得使用close()方法自行关闭流,以释放与流相依的系统资源。一个执行的结果范例如下,它将FileDemo.java复制为FileDemo.txt:
java onlyfun.caterpillar.FileStreamDemo FileDemo.java FileDemo.txt
复制文件:1723字节
复制完成
FileOutputStream默认会以新建文件的方式来开启流。如果指定的文件名称已经存在,则原文件会被覆盖;如果想以附加的模式来写入文件,则可以在构建FileOutputStream实例时指定为附加模式。例如:
FileOutputStream fileOutputStream = new FileOutputStream(args[1], true);构建方法的第二个append参数如果设置为true,在开启流时如果文件不存在则会新建一个文件,如果文件存在就直接开启流,并将写入的数据附加至文件末端。
虽然我一向不喜欢使用过长的范例来作程序示范(也不喜欢看很长的范例),不过本章的范例与其他各章的比起来相对长了一些,我会在程序中多用注释解释程序的逻辑。因为解释输入/输出操作最好的方式,是呈现一个具实用性的范例,本章的范例除了练习的作用之外,日后需要某些输入/输出功能时,也可以来参考看看如何实现。
14.2.3 BufferedInputStream和BufferedOutputStream
在介绍FileInputStream和 FileOutputStream的例子中,使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度。为了减少对硬盘的存取,通常从文件中一次读入一定长度的数据,而写入时也是一次写入一定长度的数据,这可以增加文件存取的效率。
java.io.BufferedInputStream 与java.io.BufferedOutputStream可以为InputStream、OutputStream类的对象增加缓冲区功能。构建 BufferedInputStream实例时,需要给定一个InputStream类型的实例,实现BufferedInputStream时,实际上最后是实现InputStream实例。同样地,在构建BufferedOutputStream时,也需要给定一个OutputStream实例,实现 BufferedOutputStream时,实际上最后是实现OutputStream实例。
BufferedInputStream的数据成员buf是一个位数组,默认为2048字节。当读取数据来源时,例如文件,BufferedInputStream会尽量将buf填满。当使用read ()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取。当buf中的数据不足时,BufferedInputStream才会再实现给定的InputStream对象的read()方法,从指定的装置中提取数据,如图14-2所示。
图14-2 BufferedInputStream在内部有buf成员作为缓冲区
BufferedOutputStream的数据成员buf是一个位数组,默认为512字节。当使用write()方法写入数据时,实际上会先将数据写至buf中,当buf已满时才会实现给定的 OutputStream对象的write()方法,将buf数据写至目的地,而不是每次都对目的地作写入的动作。
下面将范例14.5做个改写,这次不用自行设置缓冲区,而使用BufferedInputStream和BufferedOutputStream让程序看来简单一些,也比较有效率。
ü 范例14.6 BufferedStreamDemo.java
package onlyfun.caterpillar;
import java.io.*;
public class BufferedStreamDemo {
public static void main(String[] args) {
try {
byte[] data = new byte[1];
File srcFile = new File(args[0]);
File desFile = new File(args[1]);
BufferedInputStream bufferedInputStream =
new BufferedInputStream(
new FileInputStream(srcFile));
BufferedOutputStream bufferedOutputStream =
new BufferedOutputStream(
new FileOutputStream(desFile));
System.out.println("复制文件:" +
srcFile.length() + "字节");
while(bufferedInputStream.read(data) != -1) {
bufferedOutputStream.write(data);
}
// 将缓冲区中的数据全部写出
bufferedOutputStream.flush();
// 关闭流
bufferedInputStream.close();
bufferedOutputStream.close();
System.out.println("复制完成");
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println(
"using: java UseFileStream src des");
e.printStackTrace();
}
catch(IOException e) {
e.printStackTrace();
}
}
}
为了确保缓冲区中的数据一定被写出至目的地,建议最后执行flush()将缓冲区中的数据全部写出目的流中。这个范例的执行结果与范例14.5是相同的。
BufferedInputStream和 BufferedOutputStream并没有改变InputStream或 OutputStream的行为,读入或写出时的动作还是InputStream和OutputStream负责。 BufferedInputStream和BufferedOutputStream只是在操作对应的方法之前,动态地为它们加上一些额外功能(像缓冲区功能),在这里是以文件存取流为例,实际上可以在其他流对象上也使用BufferedInputStream和BufferedOutputStream 功能。
*************************************************************************************
本篇主要分析:1.如何将byte数组适配至ByteArrayInputStream,对应与IO部分的适配器模式;2.BufferedInputStream的工作原理,对应于IO的装饰器模式,会首先研究InputStream和FilterInputStream的源代码,同时会将要谈谈软件设计中的缓存相关的知识。后面专门一章分析PipedInputStream和PipedOutStream,简单谈谈管道相关的知识,以及软件架构的想法。
1 InputStream
InputStream 是输入字节流部分,装饰器模式的顶层类。主要规定了输入字节流的公共方法。
package java.io;
public abstract class InputStream implements Closeable {
private static final int SKIP_BUFFER_SIZE = 2048; //用于skip方法,和skipBuffer相关
private static byte[] skipBuffer; // skipBuffer is initialized in skip(long), if needed.
public abstract int read() throws IOException; //从输入流中读取下一个字节,
//正常返回0-255,到达文件的末尾返回-1
//在流中还有数据,但是没有读到时该方法会阻塞(block)
//Java IO和New IO的区别就是阻塞流和非阻塞流
//抽象方法哦!不同的子类不同的实现哦!
//将流中的数据读入放在byte数组的第off个位置先后的len个位置中
//放回值为放入字节的个数。
public int read(byte b[], int off, int len) throws IOException { //
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
} //检查输入是否正常。一般情况下,检查输入是方法设计的第一步
int c = read(); //读取下一个字节
if (c == -1) { return -1; } //到达文件的末端返回-1
b[off] = (byte)c; //放回的字节downcast
int i = 1; //已经读取了一个字节
try {
for (; i < len ; i++) { //最多读取len个字节,所以要循环len次
c = read(); //每次循环从流中读取一个字节
//由于read方法阻塞,
//所以read(byte[],int,int)也会阻塞
if (c == -1) { break; } //到达末尾,理所当然放回-1
b[off + i] = (byte)c; //读到就放入byte数组中
}
} catch (IOException ee) { }
return i;
//上面这个部分其实还有一点比较重要,int i = 1;在循环的外围,或许你经常见到,
//或许你只会在循环是才声明,为什么呢?
//声明在外面,增大了变量的生存周期(在循环外面),所以后面可以return返回
//极其一般的想法。在类成员变量生命周期中使用同样的理念。
//在软件设计中,类和类的关系中也是一样的。
} //这个方法在利用抽象方法read,某种意义上简单的Templete模式。
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
} //利用上面的方法read(byte[] b)
public long skip(long n) throws IOException {
long remaining = n; //方法内部使用的、表示要跳过的字节数目,
//使用它完成一系列字节读取的循环
int nr;
if (skipBuffer == null)
skipBuffer = new byte[SKIP_BUFFER_SIZE]; //初始化一个跳转的缓存
byte[] localSkipBuffer = skipBuffer; //本地化的跳转缓存
if (n <= 0) { return 0; } //检查输入参数,应该放在方法的开始
while (remaining > 0) { //一共要跳过n个,每次跳过部分,循环
nr = read(localSkipBuffer, 0, (int) Math.min(SKIP_BUFFER_SIZE, remaining));
//利用上面的read(byte[],int,int)方法尽量读取n个字节
if (nr < 0) { break; } //读到流的末端,则返回
remaining -= nr; //没有完全读到需要的,则继续循环
}
return n - remaining;//返回时要么全部读完,要么因为到达文件末端,读取了部分
}
public int available() throws IOException { //查询流中还有多少可以读取的字节
return 0;
}
//该方法不会block。在java中抽象类方法的实现一般有以下几种方式:
//1.抛出异常(java.util);2.“弱”实现。象上面这种。子类在必要的时候覆盖它。
//3.“空”实现。下面有例子。
public void close() throws IOException {}
//关闭当前流、同时释放与此流相关的资源
public synchronized void mark(int readlimit) {}
//在当前位置对流进行标记,必要的时候可以使用reset方法返回。
//markSupport可以查询当前流是否支持mark
public synchronized void reset() throws IOException {
throw new IOException("mark/reset not supported");
}
//对mark过的流进行复位。只有当流支持mark时才可以使用此方法。
//看看mark、available和reset方法。体会为什么?!
public boolean markSupported() { //查询是否支持mark
return false;
} //绝大部分不支持,因此提供默认实现,返回false。子类有需要可以覆盖。
}
2 FilterInputStream
这是字节输入流部分装饰器模式的核心。是我们在装饰器模式中的Decorator对象,主要完成对其它流装饰的基本功能。下面是它的源代码:
package java.io;
//该类对被装饰的流进行基本的包裹。不增加额外的功能。
//客户在需要的时候可以覆盖相应的方法。具体覆盖可以在ByteInputStream中看到!
public class FilterInputStream extends InputStream {
protected volatile InputStream in; //将要被装饰的字节输入流
protected FilterInputStream(InputStream in) { //通过构造方法传入此被装饰的流
this.in = in;
}
//装饰器的代码特征:被装饰的对象一般是装饰器的成员变量
//上面几行可以看出。
//下面这些方法,完成最小的装饰――0装饰,只是调用被装饰流的方法而已
public int read() throws IOException {
return in.read();
}
public int read(byte b[]) throws IOException {
return read(b, 0, b.length);
}
public int read(byte b[], int off, int len) throws IOException {
return in.read(b, off, len);
}
public long skip(long n) throws IOException {
return in.skip(n);
}
public int available() throws IOException {
return in.available();
}
public void close() throws IOException {
in.close();
}
public synchronized void mark(int readlimit) {
in.mark(readlimit);
}
public synchronized void reset() throws IOException {
in.reset();
}
public boolean markSupported() {
return in.markSupported();
}
//以上的方法,都是通过调用被装饰对象in完成的。没有添加任何额外功能
//装饰器模式中的Decorator对象,不增加被装饰对象的功能。
//它是装饰器模式中的核心。更多关于装饰器模式的理论请阅读博客中的文章。
}
相关推荐
当我们需要从文件中读取数据时,可以使用`FileInputStream`类,它是`InputStream`的一个子类,专门用于读取文件内容。创建一个`FileInputStream`对象,只需要传入一个`File`对象即可: ```java File file = new ...
InputStream OutputStream Reader Writer FileInputStream FileOutputStream FileReader FileWriter InputStreamReader OutputStreamWriter BufferedReader BufferedWriter PrintWriter ...
在Java中,它们是Java.io包下的基础类,提供了多种子类来处理不同类型的输出和输入操作,如`FileOutputStream`和`FileInputStream`用于文件操作,`SocketOutputStream`和`SocketInputStream`用于网络通信等。...
`InputStream`、`OutputStream`、`Reader`和`Writer`是Java IO流的基础类,它们提供了读写文件的基本接口。本篇文章将深入讲解这些类的用法,并提供实际的代码示例,帮助初学者更好地理解和应用。 1. `InputStream`...
InputStream is = new FileInputStream(file1);// 打开文件 byte[] cont = new byte[(int) file1.length()]; is.read(cont);// 读取文件 for (int i = 0; i ; i++) { System.out.print((char) ...
分析Java常用IO流,包括InputStream、OutputStream、FileInputStream、FileOutputStream、BufferedInputStream、BufferedOutputStream、Reader、Writer、InputStreamReader、OutputStreamWriter、FileReader、...
IO流文档InputStream / OutputStream IO流是一种基本的数据交换方式,在Java中,IO流是通过输入流(InputStream)和输出流(OutputStream)来实现的。输入流用于读取数据,而输出流用于写入数据。 InputStream是...
在描述中提到的文件复制功能,就是通过创建FileInputStream实例,读取源文件的数据,然后利用OutputStream的子类,如FileOutputStream,将这些数据写入目标文件。这一过程通常涉及read()方法来获取单个字节或多个...
FileInputStream 是文件输入流,它继承于InputStream。 通常,我们使用FileInputStream从某个文件中获得输入字节。 FileOutputStream 是文件输出流,它继承于OutputStream。 通常,我们使用FileOutputStream 将数据...
reload4j-1.2.25.jar重写了DailyRollingFileAppender中关于日志重命名部分 boolean copy(File src, File dst) throws IOException { try { InputStream in = new FileInputStream(src); OutputStream out = new ...
InputStream 是 Java 中的输入流接口,用于读取文件的内容,常用的 InputStream 子类包括 FileInputStream、BufferedInputStream、DataInputStream 等。FileInputStream 是 InputStream 的子类,用于打开一个输入...
InputStream OutputStream FileInputStream FileOutputStream DataInputStream DataOutputStream BufferedInputStream BufferedOutputStream ObjectInputStream ObjectOutputStream ByteArrayInputStream ...
在Java中,字节流分为输入流(InputStream)和输出流(OutputStream),用于读取和写入字节数据。本课程由IT十八掌徐培成讲解,重点介绍了Java基础中的字节流操作,特别是`FileInputStream`和`FileOutputStream`这两...
java各类IO流的整理demo,其内包含InputStream/OutputStream BufferedReader流 FileOutputStream/FileInputStream流 PrintWriter流 DataOut/DataIn流 Object流 FileChannel类;
InputStream inputStr = new FileInputStream(epubPath); book = epubReader.readEpub(inputStr); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e....
Java中的`FileInputStream`和`FileOutputStream`是用于处理文件输入和输出操作的基本类,它们分别继承自`InputStream`和`OutputStream`。这两个类在Java I/O系统中扮演着核心角色,提供了对文件进行字节级读写操作的...
在Java中,读取文本文件通常涉及到使用`InputStream`及其子类如`FileInputStream`,以及`Reader`接口的实现类`BufferedReader`。以下是对读取操作的详细解释: #### 方法一:读取到`StringBuffer` - **实现方法**...
例如,当你需要从硬盘、网络或者内存中的字节数据源读取数据时,通常会用到InputStream的子类,如FileInputStream,它专门用于从文件中读取数据。 FileInputStream 是 InputStream 的一个具体实现,主要用于从本地...
FileInputStream和FileOutputStream是InputStream和OutputStream的子类,分别用于读取和写入文件。它们提供了一些基本的方法,如read()方法,可以读取文件的内容。write()方法,可以将数据写入到文件中。 ...
OutputStream fos = new FileOutputStream(file); properties.store(fos, null); fos.flush(); is.close(); ``` 这段代码首先加载Properties文件,然后设置新的键值对,并保存回文件,覆盖原有内容。 d. 删除...