字节流类为处理字节式输入/输出提供了丰富的环境。
一个字节流可以和其他任何类型的对象并用,包括二进制数据。
这样的多功能性使得字节流对很多类型的程序都很重要。
字节流类以InputStream 和OutputStream为顶层。
一、InputStream(输入流)
InputStream 是一个定义了Java 流式字节输入模式的抽象类
该类的所有方法在出错条件下引发一个IOException 异常
- //返回当前可读的输入字节数
- int available( )
- //关闭输入源。关闭之后的读取会产生IOException异常
- void close( )
- //如果下一个字节可读则返回一个整型,遇到文件尾时返回-1
- int read()
- //试图读取buffer.length个字节到buffer中,并返回实际成功读取的字节数。遇到文件尾时返回-1
- int read(byte buffer[ ])
- //重新设置输入指针到先前设置的标志处
- void reset( )
- //在输入流的当前点放置一个标记。该流在读取numBytes个字节前都保持有效
- void mark(int numBytes)
- //如果调用的流支持mark( )/reset( )就返回true
- boolean markSupported( )
- //忽略numBytes个输入字节,返回实际忽略的字节数
- ong skip(long numBytes)
二、OutputStream(输出流)
OutputStream是定义了流式字节输出模式的抽象类。
该类的所有方法返回一个void 值 并且在出错情况下引发一个IOException异常。
- //关闭输出流。关闭后的写操作会产生IOException异常
- void close( )
- //定制输出状态以使每个缓冲器都被清除,也就是刷新输出缓冲区
- void flush( )
- //向输出流写入单个字节。注意参数是一个整型数,它允许你不必把参数转换成字节型就可以调用write()
- void write(int b)
- //向一个输出流写一个完整的字节数组
- void write(byte buffer[ ])
- //写数组buffer以buffer[offset]为起点的numBytes个字节区域内的内容
- void write(byte buffer[ ], int offset, int numBytes)
三、FileInputStream(文件输入流)
FileInputStream 类创建一个能从文件读取字节的InputStream 类,它们都能引发FileNotFoundException 异常
- FileInputStream(String filepath)
- FileInputStream(File fileObj)
filepath 是文件的全称路径,fileObj是描述该文件的File对象。
下面的例子创建了两个使用同样磁盘文件且各含一个上述构造函数的FileInputStreams类:
- public static void main(String[] args) {
- try {
- FileInputStream stream=new FileInputStream("D:/test/txt");
- FileInputStream stream2=new FileInputStream(new File("D:/test/txt"));
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
↑↑↑尽管第一个构造函数可能更常用到,第二个构造函数允许在把文件赋给输入流之前用File 方法更进一步检查文件。
当一个FileInputStream 被创建时,它可以被公开读取。FileInputStream重载了抽象类InputStream 的六个方法,mark( ) 和reset( ) 方法不被重载,任何关于使用FileInputStream的reset() 尝试都会生成IOException异常。 |
↓↓↓下面的例题说明了怎样读取单个字节、字节数组以及字节数组的子界。它同样阐述了怎样运用available( ) 判定剩余的字节个数及怎样用skip( ) 方法跳过不必要的字节。该程序读取它自己的源文件:
- public static void main(String[] args) {
- int size;
- try {
- InputStream f = new FileInputStream("D:/test.txt");
- System.out.println("\n当前可读的输入字节数:" + (size = f.available()));
- // 读取单个字节
- System.out.print("读取前文件中的前两个字节:");
- for (int i = 0; i < 2; i++) {
- System.out.print((char) f.read());
- }
- System.out.println();
- System.out.println("\n当前可读的输入字节数:" + (size = f.available()));
- // 读取字节数组
- byte b[] = new byte[10];
- if (f.read(b) != 10) {
- System.out.println("不能读取10字节");
- }
- ;
- System.out.println(new String(b, 0, 10));
- System.out.println("\n当前可读的输入字节数:" + (size = f.available()));
- // 使用用skip()方法
- System.out.println("执行skip(2)跳跃两个字节");
- f.skip(2);
- System.out.println("\n当前可读的输入字节数:" + (size = f.available()));
- if (f.read(b, 0, 5) != 5) {
- System.err.println("不能读取5字节");
- }
- System.out.println(new String(b, 0, 5));
- System.out.println("\n当前可读的输入字节数:" + (size = f.available()));
- f.close();
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- // 输出结果
- // ↓↓↓
- // 当前可读的输入字节数:490
- // 读取前文件中的前两个字节:In
- //
- // 当前可读的输入字节数:488
- // the Orien
- //
- // 当前可读的输入字节数:478
- // 执行skip(2)跳跃两个字节
- //
- // 当前可读的输入字节数:476
- // young
- //
- // 当前可读的输入字节数:471
四、FileOutputStream(文件输出流)
FileOutputStream 创建了一个可以向文件写入字节的类OutputStream,它们可以引发IOException或SecurityException异常
- FileOutputStream(String filePath)
- FileOutputStream(File fileObj)
- FileOutputStream(String filePath, boolean append)
filePath是文件的全称路径,fileObj 是描述该文件的File 对象。如果append 为true ,文件以设置搜索路径模式打开。 |
FileOutputStream的创建不依赖于文件是否存在。在创建对象时FileOutputStream在打开输出文件之前创建它。这种情况下你试图打开一个只读文件,会引发一个IOException异常。
- public static void main(String[] args) {
- try {
- String source = "Now is the time for all good men\n"
- + " to come to the aid of their country\n"
- + " and pay their due taxes.";
- byte buf[] = source.getBytes();
- OutputStream f0;
- f0 = new FileOutputStream("D:/file1.txt");
- for (int i = 0; i < buf.length; i += 2) {
- f0.write(buf[i]);
- }
- f0.close();
- OutputStream f1 = new FileOutputStream("D:/file2.txt");
- f1.write(buf);
- f1.close();
- OutputStream f2 = new FileOutputStream("D:/file3.txt");
- f2.write(buf, buf.length - buf.length / 4, buf.length / 4);
- f2.close();
- } catch (FileNotFoundException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- // 输出结果
- // ↓↓↓
- // file1.txt内容:
- // Nwi h iefralgo e
- // t oet h i ftercuty n a hi u ae.
- // file2.txt内容:
- // Now is the time for all good men
- // to come to the aid of their country
- // and pay their due taxes.
- // file3.txt内容:
- // nd pay their due taxes.
五、ByteArrayInputStream(字节数组输入流)
ByteArrayInputStream是把字节数组当成源的输入流。
- ByteArrayInputStream(byte array[])
- ByteArrayInputStream(byte array[], int start, int numBytes)
↑↑↑array是输入源。第二个构造函数创建了一个InputStream 类,该类从字节数组的子集生成,以start 指定索引的字符为起点,长度由numBytes决定。
- public static void main(String[] args) {
- String string = "abcdefghijklmnopqrstuvwxyz";
- byte b[] = string.getBytes();
- // 包含整个字母表中小写字母
- ByteArrayInputStream stream = new ByteArrayInputStream(b);
- // 包含开始的十个字母。
- ByteArrayInputStream stream2 = new ByteArrayInputStream(b, 0, 10);
- }
ByteArrayInputStream实现mark( ) 和reset( ) 方法。然而,如果 mark( )不被调用,reset( )在流的开始设置流指针——该指针是传递给构造函数的字节数组的首地址。
下面的例子说明了怎样用reset( ) 方法两次读取同样的输入:
- public static void main(String[] args) {
- String string = "abcdefg";
- byte b[] = string.getBytes();
- ByteArrayInputStream stream = new ByteArrayInputStream(b);
- for (int i = 0; i < 2; i++) {
- int c;
- while ((c = stream.read()) != -1) {
- System.out.print(i == 0 ? ((char) c) : Character
- .toUpperCase((char) c));
- }
- stream.reset();
- System.out.println();
- }
- }
- // 输出结果
- // ↓↓↓
- // abcdefg
- // ABCDEFG
该例先从流中读取每个字符,然后以小写字母形式打印。然后重新设置流并从头读起,这次在打印之前先将字母转换成大写字母。
六、 ByteArrayOutputStream(字节数组输出流)
ByteArrayOutputStream 是一个把字节数组当作输出流的实现
- ByteArrayOutputStream( )
- ByteArrayOutputStream(int numBytes)
↑↑↑一个32 位字节的缓冲器被生成。第二个构造函数生成一个跟指定numBytes相同位数的缓冲器
缓冲器保存在ByteArrayOutputStream 的受保护的buf 成员里。缓冲器的大小在需要的情况下会自动增加。缓冲器保存的字节数是由ByteArrayOutputStream 的受保护的count域保存的。 |
- public static void main(String[] args) {
- try {
- ByteArrayOutputStream f = new ByteArrayOutputStream();
- String s = "abcdefghijklmnopqrstuvwxyz";
- byte buf[] = s.getBytes();
- f.write(buf);
- System.out.println(f.toString());
- for (int i = 0; i < buf.length; i++) {
- System.out.print((char) buf[i]);
- }
- System.out.println();
- byte b2[] = f.toByteArray();
- // 写入文件
- OutputStream f2 = new FileOutputStream("D:/test.txt");
- // writeTo( ) 这一便捷的方法将f 的内容写入test.txt
- f.writeTo(f2);
- f2.close();
- f.reset();
- for (int i = 0; i < 3; i++) {
- f.write('X');
- }
- System.out.println(f.toString());
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- // 输出结果
- // ↓↓↓
- // abcdefghijklmnopqrstuvwxyz
- // abcdefghijklmnopqrstuvwxyz
- // XXX
七、过滤字节流
过滤流(filtered stream )仅仅是底层透明地提供扩展功能的输入流(输出流)的包装。这些流一般由普通类的方法(即过滤流的一个超类)访问。典型的扩展是缓冲,字符转换和原始数据转换。这些过滤字节流是FilterInputStream 和FilterOutputStream 。
- FilterOutputStream(OutputStream os)
- FilterInputStream(InputStream is)
八、缓冲字节流
对于字节流,缓冲流(buffered stream),通过把内存缓冲器连到输入/输出流扩展一个过滤流类。该缓冲器允许Java 对多个字节同时进行输入/输出操作,提高了程序性能。因为缓冲器可用,所以可以跳过、标记和重新设置流。
BufferedInputStream(缓冲输入流)
缓冲输入/输出是一个非常普通的性能优化。Java 的BufferedInputStream 类允许把任何InputStream 类“包装”成缓冲流并使它的性能提高。
- BufferedInputStream(InputStream inputStream)
- BufferedInputStream(InputStream inputStream, int bufSize)
↑↑↑第一种形式生成了一个默认缓冲长度的缓冲流。第二种形式缓冲器大小是由bufSize传入的。
- 使用内存页或磁盘块等的若干倍的缓冲区大小可以给执行性能带来很大的正面影响。但这是依赖于执行情况的。
- 最理想的缓冲长度一般与主机操作系统、可用内存空间及机器配置有关。合理利用缓冲不需要特别复杂的操作。
- 一般缓冲大小为8192个字节,给输入/输出流设定一个更小的缓冲器通常是好的方法。
- 用这样的方法,低级系统可以从磁盘或网络读取数据块并在缓冲器中存储结果。
- 因此,即使你在InputStream 外同时读取字节数据时,也可以在超过99.9% 的时间里获得快速存储操作。
在缓冲器中使用 mark( )是受限的。意思是说你只能给mark( )定义一个小于流缓冲大小的参数。 |
该例运用mark(32),该方法保存接下来所读取的32个字节
- public static void main(String[] args) {
- try {
- String s = "This is a © copyright symbol "
- + "but this is © not.\n";
- byte buf[] = s.getBytes();
- ByteArrayInputStream in = new ByteArrayInputStream(buf);
- // 包装成缓冲输入流
- BufferedInputStream f = new BufferedInputStream(in);
- int c;
- boolean marked = false;
- while ((c = f.read()) != -1) {
- switch (c) {
- case '&':
- if (marked) {
- marked = false;
- f.mark(32);
- } else {
- marked = true;
- }
- break;
- case ';':
- if (marked) {
- marked = false;
- System.out.print("(c)");
- } else {
- System.out.print((char) c);
- }
- break;
- case ' ':
- if (marked) {
- marked = false;
- f.reset();
- System.out.print("&");
- } else {
- System.out.print((char) c);
- }
- break;
- default:
- System.out.print((char) c);
- break;
- }
- }
- } catch (Exception e) {
- // TODO: handle exception
- }
- }
- // 输出结果
- // ↓↓↓
- // This is a copy(c) copyright symbol but this is copy
BufferedOutputStream (缓冲输出流)
BufferedOutputStream与任何一个OutputStream相同,除了用一个另外的flush( ) 方法来保证数据缓冲器被写入到实际的输出设备。因为BufferedOutputStream是通过减小系统写数据的时间而提高性能的,可以调用flush( ) 方法生成缓冲器中待写的数据。 不像缓冲输入,缓冲输出不提供额外的功能,Java 中输出缓冲器是为了提高性能的。
- //第一种形式创建了一个使用512字节缓冲器的缓冲流。
- //第二种形式,缓冲器的大小由bufSize参数传入
- BufferedOutputStream(OutputStream outputStream)
- BufferedOutputStream(OutputStream outputStream, int bufSize)
PushbackInputStream (推回输入流)
缓冲的一个新颖的用法是实现推回(pushback ),Pushback 用于输入流允许字节被读取然后返回(即“推回”)到流
- //创建了一个允许一个字节推回到输入流的流对象
- PushbackInputStream(InputStream inputStream)
- //创建了一个具有numBytes长度缓冲区的推回缓冲流
- PushbackInputStream(InputStream inputStream, int numBytes)
除了具有与InputStream 相同的方法,PushbackInputStream 提供了unread( ) 方法
- //推回ch的低位字节,它将是随后调用read( ) 方法所返回的下一个字节
- void unread(int ch)
- //返回buffer缓冲器中的字节
- void unread(byte buffer[ ])
- //推回buffer中从offset处开始的numChars个字节
- void unread(byte buffer, int offset, int numChars)
例子:
- public static void main(String[] args) {
- try {
- String s = "if (a == 4) a = 0;\n";
- System.out.println(s);
- byte buf[] = s.getBytes();
- ByteArrayInputStream in = new ByteArrayInputStream(buf);
- PushbackInputStream push = new PushbackInputStream(in);
- int c;
- while ((c = push.read()) != -1) {
- switch (c) {
- case '=':
- if ((c = push.read()) == '=') {
- System.out.print(".eq.");
- } else {
- System.out.print("<-");
- push.unread(c);
- }
- break;
- default:
- System.out.print((char) c);
- break;
- }
- }
- } catch (Exception e) {
- // TODO: handle exception
- }
- }
- // 输出结果
- // ↓↓↓
- // if (a == 4) a = 0;
- //
- // if (a .eq. 4) a <- 0;
PushbackInputStream具有使InputStream生成的 mark( ) 或 reset( )方法失效的副作用。用markSupported( )来检查你运用mark( )/reset( )的任何流类。 |
九、 SequenceInputStream (顺序输入流)
SequenceInputStream 类允许连接多个InputStream 流
- SequenceInputStream(InputStream first, InputStream second)
- SequenceInputStream(Enumeration streamEnum)
十、PrintStream (打印流)
PrintStream 具有本书开始以来我们在System 文件句柄使用过的System.out所有的格式化性能。
- PrintStream(OutputStream outputStream)
- PrintStream(OutputStream outputStream, boolean flushOnNewline)
↑↑↑当flushOnNewline 控制Java 每次刷新输出流时,输出一个换行符(\n )。如果flushOnNewline 为true ,自动刷新。若为false ,刷新不能自动进行。第一个构造函数不支持自动刷新。
十一、RandomAccessFile (随机访问文件类)
RandomAccessFile 包装了一个随机访问的文件。它不是派生于InputStream 和OutputStream,而是实现定义了基本输入/输出方法的DataInput 和DataOutput接口。它同样支持定位请求——也就是说,可以在文件内部放置文件指针。
- RandomAccessFile(File fileObj, String access) throws FileNotFoundException
- RandomAccessFile(String filename, String access) throws FileNotFoundException
↑↑↑第一种形式,fileObj 指定了作为File 对象打开的文件的名称。第二种形式,文件名是由filename 参数传入的
两种情况下,access 都决定允许访问何种文件类型。如果是“r”,那么文件可读不可写,如果是“rw”,文件以读写模式打开。
//用来设置文件内部文件指针的当前位置,newPos 指文件指针从文件开始以字节方式指定新位置。调用seek( ) 方法后,接下来的读或写操作将在文件的新位置发生。
- void seek(long newPos) throws IOException
- //该方法通过指定的len设置正在调用的文件的长度,该方法可以增长或缩短一个文件。如果文件被加长,增加的部分是未定义的
- void setLength(long len) throws IOException