数据流是指一组有顺序的、有起点和终点的字节集合。流被组织成不同的层次,如下图所示。按照最粗略的分法,数据流可以分为输入数据流(input stream)和输出数据流(output stream)。输入数据流只能读不能写,而输出数据流只能写不能读。显而易见,从数据流中读取数据时,从数据流中读取数据时,必须有一个数据源与该数据流相连。
在java开发环境中,java.io包为用户提供了几乎所有常用的数据流,因此在所有涉及数据流操作的程序中几乎都会在程序的最前面出现语句:
import Java.io. *;
从而使用这些由环境本身提供的数据流类。
在JKD1.1之前,java.io包中的流只有普通的字节流,即以byte为基本处理单位的流,这种流以16位的Unicode码表示的字符流处理很不方便。
从JDK1.1开始,java.io包中加入了专门用于字符流处理的类,这是以Reader和Writer为基础派生的一系列的类。
另外,为了使对象的状态能够方便的永久保存下来,JDK1.1以后的java.io包中提供了以字节流为基础的用于对象永久保存状态的机制,他们通过实现ObjectInput和ObjectOutput接口来实现。
1.输入数据流
只读不写的数据流,用于向计算机输入信息而用。
java.io包中所有输入数据流都是由抽象类InputStream继承而来的,并且实现了其中的所有方法,包括读取数据、标记位置、重置读写指针、获取数据量等。从数据流中读取数据时,必须有一个数据源与该数据流相连。
输入数据流中提供的主要的数据操作方法有:
1. int read();
2. int read(byte[] b);
3. int read(byte[] b , int off , int len);
4. void close();
5. int available();//数据流中读取的字节数
6. long skip(long l);//跳过不读的字节数
7. boolean markSupported();
8. void mark(int markarea);//标记划出一个缓冲区
9. void reset();
第一个方法,它从输入流中读取一个字节的二进制数据,然后以此数据为低位字节,配上一个全零字节,形成一个0~255之间的整数返回。它是一个抽象方法,需要在子类中具体实现。当输入流读取结束时,它会得到-1,以标志数据流的结束。
第二个方法,将多个字节读到数组中,填满整个数组。
第三个方法,从输入流中读取长度为len的数据,从数组b中索引为off的位置开始放置读入的数据,读完返回读取的字节数。
以上三个方法返回为-1,都表示数据流的结束。
2.输出数据流
只写不读的数据流,用于向计算机输出信息而用。
java.io包中所有输入数据流都是由抽象类InputStream继承而来的,并且实现了其中的所有方法。
输入数据流中提供的主要的数据操作方法有:
1. void write(int i);
2. void write(byte[] b);
3. void write(byte[] b , int off , int len);
4. void close();
5. void flush();//在目前通用的存储介质中内存访问的速度是迅速的,因此,为加快数据的传输速度,提高数据的传输效率,有时输出数据流会在提交数据之前把所要输出的数据先锁定在内存缓冲区中,然后成批地进行输出,每次传输过程都以某特定数据长度为单位进行传输。这种方式下,在数据的末尾一般都会有一部分数据由于数量不够一个批次,而存留在缓冲区里,用方法flush()则可以将这部分数据强制提交。
3.基本字节数据流
上面提到InputStream和OutputStream两个类都是抽象类。抽象类不能实例化,因此在实际应用中经常用到的并不是这两个类,而是一系列基本数据流类。它们都是InputStream和OutputStream的子类,在实现父类方法的同时又都定义了自己特有的功能,下面介绍一些常用的基本的数据流。
3.1 文件数据流
包括FileOutputSteam 和 FileInputStream,这两个类进行文件的I/O处理,其数据源和数据终点都应该是文件。但是它们不支持方法mark()和reset()。在构造方法时可直接给出文件名:
FileInputStream fis = new FileInputStream(“myFile”);
这样便把文件myFile作为该数据流的数据源。
同样
FileOutputStream out= new FileOutputStream ("myFile");
out.write('H');
out.write(69);
out.write(76);
out.write('L');
out.write('O');
out.write('!');
out.close();
这样,在myFile.dat文件中会保存字符串HELLO!
使用文件数据流进行I/O操作时,对于类FileInputStream的实例对象,如果所指定的文件不存在,则产生FIleNotFoundException异常,由于它是非运行时异常,因此必须加以捕获或声明。对于类FileOutputStream的实例对象,如果所制定的文件不存在,则系统创建一个新文件;如果文件存在,则新写入的内容将覆盖原有的数据。如果在读写时生成新文件发生错误,则产生IOException异常,也需要程序员捕获并处理。
举个栗子:
public class FileOutputStreamTest {
public static void main(String[] args){
try {
FileOutputStream out = new FileOutputStream("myFile.dat");
out.write('H');
out.write(69);
out.write(76);
out.write('L');
out.write('O');
out.write('!');
out.close();
} catch (FileNotFoundException e) {
System.out.println("Error: Cannot open file for writing.");
} catch (IOException e) {
System.out.println("Error: Cannot write to file.");
}
}
}
可以使用FileInputStream来读取FileOutputStream输出的数据,举个栗子:
public class FileInputStreamTest {
public static void main(String[] args){
try {
FileInputStream in = new FileInputStream("myFile.dat");
while (in.available()>0){
System.out.print(in.read()+" ");
}
in.close();
} catch (FileNotFoundException e) {
System.out.println("Error: Cannot open file for reading.");
} catch (IOException e) {
System.out.println("Error: Cannot read from file.");
}
}
}
执行结果:
3.2 过滤流
1.缓冲区数据流
缓冲区数据流BufferedInputStream和BufferedOutputStream,它们都属于过滤器数据流,都在数据流上增加了一个缓冲区。当读写数据时,数据以块为单位先进入缓冲区(快的大小可先进行设置),其后的读写操作则作用于缓冲区。由于采用这个办法降低了不同硬件设备之间的差异,提高了I/O操作的效率,对于有大量I/O操作的程序具有非常重要的意义。与此同时,这两个流还提供了对mark()、reset()、skip()等方法的支持。
在创建该类的实例对象时,有两种方法可以使用,一种是取默认的缓冲区的大小,如:
FileInputStream fis = new FileInputStream("myFile");
InputStream is = new BufferedInputStream(fis);
FileOutputStream fos = new FileOutputStream("myFile");
OutputStream os = new BufferedOutputStream(fos);
另一种是自行设置缓冲区的大小:
FileInputStream fis = new FileInputStream("myFile");
InputStream is = new BufferedInputStream(fis,1024);
FileOutputStream fos = new FileOutputStream("myFile");
OutputStream os = new BufferedOutputStream(fos,1024);
要注意的是,一般在关闭一个缓冲区输出流之前,应该使用flush()方法,强制输出剩余的数据,以确保缓冲区域内的所有数据全部写入输出流。
2.数据数据流
在前面所提到的数据流中,处理的都是指字节或字节数组,这是进行数据传输时系统默认的数据类型。但实际上有许多时候,所处理的并非只是这两种类型,遇到这种情况时就要应用一种专门的数据来处理。DataInputStream和DataOutputStream就是这样的两个过滤器数据流,它们允许通过数据流来读写Java原始类型,包括布尔型、浮点型等。它们的创建方式如下(这里假设is和os是前面已经创建的输入输出流对象);
DataInputStream dis = new DataInputStream(is);
DataOutputStream dos = new DataOutputStream(os);
3.管道数据流
管道数据流主要用于线程间的通信,一个线程中的PipedInputStream对象从另一个线程中互补的PipedOutputStream对象中接收输入,所以类PipedInputStream必须和PipedOutputStream一起使用来建立一个通信通道。也就是说,管道数据流必须同时具备可用的输入端和输出端。
创建一个通信通道可以按照下面的三个步骤进行:
(1)建立输入数据流
PipedInputStream pis = new PipedInputStream();
(2)建立输出数据流
PipedOutputStream pos = new PipedOutputStream();
(3)将输入数据和输出数据流连接起来
pis.connect(pos);
或者 pos.connect(pis);
还有另一种有参构造
PipedInputStream pis = new PipedInputStream();
PipedOutputStream pos = new PipedOutputStream(pis);
或者
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
举个栗子:
public class FileInputStreamTest {
public static void main(String[] args)throws IOException{
PipedOutputStream pos = new PipedOutputStream();
PipedInputStream pis = new PipedInputStream(pos);
byte datamover = 0;
System.out.println("\nNow I start to work...\n");
try{
System.out.println("transfer"+datamover+"to pos.\n");
pos.write(datamover);
System.out.println("pis get:"+(byte)pis.read());
}finally {
pis.close();
pos.close();
}
}
}
4.对象流
能够输入输出对象的流称为对象流。
Java中的数据流不仅能对基本的数据类型的数据进行操作,而且也提供了把对象写入文件数据流或从文件数据流中读出的功能,这一功能通过java.io包中的ObjectInputStream和ObjectOutputStream两个类实现的。
1、写对象数据流
举个栗子:
public static void main(String[] args)throws IOException{
java.util.Date d = new java.util.Date();
FileOutputStream f = new FileOutputStream("date.ser");
ObjectOutputStream s = new ObjectOutputStream(f);
s.writeObject(d);
s.close();
}
2、读对象数据流
Date d1 = null;
FileInputStream fis = new FileInputStream("date.ser");
ObjectInputStream os = new ObjectInputStream(fis);
try {
d1 = (Date) os.readObject();
s.close();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println("Date serialized at" +d1);