Java中的字节流是以字节(Byte)为基本处理单位的,用于对二进制数据进行读写操作。
对应输入字节流和输出字节流的两个顶层的抽象类分别是InputStream和Output- Stream。
InputStream表征字节输入流。因此,它定义了—套所有输入流都需要的方法。这些方法列在表11-1中,没带参数。
表11-1 InputStream类中的方法
方 法 |
说 明 |
read ( ) |
从流中读取数据 |
skip ( ) |
跳过流中的若干字节 |
available ( ) |
返回当前流中可用的字节数 |
mark ( ) |
在流中标记一个位置 |
reset ( ) |
返回标记过的位置 |
markSupported ( ) |
返回一个表示流是否支持标记和复位操作的布尔值 |
close ( ) |
关闭这个流 |
方法read( )在这个类中被重载,它提供了3种从流读取数据的方法,定义如下。
int read()
int read(byte b[])
int read(byte b[],int off,int len)
第一种形式的read( )只简单地从流中读单字节作为整数;若没有数据读取就返回-1。第二种形式从流中读取数据并存放到数组b[ ]中,同时返回读取到的字节数;若没有数据便返回-1。第三种形式也是读取数据到字节数组,但它能在数组中指定一个单元开始存贮字符,同时标识所读字符的最大数目。
其余方法的说明如下。
long skip(long n)
int available()
void mark(int readlimit)
void reset()
boolean markSupported()
void close()
与InputStream类相对应的类是OutputStream。它表征字节输出流。
OutputStream类中定义的方法及其描述如表11-2所示。
表11-2 OutputStream类中的方法
方 法 |
说 明 |
write ( ) |
将数据写入流中 |
flush ( ) |
强制写入缓冲的输出 |
close ( ) |
关闭这个流 |
和Inputstream类的read( )方法一样,OutputStream类的方法write( )有如下几种形式。
void write(int b);
void write(byte[]);
void write(byte[],int off,int len);
第一种形式只简单地写一个字节到流;第二种形式写所给数组中包含的全部字节到流;第三种形式则从字节数组写数据,并能指定开始写的单元及写的字节数。
通常所使用的文件有很多是二进制文件,它们以字节作为数据处理单位。对这些文件就要使用字节流来读/写,其实文本文件也可以用字节流来进行读/写。FileInputStream和FileOutputStream类分别完成字节流文件的输入和输出。
FilelnputStream类的主要方法如下。
· int available( ):返回可读入的字节数。
· void close( ):关闭输入流,并释放任何与该流有关的资源。
· protected void finalize( ):当读到无用信息时,关闭该流。
· FileDescriptor get FD( ):返回与该流有关的文件描述符(即文件的完整路径)。
· int read( ):从输入流中读取一个字节的数据。
· int read(byte[ ]b):将数据读入到一个字节数组b[ ]中。
· int read(byte[ ]b,int off,int len):将len个字节的数据读入到一个字节型数组b[ ]中,从下标off 开始存放,并返回实际读入的字节数。
· long skip(long n):跳过输入流上的n个字节。
FileOutputStream类的主要方法如下。
· void close( ):关闭输出流,并释放任何与该流有关的资源。
· void finalize( ):当写到无用信息时,关闭该流。
· void write(int b):将字节数据b[ ]写到输出流中。
· void write(byte[ ]b:将一个字节数组b[ ]中的数据写到输出流中。
· long skip(long n):跳过输出流上的n个字节。
【例11-1】 建立两个文件file1.txt和file2.txt,对文件file1.txt输入内容“Hello World!”,并将其内容复制给file2.txt。
import java.io.*;
class FilesCopy
{
public static void main(String[] args)
{
try
{
FileOutputStream fos1 = new FileOutputStream("file1.txt");
//创建FileOutputStream 对象
int b;
System.out.println("请输入字符串:");
while( ( b =System.in.read( ) ) != '\n')
fos1.write(b); //将字符写入到输出流中
fos1.close(); //关闭输出流
FileInputStream fis = new FileInputStream("file1.txt");
//创建FileInputStream 对象
FileOutputStream fos = new FileOutputStream("file2.txt");
//创建FileOutputStream 对象
int c;
while( (c = fis.read() ) != -1)
fos.write(c); //将一个字节写入到输出流中
fis.close(); //关闭输入流
fos.close(); //关闭输出流
System.out.println("成功保存并复制!");
}
catch(FileNotFoundException e)
{
System.out.println("FileStreamsTest: "+e);
}
catch(IOException e)
{
System.out.println("FileStreamsTest: "+e);
}
}
}
数据字节输入/输出流类(DataInputStream/DataOutputStream)提供了面向Java基本数据类型的高层次界面。对于整型,在DataOutputStream类中有一个相应的writeInt( )方法,在DataInputStream类中有一个readInt( )方法。
注意:由于这两个流类中的所有方法都声明为final,因此,为了安全性,在派生类中不能改变它们,并且和其他的输入/输出方法一样,它们抛出一个异常。
对于数据字节输入/输出流类,要求做某种解释的一个方面是处理字符串和字符数据。从内部讲,Java以一种由国际统一代码标准协会(Unicode Consortium)定义的格式来表示面向文本的数据。每一个Unicode码用两个字节表示,并按照标准提供足够大的字符范围,以包括美国、欧洲、中东、非洲、印度、亚洲和太平洋地区的主要书面语言中的字符。
16位Unicode码的长度是8位ASCII码的两倍。在实际运用中,对于字符数据的表示,Java提供了8位ASCⅡ码和16位Vnicode码之间的转换。下面给出两个流类的部分描述。
public class DataInputStream…
{
public DataInputStream (DataInputStream in) ;
public final void readBoolean(boolean b) throws IOException;
public final void readChar(int c) throws IOException;
public final void readDouble(double d) throws IOException;
public final void readChar(int i) throws IOException;
public final void readUTF(String s) throws IOException;
}
public class DataOutputStream…
{
public DataOutputStream(OutputStream out) ;
public final void writeBoolean(boolean b) throws IOException;
public final void writeChar(int c) throws IOException;
public final void writeDouble(double d) throws IOException;
public final void writeChar(int i) throws IOException;
public final void writeUTF(String s) throws IOException;
}
可以很灵活地运用数据字节输入/输出流类,因为它们可以分别用任意的OutputStream对象和InputStream对象来组合。
Java提供了一个功能强大的机制,通过两个类对象输入/输出类(ObjectOutputStream和ObjectInputStream)来写入和读取对象。从这两个类的名字中可以看出,它们在流的基础上操作;将对象顺序写入流并按相同的顺序从流中读取。这些类的强大之处在于:流包含的是对象而不是字节或字符或原始类型。更重要的是,可以利用对象输入/输出机制来写入和读取任何用户自定义类中的对象,对象输入/输出机制并不只限于预定义的类或类型。当将一个对象写到一个对象流中时,对象流就保存与该对象相关的所有数据,即公有的和私有的数据都被保存,原始类型的属性也被保存,就像本身是其他用户自定义类的属性一样。
Java对象流类尽管功能强大,其实也并不复杂。下面程序段中定义的两个对象输入/输出类其简单性是显而易见的,在每个类中只需要用两个方法来写入和读取对象。这两个类的构造函数将一个简单的OutputStream或者InputStream作为参数。通常,要为此创建一个与介质相关的流。
public class ObjectOutputStream
{
public ObjectOutputStream(OutputStream out)throws IOException;
public void writeObject(Object obj) throws IOException;
}
public class ObjectInputStream
{
public ObjectInputStream(InputStream in) throws IOException,
StreamCorruptedException;
public Object readObject( ) throws IOException,ClassNorFoundException;
}
除了IOException以外,ObjectInputStream类抛出两个其他的异常。构造函数抛出StreamCorruptedException。这个异常所发出的信号指出,对象流机制并没有将给它的InputStream识别为由一个ObjectInputStream对象所产生的流,从InputStream中读取的对象并不对应于这个程序所知道的任何类。这个异常说明了产生对象流的系统和读取对象流的系统没有与包含在流中的对象的类一致。
对利用对象流写入和读取其对象的类的唯一要求是:该类实现Serializable(可串行的)界面。下面的程序段给出的Serializable界面没有任何方法,这样的界面有时称为“标记”(marker)界面。对象流机制将检查正在写入到流中的对象是否实现Serializable界面。只有实现Serializable界面的那些类才被写入到流中。
public abstract interface Serializable
{
// no methods -&"maker"interface
}
存在两个条件,可以让一个类的设计都有意地选择使该类的对象是不可串行的。这两个条件与Java对象在高度分布的环境(如WWW)中的使用相关,在这种环境下,可串行的对象可以很容易地在计算机之间传输。第一个条件是关于安全性的。通过不实现Serializable界面,一个包含敏感数据的类可以对该类的对象维持一定的安全性,因为这些对象是不可传输的。第二个条件是关于功能的。当一个对象被串行化时,只有类的数据(而不是类的代码)被写到对象流。因此,从流中读取对象的程序必须已经拥有该对象的代码。在分布式环境中,如果存在一种不确定性,即类的代码可能无法在其他的计算机上出现,那么该类通过不实现Serializable界面就可以阻止它的对象迁移。