当前位置:课程学习>>第七章 输入输出流>>文本学习>>知识点一


知识点一  字节流




一、基本概念

所有的程序设计语言都具有与计算机文件系统进行交互的能力,Java也不例外。Java的I/O机制是建立在流的基础上的,这样明智的设计能有效地简化读写不同资料形态的程序。

在Java中,把不同类型的输入、输出源抽象为流(Stream),而其中输入或输出的数据则称为数据流(Data Stream),用统一的接口来表示,从而使程序设计简单明了,其示意图如图7.1:

6 图7.1 输入输出流

流一般分为输入流(InputStream)和输出流(OutputStream)两类,输入流只能进行读取操作,输出流只能进行写操作,此外根据其他划分角度还可以分为多种不同的流类型,其分类示意图如图7.2所示:

6
图7.2 Java中输入输出流的分类

在Java开发环境中,主要是由包Java.io中提供的一系列的类和接口来实现输入/输出处理。标准输入/输出处理则是由包java.lang中提供的System类来处理的。

字节流类为处理字节式输入/输出提供了良好的环境。一个字节流可以和其他任何类型的对象并用,包括二进制数据。这样的多功能性使得字节流对很多类型的程序都很重要。因为字节流是以InputStream和OutputStream为顶层,所以我们从讨论这两个类开始。

二、InputStream(输入流)

InputStream是一个定义了Java流式字节输入模式的抽象类。该类的所有方法在出错条件下引发一个IOException异常。表7.1 显示了InputStream的方法。

表7.1 InputStream类的主要方法

方法

描述

int vavilable()

返回当前可读的输入字节数

void close()

关闭输入源。关闭之后的读取会产生IOException异常

void mark(int numBytes)

在输入流的当前点放置一个标记。该流在读取numBytes个字节前都保持有效

boolean markSupported()

如果调用的流支持mark()/reset()就返回true

int read()

如果下一个字节可读则返回一个整型,遇到文件尾时返回-1

int read(byte buffer[])

试图读取buffer.length个字节到buffer中,并返回实际成功读取的字节数。遇到文件尾时返回-1

int read(byte buffer[],int offset,int numBytes)

试图读取buffer中从buffer[offset]开始的numBytes个字节,返回实际读取的字节数。遇到文件尾时返回-1

void reset()

重新设置输入指针到先前设置的标志处

long skip(long numBytes)

忽略numBytes个输入字节,返回实际忽略的字节数

【例7.1】回声程序的实现:本示例实现回声的功能,即键盘输入的内容会被重复输出。

1 //J_Echo.java

2 import java.io.InputStream;

3 import java.io.IOException;      

4 public class J_Echo

5 {

6    public static void mb_echo(InputStream in)

7    {

8       try

9       {

10          while(true)

11          {

12             int i = in.read();

13             if(i==-1)

14                return;

15             char c = (char)i;

16             System.out.print(c);

17          }

18       }

19       catch(IOException e)

20       {

21          System.err.println(e);

22       }

23       System.out.println();

24    }

25    public static void main(String[] args)

26    {

27       mb_echo(System.in);

28    }

29 }

程序最后执行的结果如下所示。

键盘输入:This program realizes “echo” function.

屏幕回显:This program realizes “echo” function.

键盘输入:To end the input, press Control-Z under Windows operation-system.

屏幕回显:To end the input, press Control-Z under Windows operation-system.

Ctrl+Z: ^Z

为了区分键盘输入的内容与屏幕回显的内容,上面的结果分别采用不同的字体表示。键盘输入的内容与屏幕回显的内容均是在标准输出窗口中显示。在Unix或Linux系统中,流的结束标志是Control+D,在Windows系列操作系统下,流的结束标志是Control+Z。

程序中System.in对应于标准输入,主要接受键盘的输入。同时,它是指向InputStream类型实例的引用。因此,可以将System.in作为参数传递给方法mb_echo。在方法mb_echo中调用了类InputStream的成员方法:

public abstract int read() throws IOException

它从输入流读入一个字符。如果输入流中没有字符可以读入,则返回-1。从标准输入中读取数据,并不是每键入一个字母都形成输入流,而是当键入回车符之后才开始将一整行字符作为输入流。所以在标准输出窗口中上面执行的结果是键盘输入内容与屏幕回显内容交替隔行排列。

【例7.2】回声程序的另一种实现。

1 //J_Echo1.java

2 import java.io.InputStream;

3 import java.io.IOException;

4 public class J_Echo1

5 {

6    public static void mb_echo(InputStream in)

7    {

8       try

9       {

10          while(true)

11          {

12             int n = in.available();

13             if(n>0)

14             {

15                byte[] b = new byte[n];

16                if(in.read(b)==-1)

17                   return;

18                String s = new String(b);

19                System.out.print(s);

20             }

21          }

22       }

23       catch(IOException e)

24       {

25          System.err.println(e);

26       }

27       System.out.println();

28    }

29    public static void main(String[] args)

30    {

31       mb_echo(System.in);

32    }

33 }

该程序最后执行的结果均与上面示例一样。只是在运行程序时,键盘的输入并不能立即在标准输出中显示出来,而必须等到键入回车符之后,才与回显内容一起显示。

三、 FileInputStream(文件输入流)

InputStream是抽象类,所以不能直接通过“new InputStream()”构造InputStream实例。但是可以通过构造InputStream子类的实例方式获得InputStream类型的实例。FileInputStream就是InputStream的子类。它的两个常用的构造方法如下:

     FileInputStream(String name) throws FileNotFoundException

     FileInputStream(File fileObj) throws FileNotFoundException

他们都能引发FileNotFoundException异常。这里,name是文件的全称路径,fileObj是描述该文件的File对象。对文件内容进行操作的基本步骤如下:

(1) 创建该文件所对应的实例,以获得相关的资源,如存放该文件信息的内存空间和对该文件的控制权限

(2) 对该文件进行读(输入)/写(输出)操作

(3) 最后关闭该文件,以释放所占用的资源

【例7.3】FileInputStream类的应用。

1 //J_Example1.java

2 import java.io.*;

3 public class J_Example1

4 {

5    public static void main(String[] args)

6    {

7       int i,b;

8       try

9       {

10          FileInputStream in=new FileInputStream("source.txt");

11          i=0;

12          while((b=in.read())!=-1)

13          {

14             System.out.println((char)b);

15              i++;

16          }

17          System.out.println();

18          System.out.println("TOTAL="+i);

19          in.close();

20       }

21       catch (Exception e)

22       {

23       }

24       try

25       {

26          System.in.read();

27       }

28       catch (Exception e)

29       {

30       }

31    }

32 }

该程序最后执行的结果是将文件“source.txt”中的内容显示在标准输出窗口中,并显示文件的字节数。如果当前目录中不存在“source.txt”,则会输出一条异常信息。

四、OutputStream(输出流)

类OutputStream是用来处理输出流的。与InputStream一样,它也是一个抽象类,所以不能通过“new OutputStream()”的方式构造OutputStream实例。该类的所有方法返回一个void值并且在出错情况下引发一个IOException异常。表7.2显示了OutputStream的方法。

表7.2 OutputStream类的主要方法

方法

描述

void close()

关闭输出流。关闭后的写操作会产生IOException异常

void flush()

定制输出状态以使每个缓冲器都被清除,也就是刷新输出缓冲区

void write(int b)

向输出流写入单个字节。注意参数是一个整型数,它允许你不必把参数转换成字节型就可以调用write()

void write(byte buffer[])

向一个输出流写一个完整的字节数组

void write(byte buffer[],int offset,int numBytes)

写数组buffer以buffer[offset]为起点的numBytes个字节区域内的内容

【例7.4】输出流的示例。

1 //J_Example2.java

2 import java.io.OutputStream;

3 import java.io.IOException;

4 public class J_Example2

5 {

6    public static void main(String[] args)

7    {

8       String s = "OutputStream!";

9       byte[] b;

10       OutputStream out = System.out;

11       b = s.getBytes();

12       try

13       {

14           out.write(b);

15          out.flush();

16       }

17       catch(IOException e)

18       {

19          System.err.println(e);

20        }

21    }

22 }

该程序最后执行的结果是:OutputSteam!

可以看出标准输出System.out是指向OutputStream实例的引用。上面的语句将System.out的值赋给OutputStream变量out。然后通过OutputStream的成员方法:

   public void write(byte[] b) throws IOException

输出指定的内容。在调用write之后,常常还会调用OutputStream的成员方法:

   public void flush() throws IOException

这往往是很有必要的,因为目前的计算机系统为提高速度,常常采用缓存机制。这样,在调用方法write之后,常常不会将内容直接输出或写入文件,而是暂时保存在缓存中。当积累数据到了一定程度时,才会真正往外输出。调用方法flush就是为了强制输出。这样,在调用方法flush之后,一般马上就可以看到输出结果。

五、FileOutputStream(文件输出流)

FileOutputStream是OutputStream的一个子类,其中最常用的构造方法是:

   public FileOutputStream(String name) throws FileNotFoundException和

   public FileOutputStream(String name, Boolean append) throws     

   FileNotFoundException

在这两个常用的构造方法中,前者的参数name是用来指定文件名,指定数据要写入的文件,而后者的第二个参数append用来表明写入的方式。当参数appned为true时,数据将添加到文件已有内容的末尾处。而当参数append为false时,文件已有的内容将被删除,然后再写入新内容。当appned为false时,两种构造方法的作用相同。

应用FileOutputStream将数据写入文件,也遵循文件内容操作的基本步骤,即:

(1) 创建FileOutputStream的实例,以获得相关的资料

(2) 将数据写入文件中

(3) 最后关闭该文件,以释放所获得的资源

下面给出关于FileOutputStream的应用示例,下面的例子创建一个样本字节缓冲器。先生成一个String对象,接着用getBytes()方法提取字节数组对等体,然后创建了三个文件:第一个file1.txt将包括样本中的各个字节;第二个文件是file2.txt,它包括所有字节;第三个也是最后一个文件file3.txt,仅包含最后的四分之一。不像FileInputStream类的方法,所有FileOutputStream类的方法都返回一个void类型值。在出错情况下,这些方法将引发IOException异常。

【例7.5】FileOutputStream类的应用实例。

1 //FileOutputStreamDemo.java

2 import java.io.FileOutputStream;

3 import java.io.OutputStream;

4 public class FileOutputStreamDemo {

5    public static void main(String[] args) {

6       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.";

7       byte buf[] = source.getBytes();

8       try

9       {

10          OutputStream f0 = new FileOutputStream("file1.txt");

11          for (int i=0;i<buf.length;i+=2){

12             f0.write(buf[i]);

13          }

14          f0.close();

15          OutputStream f1 = new FileOutputStream("file2.txt");

16          f1.write(buf);

17          f1.close();

18          OutputStream f2 = new FileOutputStream("file3.txt");

19          f2.write(buf,buf.length-buf.length/4,buf.length/4);

20          f2.close();

21       }

22       catch(Exception e)

23       {

24          System.out.println(e);

25       }

26    }

27 }

上面的程序运行结果,在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.

六、DataInputStream(数据输入流)和DataOutputStream(数据输出流)

有时,按字节为基本单位进行读/写处理并不方便,如一个二进制文件中存放有100个整数值,从中读取时,自然希望按int为基本单位(4字节)进行读取,每次读取一个整数值,而不是每次一个字节。Java语言中按照基本数据类型进行读/写的是:DataInputStream和DataOutputStream类,这两个类的对象是过滤流。(将基本字节输入/输出流,自动转成按基本数据类型进行读/写的过滤流)。

流的串接是指将一个流与其他流串连起来,以达到数据转换的目的。如从一个二进制文件按int为基本单位进行读取时流的串接图如图所示。流的串接长度没有限制,通过串接机制,极大地提高了Java I/O的灵活性。

6
图7.3 流的串接

FileInputStream类的对象是1字节输入流,每次1字节。与DataInputStream类的对象串接后,每次可直接读取一个int(4字节)。下面两个表中分别列出了DataInputStream和DataOutputStream类的常用方法。

                  表7.3 DataInputStream类的常用方法

方法

描述

public DataInputStream(InputStream in)

由字节输入流对象in生成一个DataInputStream对象

public Boolean readBoolean() throws IOException

从流中读取1字节。若字节值非0则返回true,否则返回false

public byte readByte() throws IOException

从流中读取1字节,返回该字节值

public char readChar() throws IOException

从流中读取a,b2字节,形成Unicode字符

(char)((a<<8)|(b&0xff))并返回

public double readDouble() throws IOException

从流中读入8字节形成double值并返回

public float readFloat() throws IOException

从流中读入4字节形成float值并返回

public int readInt() throws IOException

从流中读入4字节形成int值并返回

public long readLong() throws IOException

从流中读入8字节形成long值并返回

public short readShort() throws IOException

从流中读入2字节形成short值并返回

                           表7.4 DataOutputStream类的常用方法

方法

描述

public DataOutputStream(OutputStream out)

由字节输出流对象out生成一个DataOutputStream对象

public void writeBoolean(Boolean v) throws IOException

若v的值是true,则向流中写入(字节)1,否则写入(字节)0

public void writeByte(int v) throws IOException

向流中写入1字节。写入v的最低1字节,其他字节丢弃

public void writeShort(int v) throws IOException

向流中写入v的最低2字节,其他字节丢弃

public void writeChar(int v) throws IOException

向流中写入v的最低2字节,其他字节丢弃

public void writeInt(int v) throws IOException

向流中写入参数v的4字节

public void writeDouble(int v) throws IOException

向流中写入参数v的8字节

public void writeFloat(int v) throws IOException

向流中写入参数v的4字节

public void writeLong(int v) throws IOException

向流中写入参数v的8字节

【例7.6】DataOutputStream的应用示例。

1 //DataOutputExample.java

2 import java.io.*;

3 public class DataOutputExample {

4    public static void main(String[] args) {

5    int b;

6    try

7    {

8       FileOutputStream fout = new FileOutputStream("out.txt");

9       DataOutputStream dout = new DataOutputStream(fout);

10       for(b=0;b<10;b++)

11       {

12          fout.write(b+'0');

13       }

14       fout.close();

15       }catch(Exception e)

16       {

17          System.out.println(e);

18       }

19    }

20 }

该程序的输出结果是文件“out.txt”中写入了“0123456789”。

【例7.7】DataInputStream的应用示例,输出上面产生的文件的内容。

1 //DataInputExample.java

2 import java.io.*;

3 public class DataInputExample {

4    public static void main(String[] args) {

5       byte b;

6       try

7       {

8          FileInputStream fin = new FileInputStream("out.txt");

9          DataInputStream din = new DataInputStream(fin);

10          while(true)

11          {

12             try

13             {

14                b = din.readByte();

15                b-=(byte)'0';

16                System.out.println(b);

17             }catch(EOFException ex)

18             {

19             System.out.println();

20             break;

21          }

22       }

23       fin.close();

24       }catch(Exception e)

25       {

26          System.out.println(e);

27       }

28    }

29 }

七、RandomAccessFile(随机访问文件类)

在Java语言中,RandomAccessFile类的一个对象提供了对随机读/写文件的支持,它没有继承InputStream和OutputStream,而是直接继承于Object,专门用于处理由长度已知的记录组成的文件,记录的长度不一定相等,只要已知就行。RandomAccessFile只用于处理文件的输入和输出,其语法结构为:

 RandomAccessFile rwFile = new RandomAccessFile(“source.txt”, “rw”);

其中,第二个参数rw代表read和write,表示文件即可读又可写。如果要建立只读的文件,只要在上面的语句中去掉第二个参数中的w即可。根据常识,不存在只可写不可读的文件。

【例7.11】用RandomAccessFile类进行输入和输出的程序;

1 //RandomAccessExample.java

2 import java.io.*;

3 public class RandomAccessExample {

4    public static void main(String[] args) {

5       String s = new String();

6       float f = 3.145f;

7       try

8       {

9       //part1

10          RandomAccessFile rwFile1 = new RandomAccessFile("rwtest.dat","rw");

11          rwFile1.writeFloat(3.14f);

12          rwFile1.writeFloat(4.14f);

13          rwFile1.writeFloat(5.14f);

14          rwFile1.writeFloat(10.14f);

15          rwFile1.writeFloat(9.14f);

16          rwFile1.close();

17          //part2

18          RandomAccessFile rFile1 = new RandomAccessFile("rwtest.dat","r");

19          System.out.println("the first time when written");

20          for(int i=0;i<5;i++)

21             System.out.println("Value " + i + ":" +rFile1.readFloat());

22          rFile1.close();

23          //part3

24          RandomAccessFile rwFile2 = new RandomAccessFile("rwtest.dat","rw");

25          rwFile2.seek(8);

26          System.out.println("pos1=" + rwFile2.getFilePointer());

27          rwFile2.writeFloat(0f);

28          System.out.println("pos2=" + rwFile2.getFilePointer());

29          rwFile2.close();

30          //part4

31          RandomAccessFile rFile2 = new RandomAccessFile("rwtest.dat","r");

32          System.out.println("the second time when written");

33          for(int i=0;i<5;i++)

34             System.out.println("Value " + i + ":" + rFile2.readFloat());

35          //part5

36          System.out.println("pos3=" + rFile2.getFilePointer());

37          System.out.println("length=" + rFile2.length());

38          rFile2.close();

39          }catch(Exception e)

40          {

41             System.out.println(e);

42          }

43    }

44 }

上面的程序分为5部分:

part1:程序首先建立一个新文件,向其中写入若干浮点数,然后关闭文件。

part2:打开同一文件,把文件内容显示在屏幕上,然后关闭文件。

part3:打开同一文件,向其中写入一个数据,然后关闭文件。这个数据的位置如果和现有的某个数据重叠,那么会抹掉原数据。rwFile2.seek(8)语句把文件指针移动到文件的第8个字节处,接下来的输出语句输出指针的位置pos1。rwFile2.writeFloat(0f)语句从指针处开始写入一个浮点数0,接下来的输出语句又输出指针的位置pos2。

part4:打开同一文件,把文件内容显示在屏幕上。

part5:输出指针的位置pos3和文件的长度,然后关闭文件。

程序的输出结果如下:

the first time when written

Value 0:3.14

Value 1:4.14

Value 2:5.14

Value 3:10.14

Value 4:9.14

pos1=8

pos2=12

the second time when written

Value 0:3.14

Value 1:4.14

Value 2:0.0

Value 3:10.14

Value 4:9.14

pos3=20

length=20

结果中可以看到,在part3中,pos1的位置指出指针的位置在第8字节处,由于一个浮点数占4个字节,所以当把浮点数0写入到文件后,指针位置移动4个字节变为12。切记指针始终指向下一个要读写的位置。在part4中,由于写入的浮点数0正好在第3个数据的位置,所以原位置上的数据5.14被0取代。在part5中,由于在part4输出了文件中的所有数据,根据前面所讲,指针始终指向文件的下一个读写位置,因此这时指针位于最后一个数据的尾部,并且应该与程序的长度一致,结果也验证了这一点。

 

进入知识点二学习