Java程序的输入与输出
讨论Java程序的输入与输出。Java在I/O方面提供了众多支持,使我们的工作得到大大的简化。我们将学习利用这些支持以完成各种复杂的输入、输出。

7.1 理解java.io的类继承关系

  首先,让我们考察Java提供的常用输出输出流类(图7.1)。由于类的数目较多,没有列出1.1版本中新增的字符流类。在图7.2中,我们把字符流类与字节流类作了对比,在该图中可以看到字符流类的继承关系。接口和异常类也被省略了。
                        ┌BufferedInputStream
                        ├DataInputStream
              ┌FilterInputStream┼LineNumberInputStream
              ├FileInputStream └PushbackInputStream
              ├ByteArrayInputStream
     ┌InputStream──┼PipedInputStream
     │        ├SequenceInputStream
     │        ├StringBufferInputStream
     │        └ObjectInputStream ┌BufferedOutputStream
     │         ┌FilterOutputStream┼DataOutputStream
  Object┤         ├FileOutputStream └PrintStream
     ├OutputStream──┼ByteArrayOutputStream
     ├File       ├PipedOutputStream
     ├FileDescriptor └ObjectOutputStream
     ├ObjdecStreamClass
     ├RandomAccessFile
     └StreamTokenizer
    图7.1 java.io包中常用类层次图(不含字符流类)
  图7.1中包含了许多的输入和输出类(这还不包括我们欢天喜地上要讲到的字符流输入输出类)。为了能正确运用它们,我们必须对它们的功能和关系有个大根式的认识。

  7.1.1 字节流与字符流

  第二章中提到了Unicode字符集和ASCII字符集。前者用16位来表示一个字符,而者用8位来表示一个字符。Unicode字符集可表示的符号显然比ASCII字符集多得多,它可以表示世界上大多数语言的符号。
  在JDK1.0x版本中,只提供了字节流输入输出类。也就是说,输入输出的数据以字节为读写单位。这就给操作一些双字节字符带来了困难。比如汉字,用一个字节是不能表示,这就使Java程序的汉化成了问题。例如,用1.0x版的JDK开发一个文本编辑器,就可能出现这样的情况:用剪贴板可以把汉字贴进文本域却无法用键盘向文本域输入汉字字符。这就是标准输入流每次只接收了一个汉字的第一字节引起的。
  JDK1.1版对输入输出作了改进,为字节流输入输出类增加了对应的字符流输入输出类这样,程序员就可以根据实际情况选用合适的类。
  字符流I/O有其显示而易见的好处。首先它可以适用于世界上大部分语言,从而为Java程序的本地化带来方便。其次,一次读一个字符(16位)比读一个字节来得快,一般情况下可以弥补将数据按当前语言标准编码、解码的时间开销。
  字节流I/O类和字符流I/O类的命名有其对应关系。字节输入流类的名字以“InputStream”结尾。而字符输入流类的名字以“Reader” 结尾。字节输出流类的名字后缀为“OutputStream”,而字符输出流类的名字后缀为“Writer”。
  为了在适当的时候能把这两种流类联系起来,API中设置了两个类,充当二者的桥梁。InputStreamReader根据特定的编码规则从字节流创建相应的字符流,而Output。StreamWriter则根据编码规则从字符流读取字符,把它们转化为字节,写入字节流中。
  下面列出两种流类的对应关系(图7.2)。其中,左边一栏是按继承关系排列的字符流类,右边是对应的字节流类。
  Reader            InputStream
   ├BufferedReader      BufferedInputStream
   │  └LineNumberReader  LineNumberReader
   ├CharArrayReader     ByteArrayInputStream
   ├InputStreamReader     (none)
   │  └FileReader     FileInputStream
   ├FilterReader       FilterInputStream
   │  └PushbackReader   PushbackInputStream
   ├PipedReader        PipedInputStream
   └StringReader       StringBufferInputStream

  Write             OutputStream
  ├BufferedWriter       BufferedOutputStream
  ├CharArrayWriter       ByteArrayOutputStream
  ├OutputStreamWriter     (none)
  │  └FileWriter       FileOutputStream
  ├FilterWriter         FilterOutputStream
  ├PrintWriter         PrintStream
  ├PipedWriter         PipedOutputStream
  └StringWriter         (none)
    图7.2字符流类与字节流类的对应关系

  另外,1.1版的API中,对一些1.0x版本中已存在的类也进行了微小的修改,这主要是因为有类对字节和字符的转换可能产生错误。如以下构造函数和方法标记为过时:
  Sting  DataInputStream.readLine()
  InputStream  Runtime.getLocalizedInputStream(InputStream)
  OutputStream Runtime.getLocalizedOutputStream(OutputStream)
         StreamTokenizer(InputStream)
         String(byte ascii[],int hibyte,int offset,int count)
         String(byte ascii[],int hibyte)
    void    String.getBytes(int srcBegin,int srcEnd,byte dst[],int dstBegin)
  另外,添加了如下构造函数和方法:
         StreamTokenizer(Reader)
  byte[]     String.getBytes()
  void     Throwable.printStackTrace(PrintWriter)
  当程序员使用旧的API编程时,可以用
  javac -deprecation(文件名)
  来进行编译,这样编译器会给出较为详细的警告信息。编程人员可根据这些信息查找新文档,以获知新版本中的替代方法。
  本章的例子都是依据1.1版本的API编写的。

  7.1.2 输入输出类的分类

  java.io包中的类各有各的分工,粗略说来可以分为以下几类:
  文件I/O:有三类。对字节流类来说,包括把文件作为源进行流式输入的FileInputStream类;把文件作为目的进行流式输出的 FileOutputStream类;若你想随机存取文件,即在文件的任意位置读、数据,那么可以使用RandomAccessFile类。字符类则有 FileReader和FileWriter类。它们的功能对应于前两个字节流类。
  除此之外,还有两个类是与文件访问有关的,确切地说其功能更近于文件管理。它们是File类,用以访问文件或目录;FileDescriptor则封装了操作系统用以追踪被访问文件的信息。
  内存缓冲区I/O:字节流类有ByteArrayInputStream类,将字节数组转化为输入流,是从一个字符串创建输入流,与 ByteArrayInputStream异曲同工,帮也归入此类别。相应地,字符流类有CharArrayReader, CharArrayWriter,StringReader,此外还多一个StringWriter用来写字符串。
  余下一些类可以不同方式存取流中的数据。字节流类中,DataInputStream和DataOutputStream因其能对流中的不同类的对象分别操作而显得与众不同;ObjectInputStream和ObjectOutputStream能把若干完整的对象按选定的格式进行读写,但要求被操作对象实现Serializable接口;BufferedInputStream和BufferedOutputStream可以对流数据进行缓冲,实现类似“预输入”、“缓输出”的功能;LineNumberInputStream跟踪输入流中的行数;PusthbackInputStream提供了一个“可推回”的流,从这个流中读了数据后,还可以将它放回流中;PrintStream类提供了许多重载的方法以简化输出。对应的字符流类可以从 7.1.1节的对应关系中查出。
  除了上述类以外,Java还有种特殊的I/O类--管道I/O类。它们是专门为线程通讯预备的。管道提供了自动同步机制,可以防止线程通讯中的数据混乱。
  至引相信读者已对各个I/O类的功能有所了解。这里再解释一下过滤器I/O 推广java.io包中有不少类是过滤器类,它们都是从FilterInputStream或FilterOutputStream之中派生而来(参见图 7.1)。在字符流中,也有类似的类,但并不像字节流类一样必然从某个公共的过滤器父类派生而来。
  过滤器(Filter)形成的类对象从一个流中读入数据,写入另一个,就像一个流经过过滤产生另一个流一样。过滤器可以联合使用,也就是说“过滤”过的流可以再经其它过滤器“过滤”,过滤器型类的共性是:
  (1)用和种流为参数的构造,且输入型过滤器用输入流,输出型过滤器用输出流;
  (2)无明显的源/目的限制;
  (3)流中数据的内容“多少”并未改变,只可能性质略有变化。
读者不妨以这几条标准去理解过滤器I/O类与其子类,并在以后的示例中加以验证。

7.2 输入流与输出流

  字节输入流InputStream与字节输出流OUtputStream是两个抽象类。它们为java.io包中名目繁多的字节输入和输出流打下了基础。由于是抽象类,它们不能被实例化(也就是说,不能得到其对象),但它们的方法可以被派生类所继承或重写。
  对于字符流,相应的流类是Reader和Writer。由于它们的方法与InputStream和OutputStream对应,只是把对字节的操作改为对字符的操作,这里不再重复介绍。但为了读者能够对它们的对应关系有个基本认识,在本节末尾附上Reader类的方法列表,请读者参照。
  InputStream的方示如下:
  ■public abstract int read() throws IOException
  ■public int read(byte b[]) throws IOException
  ■public int read(byte b[],int offset,int length) throws IOException
  功能为从输入流中读数据。这一方法有几种重载形式,可以读一个字节或一组字节。当遇到文件尾时,返回-1。最后一种形式中的offset是指把结果放在b[]中从第offset个字节开始的空间,length为长度。
  ■public int available() throws IOException
  输入流共有多少字节可读。注意此方法对InputStream的各派生类不一定都有效,有时会有返回零字节的错误结果。
  ■public void close() throws IOException
  关闭输入流并释放资源。
  ■public boolean markSupperted()
  返回布尔值,说明此流能否做标记。
  ■public synchronized void mark(int readlimit)
  为当前流做标记。其参数说明在标记失效前可以读多少字节,这个值通常也就设定了流的缓冲区大小。
  ■public synchronized void reset() throws IOException
  返回到上一次做标记处。
  ■public long skip (long n) throws IOEnception
从输入流跳过几个字节。返回值为实际跳过的字节数。
  对于“mark”我们还需解释一下。输入流提供“标记”这一机制,使人们可以记录流中某些特定的位置,并能重复读部分内容。支持“mark”就必须要求当前流有一定大小的缓冲区,存放部分数据,即从标记点到当前位置的数据。当这一缓冲区装满溢出,我们就无法追踪到上一个标记处的数据了,这就称之为“标记失效”。若想用reset()返回到一个失效的标记处,将会发生输入输出异常(IOException)。
  OutputStream的方法如下。各方法均可能抛出输入输出异常(throws IOException)。
  ■public abstract void write(int b)
  ■public void write(byte b[])
  ■public void write(byte b[],int offset,int length)
  这三个重载形式都是用来向输出流写数据的。具体每个不甘落后 作用,读者可根据前文read()方法对照之。
  ■public void flush()
  清除缓冲区,将缓冲区内尚未写出的数据全部输出。若要继承OutputStream类,这个方法必须重写,因为OutputStream中的方法未做任何实物性工作。
  ■public void close()
  关闭输出流,释放资源。
  以上提到的这些方法,在下面的章节中将有不少被运用,读者可根据实例领会它们。
  附Reader类的方法列表。
  构造函数:
  ■protected Reader() 
  ■protected Reader(object lock)
  方法:
  ■public int read() throws IOException
  ■public int read(char cbuf[]) throws IOException
  ■public abstract int read(char cbuf[],int off,int len)throws IOException
  ■public long skip(long n) throws IOException
  ■public boolean ready() throws IOException //判断流是不可以读
  ■public boolean mark(int readAheadLimit)throws IOException
  ■public void reset() throws IOException
  ■public abstract void close() throws IOException

7.3 文件I/O

  这一节中我们将结合实例讨论File,FileInputStream,FileOutputStream,FileDescriptor和RandomAccessFile类的方法与使用。

  7.3.1 一个文件I/O实例

  让我们用一个例子来演示对文件的输入输出(例7.1)。图7.3中列出了这个例子的运行结果。
  例7.1 fileIODemo.java。
  1:import java.io.*;
  2:import java.lang.*;
  3:
  4: public class fileIODemo{
  5:  public static void main(String args[]){
  6:   try{
     //创建输入输出流
  7:   FileInputStream inStream = new FileInputStream("text.src");
  8:   FileOutputStream outStream = new FileOutputStream("text.des");
      //读文并写入输出流
  9:     boolean eof = false;
  10:    while(!eof){
  11:     int c = inStream.read();
  12:     if(c==-1) eof = true;
  13:     outStream.write((char)c);
  14:    }
  15:    inStream.close();
  16:    outStream.close();
  17:   }catch(FileNotFoundException ex){
  18:    System.out.println("Error finding the files");
  19:   }catch(IOException ex){
  20:   System.out.println("IOException occured.");
  21:  }
    //获取文件管理信息
  22:  File file = new File("text.des");
  23:  System.out.println("Parent Directory:"+file.getParent());
  24:  System.out.println("Path:"+file.getPath());
  25:  System.out.println("File Name:"+file.getName());
  26:  try{
     //创建RandomAccessFile对象,以便随机读写。"rw"代表可读可写
  27:   RandomAccessFile rafile = new RandomAccessFile("text.des","rw");
     //指针置到文件头
  28:   rafile.seek(0);
  29:   boolean eof=false;
  30:   System.out.println("The content from very head:");
     //读文件
  31:   while(!eof){
  32:   int c = rafile.read();
  33:    if(c==-1) eof = true;
  34:    else System.out.print((char)c);
  35:   }
     //下两行把读指针置到第三字节
  36:   rafile.seek(0);
  37:   rafile.skipBytes(3);
  38:   System.out.println("/nThe pointer's position:"+rafile.getFilePointer());
  39:   System.out.println("The content from current position:");
  40:   eof=false;
  41:   while(!eof){
  42:    int c=rafile.read();
  43:    if(c==-1) eof=true;
  44:    else System.out.print((char)c);
  45:   }
     //强制输出缓冲区中所有内容
  46:   System.out.flush();
  47:   rafile.close();
  48:  }catch(IOException ex){
  49:   System.out.println("RandomAccessFile cause IOException!");
  50:  }
  51: }
  52:}
  例7.1的运行结果如下:
  (略)
  为了充分展示与文件I/O相关的类的作用,我们的例子中有一些冗余的东西。我们的这个程序位于C:/BookDemo/ch07路径下(见例7.1行 7),此路径又有一个子上当text,其中有文件text.src。运行此程序,将在C:/bookDemo/ch07下创建一个新文件 text.des,text.src的内容被写信此文件。下面的段对File类的演示说明了文件的部分管理信息。然后我们又使用了 RandomAccessFile,试验了文件在指定位置的读写。
  第46行的Sytem.out.flush()语句不可以被省略,读者不妨去掉它试一试。你会发现,有一部分输出信息不知道到哪儿去了。实际上,flush()的作用就是把缓冲区中的数据全部输出,我们棣输出流输出以后,某些输出流(有缓冲区的流)只是把数据写进了缓冲区而已,不会马上写到我们要求的目的地。如果不像例子中一样强制输出,部分数据可以就来不及在程序结束前输出了。
  细心的读者或许要问:为什么第一次用ReadomAccessFile读文件时,输出语句后面没有flush()呢?岂非自相矛盾吗?原来, System.out是PrintStream类的对象(关于PrintStream后有缓冲区中的内容清除出去。因此许多地方就不必加flush() 了。PrintStream的这个特点,在创建其对象时是可以去掉(disable)的。
  这个程序中用到了IOException和FileNotFoundException两个异常。后者是从前者派生出来的,因此,如果去年程序中的所有try、catch,而在main()方法开头加上throws IOException,哪样可以。但这样不好区分各种不同的异常情况,即使找不到我们需要的text.src文件,也不会有任何信息显示。这无疑是一种不良的编程风格。因此我们提倡对各个异常分别处理,这样对出错情况可以很地掌握。

  7.3.2 文件输入输出的类库支持

  下面我们逐一介绍例7.1中用到的各个类。
  1.File类
  File类的构造函数有三个。分别根据文件名、文件路径与文件名、文件对象(目录)与文件名创建实例。即:
  ■public File(String path)
  ■public File(String path,String name)
  ■public File(File dir,String name)
  除了例子中用到的以外,还有许多方法,下面仅列出较常用的:
  ■public boolean exists()判断文件是否存在
  ■public boolean canRead()判断文件是否可读
  ■public long length()返回文件长度
  ■public boolean mkdir()创建目录
  ■public boolean renameTo(File dest)文件改名
其中,后三个方法可能抛出I/O异常。
  2.FileInputStream类
  它是文件输入流类
  构造函数有三个:
  ■public FileInputStream(String fileName) throws FileNotFoundException
  ■public FileInputStream(File file) throws FileNotFoundException
  ■public FileInputStream(int fd) throws FileNotFoundException
  三个构造函数分别根据文件名、文件对象、文件描述符创建一个文件输入流。例子中用的是第一种。
  方法:
  read()、skip()、available() 、close()分别重写了抽象类InputStream的同名方法,功能如前所述。此外还有:
  ■public final int getFD()
  返回相应的文件描述符。
  ■protedted void finalize() throws IOException
  关闭输入流,并收集无用内存空间。
  现在我们必须介绍一下文件描述符类FileDescriptor。这个类用于访问操作系统维护的文件描述符(也称句柄)。但这个类产不能访问很多信息。它只提供了两个方法,即valid(),以判断文件描述符是否有效;sync(),用以同步系统缓冲区。
  3.FileOutputStream类
  文件输出流。三个构造函数,其参数、返回值及异常均与FileInputStream的相对应。write()、close()方法重写了 OutputStream的同名方法。getFD()与finalize()功能与InputStream的类似。
  4.ReadomAccessFile类
  该类用于随机访问文件。
  构造函数有三种:
  ■public RandomAccessFile(String Filename,String mode) throws IOException
  ■public RandomAccessFile(int FD) throws IOException
  ■public RandomAccessFile(File file,String mode)throws IOException
  由上可见,我们可以用文件名加读写方式、文件描述符、File对象加读写方式来创建其对象。其中读写方式用“r”表示只读,“rw”表示可读写,等等。用过C语言的读者对此应当不会陌生。
  此类的成员方法很多。除了重写InputStream的read()方法之外,还可以读、写一个布尔值、一个字节、一个整数......等对象。这些方法都不可重写,并且抛出I/O异常(IOException)。讯方法名为“read”加类型名(类型名的第一字母大写),写方法名为“write”加类型名。如
  readInt()读一个整型数
  writeDouble()写一个双精度浮点数
等。另外还有文件指针的操作,如skipBytes(int n)等。
  有了以上这些类的支持,处理文件输入输出和维护文件就容易多了。

7.4 内存缓冲区

  内存缓冲区I/O,对字节流来说指的是ByteArrayInputStream和ByteArrayOutputStream类的运用。此外, StringBufferInputStream与ByteArrayInputStream用法相似将一并介绍。对字符流不另举例,它们使用与字节流类类似。

  7.4.1 程序示例

  同上一节一样,我们还是先看一个例子(例7.2)
  例7.2 ByteArrayIODemo.java
  1:import java.io.*;
  2:
  3: public class ByteArrayIODemo{
  4:  public static void main(String args[]) throws IOException{
      String s ="This a test";
  5:   byte buffer[]=s.getBytes();
  6:   ByteArrayOutputStream byteArrayOut = new ByteArrayOutputStream();
  7:  for(int i=0;i<buffer.length;++i)
  8:    byteArrayOut.write(buffer[i]);
  //由字节数组创建字节输入流
  9:   ByteArrayInputStream inStream = new
       ByteArrayInputStream(byteArrayOut.toByteArray());
  //读写
  10:  boolean eof=false;
  11:  while(!eof){
  12:   int c=inStream.read();
  13:   if(c==-1) eof=true;
  14:    else System.out.print((char)c);
  15:  }
  16:  System.out.println("/nThe'writeTo' method can produce same results.");
  //用ByteArrayOutputStream的writeTo()方法写
  17:  byteArrayOut.writeTo(System.out);
  18:  byteArrayOut.close();
  //available()与reset()的使用
  19:  System.out.println("/nThe buf of inStream has the length(before
    seset):"+inStream.available());
  20:  inStream.reset();
  21:  System.out.println("/nThe buf of inStream has the length:"+inStream.available());
  22:  inStream.close();
  23: }
  24:}
  该程序的运行结果:(略)
  这个例子看来相对简单些。我们先把字节数组的内容写进一个字节数组输出流对象。然后,用一个字节数组输入流对象读数据,再用System.out输出。程序显示了writeTo()方法的作用。另外,我们还在reset()前、后用了两疚available()方法,请注意两方法先后产生的不同结果。这个例子主要用来演示字节数组I/O的部分方法。

  7.4.2缓冲区I/O的类库支持

  看过了例子,我们接下来介绍有关的类。
  1.ByteArrayInputStream类
  这个类用于从一个
  字节数组取得输入数据。
  它有两个构造函数:
  ■public ByteArrayInputStream(byte Buf[])
  由字节数组创建相应的输入流。
  ■public ByteArrayInputStream(byte buf[],int offset,int length)
  由字节数组中起点为offset长为length的一段创建输入流。
  成员变量:
  protected byte buf[]数据缓冲区
  protected int pos 缓冲区中当前位置
  protected int count缓冲区中字节数目
  该类的成员方法都是同步(synchronized)的。
  ■public synchronized int read()
  读一个字节。
  ■public synchronized int read(byte b[],int offset,intrlength)
  读取多个字节,返值一般为读到的字节数。但读到末尾时返回-1。
  ■public synchronized long skip(long n)
  跳过n个字节。若返回值不等于n,可能是遇到末尾。
  ■public synchronized int available()
  求得缓冲区内字节数目。
  ■public synchronized void reset()
  该指针重新设置到输入流的开始处。注意,这个reset()与InputStream中的功能不同。它并不作用于标记。
  2.ByteArrayOutputStream类
  这个类用于把数据写进字节数组(缓冲区)。
  构造函数:
  ■public ByteArrayOutputStream()
  ■public ByteArrayOntput Stream(int size)
  其中size指定缓冲区的初始大小(它是可以动态增长的)。
  成员变量:
  protected byte buf[]缓冲区
  protected int count缓冲区大小
  方法:
  ■public synchronized void write(int b)
  写一个字节。
  ■public synchronized void write(byte b[],int offset,int length)
  把数组b中由offset开始长为length的一部分写入缓冲区。
  ■public synchronized void writeTo(OutputStream out)throws IOException
  把缓冲区内容写到另一输出流out。
  ■public synchronized void reset()
  指针定到缓冲区开始。当然,以后再写入也就是从缓冲区的开始位置重写了,原有的内容就都清掉了。
  ■public syschronized byte[] toByteArray()
  将缓冲区内容作为一个字节数组返回。
  ■public int size()
  当前缓冲区大小。
  ■public string toString()
  ■public string toString(int hibyte)
  把缓冲区内容转化为字符串。其中hibyte指把字符(通常是8位的ASCII字符)转为16位的Unicode值时,高八的值。
  3.StringBufferInputStream类
  它的构造函数以一个字符串为参数,原型为:
  ■public StringBufferInputStream(String s)
  其余成员变量及方法均与ByteArrayInputStream的同名且基本功能相同,此不赘述。
  这三个类的共性是内存中开辟了一段空间来做I/O缓冲区,故称缓冲区I/O类。

7.5 过滤器I/O

  这一节涉及的类较多,但我们可以结合几个例子逐一介绍。
  在第一节中,我们已经谈了一些过滤器类的特性。过滤器是可以“连接”的,即一个数据流经过过滤后,其结果可以再次过滤。我们可以使用这样一串过滤器中的任一个方法来完成某种特殊的操作。关于这一点在第二个例子中有更明白的阐述。

  7.5.1 例1:各类数据的I/O

  第一个例子(例7.3)演示了对各类数据的输入输出。
  例7.3FilterIODemo1.java。
  1: import java.io.*;
  2: public class FilterIODemo1{
  3:  public static void main(String args[]) throws IOException{
      //串接过滤器
  4:   BufferedOutputStream bufOut=
  5:    new BufferedOutputStream(new FileOutputStream("text.txt"));
  6:   DataOutputStream dataOut = new DataOutputStream(bufOut);
      //用DataOutputStream类写各种数据
  7:   dataOut.writeBoolean(true);
  8:   dataOut.writeChar('a');
  9:   dataOut.writeInt(1);
  10:  dataOut.writeDouble(3.3);
  11:  bufOut.close();
  12:  dataOut.close();
  13:  BufferedInputStream bufIn=
  14:   new BufferedInputStream(new FileInputStream("text.txt"));
  15:  DataInputStream dataIn= new DataInputStream(bufIn);
    //用DataInputStream类读各种数据
  16:  System.out.println(dataIn.readBoolean());
  17:  System.out.println(dataIn.readChar());
  18:  System.out.println(dataIn.readInt());
  19:  System.out.println(dataIn.readDouble());
  20:  bufIn.close();
  21:  dataIn.close();
  22: }
  23:}
  例7.3的运行结果如下:(略)
  上述例子演示了DataInputStream、DataOutpurStream、BufferedInputStream和 BufferedOutputStream的使用。该程序中只有一个方法main()。
  在方法的开头,我们实例化了BufferedOutputStream类,得到对象bufOut。注意,我们的数据输出的最终目的地是文件 “Text.txt”。为了能够利用BufferedOutputStream的缓输出(把输出内容先存入缓冲,然后大块输出)功能,我们在文件输出应对上加一个过滤器,形成:
  数据→过滤器对象bufOut→文件输出流
这样,我们用dataOut来写数据,就可以直接把各种类型的数据写入文件text.txt。
  程序的后半部分几乎是第一个程序的翻版。我们在输入流上也加了过滤器,就可以用过滤器的方法来操作输入流了。
  由于BufferedOutputStream和BufferedInputStream没有提供新的方法,这个例子也许会使读者产生一种错觉,好像只有最外层(最接近“数据”)的过滤器才能操纵输入。事实并非如此,我们将在下一个例子中说明这一点。
  我们要解释的问题是,如果我们读数据时,选用的读方法与写时不一致会怎么样呢?读者可以自行实验一下。如果我们把
  dataIn.readBoolean()换作dataIn.readChar()
读出的结果就不正确了(注意读到的并不是字符‘t’),各种类型的数据存储的格式是不同的。虽然我们得到了图7.4所示的结果,但如果你用type命令看一下 text.txt,将会看到不同的输出。因此,不要把程序的输出和数据的内部在存储混为一谈。当使用dataI/O时,应当对你要读的数据的类型心中有数。DataInputStream并不能从一堆数据中析取你所需要的那个整数。

  7.5.2 过滤器类家庭

  下面我们介绍例1中出现的过滤器类。首先介绍一下它们的父类FilterInputStream和FilterOutputStream。
  1.FilterInputStream类
  这是一个抽象类。它是所有过滤器输入类的父类,提供了从一个输入流创建另一个输入流的方法。
  构造函数:
  ■public FilterInputStream(InputStream in)
  人一个输入流构造过滤器输入流。
  方法:
  重写了InputStream的同名方法,未提供新的方法。
  2.FilterOutputStream类
  与FilterOutputStream相对应,提供从一个输出流创建另一个输出流的方法。
  构造函数:
  ■public Filer OutputStream(OutputStream out)
  由输出流创始创建一个过滤器输出流。
  方法:
  重写了OutputStream的同名方法。
   3.BufferedInputStream类
  从这个类开始,我们来介绍例7.3中用到的过滤器子类。BufferedInputStream类提供了一种“预输入”功能,它把输入数据在其缓冲区内暂存,在适当的时候把较大块的数据提交出去。
  构造函数:
  ■public BufferedInputStream(InputStream in)
  ■public BufferedInputStream(InputSteam in,int size)
  其中size指缓冲区大小。
  方法:
  重写了父类的方法。其中skip()、available()、mark()、reset()均为同步(synchonized)方法。
  4. BufferedOutputStream类
  提供“缓输出”功能,把输出数据暂存后,在适当时候大批送出。
  构造函数:
  ■public BufferedOutputStream(OutputStream out)
  ■public BufferedOutputStream(OutputStream out,int size)
  方法:
  ■public synchronized void write(int b) throws IOException
  ■public synchronized void write(byte b[],int offset,int length) throws IOException
  ■public synchronized void flush() throws IOException
以上方法重写了父类的同名方法。
  在BufferedI/O类中,还有一些protect型的成员变量,是关于缓冲区和标记的,这里就不一一列出了。
  5. DataInput接口和DataOutput接口
  要介绍Data I/O类,就必须介绍Data I/O接口。
  Data I/O类的目的是从流中析取或向流中写入指定的数据对象。一个流可以是纯字符流,也可以包含许多类型的数据。DataInput接口和 DataOutput接口就提供了从流中析取和写入数据的方法。用于读的方法除了个别之外都是无参的,写的方法则往往以被 写的数据类型为参数。方法的名字也很好记,即为“read”或“write”后接类型名,如readInt(),readUnsignedByte(),writeInt(), writeUnsignedByte()等。这引起方法均可能抛出I/O异常。一般说来,读时遇文件尾时抛出EOFException(是 IOException的子类),读写时发生其它错误抛出IOException。
除了上面所说的名字很有规律的方法外,Data I/O接口中还有几个方法:
  ■public abstract void readFully(byte buffer[])
  读全部数据到buffer[]数组。读时系统处于阻塞状态。
  ■public abstract void readFully(byte buffer[],int offset,int length)
  把数据读到数组buffer[]中从Offset开始长为length的地方。
  ■public abstract int skipBytes(int n)
  跳过规定字节数。返值为实际跳过的字节数。
  ■public abstract String readLine()
  读取一行数据。
  此外,还有我们早已熟悉的write()方法的三种重载形式。
  6. DataInputStream类
  介绍过两个数据I/O的接口后,介绍数据I/O流类就简单多了。DataInputStream类实现了DataInput接口,因面也就实现了这个接口的所有成员方法。此外,还有两个read()方法:
  ■public final int read(byte b[])
  ■public final int read(byte b[],int offset,int length)
  重写了FilterInputStream的同名方法。
  DataInputStream只有一个构造函数。像所有过滤器输入流类一样,这个构造函数的参数是InputStream的一个对象。
  7.DataOutputStream类
  这个类的成员方法我们都很熟悉了。除了实现DataOutput接口的方法之外,再就是一个flush()方法。write()与flush()重写了FilterOutputStream类的同名方法。
  现在我们可以回过头来再看一下例7.3,印证一下刚才讲过的内容。这个例子的重点之一是演示过滤器的“连接”,另一个是介绍相应的类。

  7.5.3 例2:行号与“可推回”的流

  在下面的例子(例7.4)中,我们将进一步理解过滤器的连接问题。前面例子基本上用的都是字节流类,这个例子使用字符流类。
  例7.4 FilterIODemo2.java。
  1:import java.io.*;

  3:public class FilterIODemo2{
  4: public static void main(String args[]) throws IOException{
  5:  String s="this is a multi-line string./n It is /nused to demo filterIO./n";
  6:  char array[]=new char[s.length()];
  7:  for(int i=0;i<s.length();++i)
  8:  array[i]=s.charAt(i);
    //创建字符流,串接过滤器
  9:  CharArrayReader charReader = new CharArrayReader(array);
  10:  PushbackReader pushReader = new PushbackReader(charReader);
  11:  LineNumberReader lineReader = new LineNumberReader(pushReader);
  12:  String line;
    //读字符流,加行号输出
  13:  while((line = lineReader.readLine())!=null){
  14:   System.out.println(lineReader.getLineNumber()+":"+line);
  15:  }
    //指针置到开头
  16:  try{ pushReader.reset();}catch(IOException e){}
    //读字符流,每读到一个'/n'就把它推回
  17:  boolean eof = false;
  18:  boolean met = false;
  19:  while(!eof){
  20:   int c=pushReader.read();
  21:   if(c==-1) eof=true;
  22:   else if(((char)c=='/n')&&!met){met =true;pushReader.unread(c);}
  23:   else met =false;
  24:   if(c!=-1) System.out.print((char)c);
  25:  }
  26:  System.out.flush();
  27:  pushReader.close();
  28:  charReader.close();
  29:  lineReader.close();
  30: }
  31:}
  该程序的运行结果如下:(略)
  这个例子的功能是:给一个字符串加上行号后输出;把每个换行符都“重复”一次,即每次换行时加一个空行。该例子使用的是字符流I/O,演示了几个类的使用:CharArrayReader,PushbackReader,LineNumberReader。此外,我们还可以复习一下前面提到的几个流类。
  PushbackReader,顾名思义是是可以把数据“推回”输入流的流类。我们用它来实现对换行符的重复--只要读完后把“推回去”,下次就可再读一遍了。LineNumberReader可以追踪输入的行数,用它来实现加行号输出。
  现在来讲解一下程序。第5行中,在main()方法的开始,定义了一个字符串s。其中,有三个换行符‘/n’。然后创建一个字节数组Array[],并在接下来的for循环(第7、8行)中为它赋值。以此为参数创建了一个内存缓冲区输入流的对象。这就是我们一串过滤器的源点。注意array并不是一个输入流,相应的CaarArrayReader也不是一个过滤器。
  现在考虑选用过滤器。可根据我们想要的功能来选择。既然我们要行号,那么显然最好是一行一行读数据。BufferedReader的readLine ()方法正是我们需要。(readLine()方法本来是DataInputStream类的方法,但在1.1版中过时了。详细情况在第一节中已有说明。这里用DataInputStream也是可以的但编译时会警告信息。)加行号我们可以一行一行地读,也可以自己高于个变量来累计行数。当然也可以利用一个现成的类和现在的方法--选择LineNumbdrReader类及其getLineNumber()方法。由于LineNumbdrReader本身是BuffredReader类的子类,可以直接用它来逐行读数据,不必再引入BufferedReader类。为了重复写回画换行符可选用 PushbackInputStream类和它的unread()方法。
  下面的任务是把它们串起来,如例子所示,可将它些过滤器一个“输出”作为下一个的“输入”。第一个while循环(第13到15行)中做的事很简单;读一行信息,取得其行号,然后一些输出。
  第二个while循环(第19行到25行)的工作是重写操作符。我们用pushReader来读数据。布尔量eof来标识输入是否结束,met用来标识当瓣换行符是否被推回过。当输入没有结束时,每读到一个‘/n’ 时,就不会再“推回”了,保证换行符只被重复一次。
  正如前面所提到过的,一串过滤器中的任一个都可以操作数据,无论该过滤器是最先的或最末的或是中间的任何一个。
  由于我们是用print()方法来输出字符的,程序结束时可能还有一部分数据在缓冲区中,没被写到屏幕上。因此我们加了一个flush()方法强制显示到屏幕上。
  将用过的流都关闭(第27到29行)是一种好的编辑风格。虽然Java的“垃圾收集”系统可以回收废弃不用的资源,仍应自觉地打扫“战场”,把能回收的资源主动回收。

  7.5.4 类库支持

  下面详细介绍一下例7.4中新出现的类。有一点需要解释,就是字符流I/O类与字节流I/O类的继承关系并不是一一对应的。比如,字节流I/O类中的 PrintStream是FilterOutputStream的子类,而对应的字符流类PrintWriter却是Writer类的子类。因此,严格地说PrintWriter并非过滤器类。但是,为了能够分六别类地研究这些类,我们不苛求这个差别,而是按照字节流I/O类的继承关系,对应地把相应字符流I/O类也看作过滤器类。
  1.PushbackReader类
  构造函数两个:
  ■public PushbackReader(Reader in,int size)
  创建缓冲区大小为size的一个PushbackReader对象。
  ■public PushbackReader(Reader in)
  创建缓冲区大小为一个字符的一个PushbackReader对象。
  方法:
  ■public int read()
  ■public int read(char cbuf[],int offset,int length)
  读数据。
  ■public void unread(int ch)
  回退一个字符。当缓冲区满或发生其它输入输出的异常情况时,抛出I/O异常。
  ■public int avaliable()
  返回缓冲区内字节个数。
  ■public boolean markSupported()
  确认输入流是否支持标记功能。
read()、unread()、available()均可能抛出IOException。
  2.LineNumberReader类
  构造函数两个,与PushbackReader类似。
  下面列出方法的原型,其中我们已经熟悉的,在此就不给出解释了。
  ■public int read() throws IOException
  ■public int read(char cbuf[],int offset,int length) throws IOException
  ■public void setLineNumber(int lineNumber)
  设置行号。
  ■public int getLineNumber()
  读行号。
  ■public long skip(long n) throws IOException
  ■public int available()throws IOException
  ■public void mark(int readAheadLimit)throws IOException
  在当前位置作标记。从此读取readAheadLimit个字符后标记变为无效。
  ■public void reset()throws IOException
  返回到上一标记。
  3.PrintStream类和PrintWriter类
  PrintStream类是过滤器类中一个不可忽视的成员,最基本的标准输出就要借助于它--我们常用的System.out变量就是 PrintStream实例。与之对应的字符流类是PrintWriter类。
  PrintStream有两个构造函数(在新版API中已标记为过时):
  ■public PrintStream(OutputStream out)
  ■public PrintStream(OutputStream out,boolean autoFlush)
其中,autoFlush置为true时,每当输出遇到换行符,缓冲区的内容就被强制全部输出,如同调用了一次flush()。但要注意,如果没遇到换行符,还是会有数据“憋”在缓冲区里。
  方法(已熟悉的就不解释):
  ■public void write(int b)
  ■public void write(byte b,int offset,int length)
  ■public void flush()
  ■public void close()
  ■public void print(Object obj)
  这个方法功能是非常强大的,它可以输出任何对象,而不必另加说明。此外print()方法有许多重载形式,即有多种参数。它们是字符串 (String)、字符数组(char[])、字符(char)、整数(int)、长整数(long)、浮点数(float)、双精度浮点数 (double)、布尔值(boolean)。其中,输出多个数单位的print()方法(也就是指参数为String和char[]的)是同步 (synchronized)方法。
  ■public void println()输出一个换行符。
  ■public synchronized void println(Object obj)
  println()方法有9个重载形式,几乎就是print()方法的翻版。唯一的区别在于println()方法都是同步的。
  ■public boolean checkError()
  检查输出过程中有什么错误,如有,返回true值。只要输出流中出现一次错误,则出错后的任意对checkError()的调用均会返回真值。
  下面介绍PrintWriter类。
  如同第二节中所说,PrintWriter是JDK1.1版增加了与字节流I/O相对应的字符流I/O。但是,为了保持兼容性,原先的类几乎没有改动。再加之调试的需要,PrintStream类被保留,并且System类中的成员变量out、err仍作为它的对象。然而,PrintWriter用于大多数输出比PrintStream更为合适。因此1.1版的API中建议新开发的代码使用PrintWriter类,并将 PrintStream类的两个构造函数标记为过时。这样,虽然使用System.out输出不会产生问题,在程序中创建新的PrintStream对象时却会产生编译时的警告。
  PrintWriter类与PrintStream类的方法是对应的。有一个不同之外需提请读者注意,就是当前者的自动清空缓冲区的功能被使能时(构造函数中autoFlush置为true),仅当println()方法被调用时才自动清缓冲区,而不是像PrintStream一样遇到一个换行符就清缓冲。
  到此为止,我们已介绍了各种类型的过滤器I/O类。适用于字节流和字符的各种对应过滤器类,其方法也是对应的。因此,对没有介绍的类读者可以从其对应类推理其功能。

7.6 管道I/O

  管道I/O是专门用于线程通信的。对于字节流Java提供了两个类,PipedInputStream类被线程用来写字节数据。两个管道I/O流对象可以连接起来,这样一个线程写的数据就可以被另一个线程来读。对于字符流也有两个类,分别叫做PipedReader和PipedWriter。我们只详细介绍字节流的管道I/O类。

  7.6.1 PipedInputStream类

  这个类有两个构造函数。一个无参,用它建立起输入流后,需将它与一个管道输出流相连接。另一个以管道输出流(PipedOutputStream)对象为参数,创建一个与该输出流对象相连接的输入流。
  PipedInputStream类的所有方法均可能抛出IOException。
  ■public void connect (PipedOutputStream src)
  将输入流连接到某管道输出流。
  ■public synchronized int read()
  ■public synchronized int read(byte b[],int offset,int length)
  读数据。
  ■public void close()
  关闭流。

  7.6.2 PipedOutputStream类

  与PipedInputStream类完全对应,它有两个构造函数,其中一个以PipedInputStream对象为参数,另一个无参。成员方法也是包括connect(),close(),另外还有两种形式的write()方法,这里就不细述了。

  7.6.3 程序示例

  下面用一个示例(例7.5)具体演示管道I/O的使用。
  例7.5 PipeIODemo.java
  1: import java.lang.*;
  2: import java.io.PipedInputStream;
  3: import java.io.PipedOutputStream;
  4: import java.io.IOException;
  5:

  6: public class PipeIODemo{
  7:  public static void main(String args[]){
     //这里的Reader和Writer不是字符流输入输出的基本类,而是下文自定义的
  8:   Reader thread1=new Reader("1");
  9:   Writer thread2=new Writer("2");
    //联接管道
  10:  try{
  11:   thread2.pipeOut.connect(thread1.pipeIn);
  12:  }catch(IOException ex){
  13:   System.out.println("IOException occured when connecting two stream");
  14:  }
    //启动线程
  15:  thread1.start();
  16:  thread2.start();
    //循环,等线程均结束后程序中止
  17:  do{
  18:  }while(thread1.isAlive()||thread2.isAlive());
  19:  System.out.println("All over!");
  20: }
  21:}
  //自定义读者类
  22:class Reader extends Thread{
  23: public PipedInputStream pipeIn;
  24: String threadName;
  25: public Reader(String name){
  26:  super();
  27:  threadName = name;
  28:  pipeIn = new PipedInputStream();
  29: }
  30: public void run(){
  31:  try{
  32:   boolean over = false;
  33:   while(!over){
  34:    int ch=pipeIn.read();
  35:    try{
  36:     Thread.sleep(200);
  37:    }catch(InterruptedException ex){
  38:     System.out.println("Sleep is interrupted!");
  39:   }
  40:   if(ch=='.') over = true;
  41:    else System.out.println("Thread "+threadName+" read "+(char)ch);
  42:   }
  43:
  44:  }catch(IOException ex){
  45:   System.out.println("IOException occured when try to read data");
  46:  }
  47: }
  48:}
  //自定义写者类
  49:class Writer extends Thread{
  50: public PipedOutputStream pipeOut;
  51: String threadName;
   //待写内容
  52: String content = "orange apple";
  53: public Writer(String name){
  54:  super();
  55:  threadName=name;
  56:  pipeOut = new PipedOutputStream();
  57: }
  58: public void run(){
  59:  try{
     //将字符串内容逐字输出
  60:   for(int i=0;i<content.length();++i){
  61:    pipeOut.write(content.charAt(i));
  62:    try{
  63:     Thread.sleep(200);
  64:    }catch(InterruptedException ex){
  65:     System.out.println("Sleep is interrupted!");
  66:    }
  67:    System.out.println("Thread "+threadName+" wrote "+content.charAt(i));
  68:   }
  69:    pipeOut.write('.');
  70:   }catch(IOException ex){
  71:   System.out.println("IOException occured when try to write data");
  72:  }
  73: }
  74:}
  该程序的运行结果如下:(略)
  这个例子功能很简单。两个线程,一个是读者,一个是写者,读者取写者所写的内容。双方约定以‘.’为结束符。
  这个例子演示了管道I/O一般过程,首先是创建管理I/O流类对象。这个工作是在Reader和Writer类的构造函数中做的(第28、56行)。因此当我们创建了thread1和thread2两个线程时,pipeIn和pipeOut就被创建了。然后我们把它们连接起来,再启动两个线程工作,最后打印“All Over!” 表示运行结束。
  可以看出,读线程与写线程实际上是不必关心对方的情况的。它们的工作就是读或写,每处理一个字符输出一条信息表明自己做过的工作。我们在pipeIn 的输出信息中加了一大段空格,这样的目的是使两个线程的输出能容易分辨。另外,让两个线程处理一个字符就睡眠(sleep) 一会儿并不是必须的,这样只是为了增加线程交替执行的机会。如果去年这一段,可能执行数次者不出现thread1、thread2交替输出信息的现象,容易被误解为两个线程必须一个死亡才执行另一个。另外,作为结束符的“.” 并没有显示出来。
  这个例子实现的是单向通信。实际上,为每个线程都分别创建输入流对象和输出流对象,再分别连接起来,就可以实现双向通信。读者有兴趣不妨一试。

7.7 java.io包中的其它类

  7.7.1 SequenceInputStream类

  这个类的功能是合并多个输入流。其构造函数有两个,一个以枚举(Enumeration)对象为参数,一个以两个InputStream对象为参数。方法则有两个read()方法,分别读一个字符、读数据入字节数组中的一段。再就是一个close()方法。例7.6利用它来实现了两上文件的并接。其中还使用了ByteArrayOutputStream,用意是将两个文件并接的结果先在内存缓冲区中暂存一下。这个例子允许目的的文件是两个源文件之一。
  例7.6 FileCat.java
  import java.lang.System;
  import java.io.*;

  public class FileCat{
   public static void main(String args[]){
    SequenceInputStream seqIn;
    if(args.length!=3){System.out.println("Usage:java FileCat filesrc filesrc filedst");}
    else{
     try{
      FileInputStream f1=new FileInputStream(args[0]);
      FileInputStream f2=new FileInputStream(args[1]);
      seqIn=new SequenceInputStream(f1,f2);
      ByteArrayOutputStream byteArrayOut=new ByteArrayOutputStream();
      boolean eof=false;
      int byteCount=0;
      while(!eof){
       int c=seqIn.read();
       if(c==-1)eof=true;
       else{
        //将读到的数据写入字节数组输出流
        byteArrayOut.write((char)c);
        ++byteCount;
       }
      }
      FileOutputStream outStream=new FileOutputStream(args[2]);
      //将数据写入文件
      byteArrayOut.writeTo(outStream);
      System.out.println(byteCount+" bytes were read.");
      seqIn.close();
      outStream.close();
      byteArrayOut.close();
      f1.close();
      f2.close();
     }catch(FileNotFoundException ex){
      System.out.println("Cannot open source files.Please check if they"+
        "exists and allows freading.");
     }catch(IOException ex){
      System.out.println("IOexception occured!");
     }
    }
   }
  }

  7.7.2 Streamtokenizer类

  这个类是用来构造词法分析器的。缺省情况下,它可以识别数值、字母以及字符串。它的构造函数只有一个,以输入流(inputStream)对象为参数。本节我们给出一个例子(例7.7),并介绍例子中出现的该类的部分方法。
  例7.7 TokenIODemo.java。
  1:import java.io.IOException ;
  2:import java.lang.System;
  3:import java.io.InputStreamReader ;
  4:import java.io.StreamTokenizer;
  5:import java.io.FileInputStream ;
  6:
  7:public class TokenIODemo{
  8: public static void main(String args[]) throws IOException{
    //从文件创建输入流
  9:  FileInputStream fileIn = new FileInputStream ("hello.c");
    //从字节流创建字符流
  10:  InputStreamReader inReader = new InputStreamReader (fileIn);
  11:  StreamTokenizer tokenStream = new StreamTokenizer (inReader);
    //设置注释风格
  12:  tokenStream.slashStarComments(true);
  13:  tokenStream.slashSlashComments (true);
    //识别行结束符;如果参数为假,将行结束符视作空白符
  14:  tokenStream.eolIsSignificant (true);
    //设置引号的符号表示
  15:  tokenStream.quoteChar ('"');
    //将ASCII码为0-32的字符设为空白符
  16:  tokenStream.whitespaceChars (0,32);
  17:  boolean eof = false;
  18:  do{
  19:   int token = tokenStream.nextToken ();
  20:   switch(token){
      //文件结束符
  21:   case tokenStream.TT_EOF :
  22:    System.out.print(" EOF ");
  23:    eof=true;
  24:    break;
      //行结束符
  25:   case tokenStream.TT_EOL :
  26:    System.out.print (" EOL ");
  27:    break;
      //单词
  28:   case tokenStream.TT_WORD :
  29:    System.out.print (" Word "+tokenStream.sval );
  30:    break;
      //数字
  31:   case tokenStream.TT_NUMBER :
  32:    System.out.print(" Number "+tokenStream.nval );
  33:    break;
  34:   default:
  35:    System.out.print(" "+(char)token);
  36:   }
  37:  }while(!eof);
  38:  System.out.flush();
  39: }
  40:}
  下面是该例的运行结果:
  E:/>java TokenIODemo
  # Word include < Word stdio.h > EOL EOL Word main ( ) { EOL Word print ( " ,
Number 1234.0 ) ; EOL EOL } EOL EOF
  E:/>
  其中,hello.c程序的源代码如下:
  #include <stdio.h>
  //To say "hello world"
  main(){
   print("hello world %d/n",1234);
   /* It is a test for TokenIODemo*/
  }
  例子中我们用到了这样一些方法:
  ■public void whitespaceChars(int low,int hi)
  把给定范围的字符设为空格(不可见)字符。类似的方法还有wordChars()(设为单词字符),ordinaryChars()(设置为除了单词字符、数据字符等有实际含义字符之外的其它字符)。
  ■public void slachStarComments(boolean flag)
  ■public void slachSlashComments(boolean flag)
  flag为真,则可训别相应风格的注释。前者(slashStar)指C风格的注释(/*...*/)。后者指C++风格的注释“//”。
  ■public int nextToken()
  从输入流取得下一个词法分析单位。
  ■public void eolIsSingnificant(boolean flag)
  如果参数为真,识别行结束符;否则,将行结束符视作空白符。
  例子中还用到了一些常量和变量。TT_EOF、TT_EOL、TT_NUMBER、TT_WORD分别表示文件结束符、行结束符、数值和单词。public String sval是指字符串值;public double nval指双精度值。这些常量、变量的使用在例子中已有明确的演示,这里就不多说了。

  7.7.3 FilenameFilter接口

  这个接口不太常用,只提供了一个方法:
  ■public abstract boolean accept(File dir,String fileName)
  功能是确定某一文件列表是否包含了指定的文件。

  7.7.4 Serializable接口

  实现这一接口的类可以被“串行化”,即它们的对象可以被转化为某种形式,该形式可以被输入输出,而保存对象的结构。也就是说,只有实现了这一接口,类的对象才能被完整地输入输出和存储。
  该接口不含任何方法和变量,它只充当一个标记。编程时只要在类定义时中上:
  ... implements Serializable
即可使该类的对象具有“串行性” 。

本章小结

  在这一章中,我们比较全面地介绍了java.io包中的类和接口,并给出了示例。读者通过这一章学习,应掌握java的输入输出类,并将种I/O手段灵活运用于自编的程序之中。

posted @ 2006-09-28 10:41 空空 阅读(1) | 评论 (0) | 编辑 收藏
 
JAVA全方位学习
列出java语言的所有重点

 

java 2全方位学习 J2ME无线java应用开发  JAVA手机程序设计入门与应用
1、对于一般PC平台来说,Java的程序分成两大类,一个是在PC的操作系统上通过JVM直接运行的Java Application,另一种是通过浏览器中附带的JVM运行的Java Applet。
2、<applet code="要运行的class文件名称" width="显示的宽度" height="显示的高度"></applet>。
3、javac,java,appletviewer。
4、java是用unicode作为字符集的,所以我们在Java程序中使用中文或是英文甚至是其他的语言作为class名称、变量名称都可以。
5、JFC-java foundation classes,GUI-graphical uesr interface。
6、java -jar Java2D.jar。
7、PDA-个人数据处理。jpda-Java Platform Debugger Architecture。

第4章 程序基本单元
8、关键字与保留字的区别。标志符的魔力。Literal的含义。变量的意义,变量命名的原则。
9、基本数据类型:整型,浮点,其他类型。
10、为什么数值范围正负值分开,无理数或是无穷小数的表示问题。其核心是精度问题。浮点数不存在0这个数值,所以会产生误差。
11、其他数据类型:boolean,char,常用转义字符,特殊字符的表示方法。
12、Java两种变量的模式:成员变量(member variable),局部变量(local variable)。成员变量初始值在声明时就指定了。而局部变量则不会,要求用户自己设定初始值。
13、类型转换分为自动类型转换(promotion)和强制类型转换(casting)两种。其中每种又分为放大(widening)和缩小(narrowing)两种。放大转换属于自动类型转换,缩小转换属于强制类型转换。
14、数据类型的后面加上一个英文字母,是由于Java对于literal默认的数据类型有关,基本上Java对整数的lieral默认为int型,而对于浮点数的literal默认为double型。
15、Java里有个特殊的类,可以像一般的基本数据类型一样使用,它就是String-字符串。

第5章 Java的表达式
16、5%2与5%-2与-5%2与-5%-2的区别。
17、比较运算符的结果只有两种,true和flase。instanceof?
18、逻辑与和一般与的差别。在需要改变变量的值时,用一般与。通常使用逻辑运算符,因为运算速度会快一些。
19、逻辑运算的优先级在比较运算符之下。
20、赋值运算符是所有运算符中最低的。赋值运算符从右边运算到左边。而算术运算符是从左边运算到右边。并且赋值运算符的左边只能有一个变量存在。
21、位运算符(bitwise)。 &,|,^,~(complement)。位运算符只能用于整型数据类型中。位移运算(shift)的用处。位移运算比较难。要理解位移运算的用途。了解减次运算的含义。     
22、运算符的优先级和结合性。

第6章 Java的语句
23、语句有很多种,粗略的把它们分为四类:第一类是一般的语句,第二类是声明语句,第三类是条件流程控制语句,第四类是循环控制语句。
24、对象的声明与变量的声明是不同的。对象在声明后,必须进行实例化,而变量声明是不需要的。
25、?:运算符的使用。
26、Switch的参数只能是(<byte、short、int或char变量>)的其中一种。 
27、for(<控制循环变量初始值设置>;<循环结束判断条件语句>;<控制循环变量值改变方法>){<代码>s}。千万不使用浮点数作为控制变量。由于浮点数误差问题。
28、while(<循环结束判断条件语句>){<代码>s}。
29、do{<代码>s} while(<循环结束判断条件语句>)。
30、高级循环控制-嵌套循环。以及break和continue的使用。
31、标记<token>的使用。以冒号: 作为结束。运用适当的break、continue和token可以增强程序的弹性。但不要乱用token。
32、必须了解注释语句、if语句、switch语句、循环语句、break、continue和标记的使用与限制。
                                   
第7章 Java面向对象程序设计
33、对象是符合某种类定义所产生出来的实例(instance)。类是抽象的,而对象是实在的。属性(attribute)是用来形容一个实例对象的,其实就是变量。方法(method)是对象自己的行为或者是使用它们的方法,其实就是函数。属性和方法称为对象的成员。类可以说是蓝图(blueprint),类中会定义许多产生该类对象时,所必须具备的一些属性与方法。
34、继承(inheritance)和多态(polymorphism)是类的另外两个重要的特性。继承最主要的目的是为了"扩展"原类的功能、加强或改进原类所没有定义的属性及方法。被继承的类为父类,继承的类为子类。采用UML(Unified Modeling Language)的表达方式来设计类,可以画出类关系图,其中最重要的部件是类图标和继承图标。多态的概念比较难理解,需要加强理解,其中还有覆盖(override)的概念。
35、为了增强程序的可读性和易用性。全世界的java程序师都遵守以下的规则:(1)Package(包),作为Package名称的英文单词全部要小写;(2)类,每个英文单词的第一个字母大写;(3)接口,规则与类一样;(4)属性,每个英文单词的第一个字母小写,其他单词的第一个英文字母大写;(5)方法,规则和属性一样,不过后面有小括号;(7)常量,英文单词全部大写,而且每两个英文单词之间用下划线隔开。
36、Animal和Zoo两个类只需要对Zoo.java进行编译即可。这是因为在Zoo.java中所有用到的类如果还没有进行过编译的话,在编译Zoo.java的过程中它们都会被自动编译。
37、构造函数(constuctor),除了可以在Java编译时为我们自动产生之外,还可以自行编写所需要的构造函数。构造函数也是一个方法。
38、在一个类中,有多个构造函数使用相同的名称,但是参数类型与个数却各不相同,我们把这样的一个行为称为构造函数重载(overloading)。
39、原则上重载有两个规则一定要遵守:一、方法名称一定要一样。否则的话,就是两个不同的方法,不能称为重载。二、调用的参数类型一定要不一样。因为编译器要通过参数类型来判断调用的是哪一个方法。
40、面向对象程序设计中一个非常重要的概念,我们称为信息的隐藏(information hidding),专用的技术术语为封装(encapsulatio)。封装的目的有两个:一、保护类中的数据,不让这些数据被错误的使用或破坏;二、隐藏不需要让别人知道的细节,以防别人误用。封装还有一些其他的重要的特点:隐藏类的具体细节;强制用户通过单一接口访问数据;程序更加容易维护。
41、属性访问方法的命名规则:设置属性值的方法以set作为开头;获取属性值的方法以get作为开头;Boolean数据类型值的获取用isXXX形式来命名。
42、类的多态的,指类在不同情况下,可以看作是不同的类。
43、类成员和实例成员。前面介绍的属性和方法,属于对象等级的,称为实例成员。类成员必须使用限定词static。类成员的调用方法为<类名称>.<类成员名称>,也可以为<对象名称>.<类成员名称>。但是类方法中不能使用实例成员。
44、子类产生对象时,会往上通知它的父类,它的父类又会通知父类的父类,持续这个操作直到最上层的java.lang.Object类。通知上层父类最主要的目的时,对于那些继承自父类的属性或其他的设置做初始化的操作。从而达到 程序代码的重复使用,这也是继承的目的。Java在编译时自动帮我们加上通知父类的程序代码,是加在构造函数里面。super();这行的意思必须明白。super是关键字。
45、调用super函数必须注意两点:一、super调用必须在构造函数的第1行。二、如果子类中有好几个不同的构造函数,二父类又没有不需要参数的构造函数,那么就必须在子类中的每个构造函数的第一行加上适当的super调用。
46、构造函数调用构造函数用this这个关键字。
47、super和this在使用上有一些要注意的地方:一、super和this只能使用在构造函数程序代码中的第一行;二、super和this同时只能使用一种;三、super和this的调用只能使用在构造函数中;四、如果构造函数中没有使用super或this,那么Java会自动帮你加上super()调用。
48、屏蔽(shadow)-属性(继承关系)、覆盖(override)-方法(继承关系)、重载(overload)-方法(同一个类下函数同名,但参数不同)。使用属性的几种方法必须了解,super、this、强制类型转换的使用。

第8章 深入Java面向对象程序设计
49、什么是包(package)?包很像我们计算机中的目录或是文件夹。目录分隔符,dos用/,unix用/。目录机制应用于Java面向对象的程序当中就是所谓的包。
50、package语句中,原本的目录分隔符改用句点[.]来代替。package <package名称>;必须注意:package语句一定只能写在程序代码的第一行。package的UML图示。除了每一个类的Java文件中的第一行设置package外,程序代码中用到其他类的地方也一并加上它的package的名称。package的设置与使用。
51、import语句,必须写在package语句之后,所有类声明之前。import语句的通配符(*)。使用通配符只是将该package下的类import进来,不会把子目录下的其他目录中的类import进来。
52、classpath的设置。classpath是Java程序在编译与运行时会使用到的一个【环境变量】,它的主要用途是告诉编译器去哪里找到编译或运行时所需要的类。windows默认情况下classpath为【.】。设置classpath的两种方法:一、直接设置;二、javac加上-classpath这个参数。
53、访问权限的限定词的使用。protected与default的使用。成员限定词有四个,而类声明限定词只有两个。
54、final限定词的使用-用于常量。final和static的使用。
55、抽象类的使用。关键字是abstract。能让继承的子类一定覆盖某个特殊的方法,这种机制就是【抽象(abstract)】。在一个类中,我们可以指定某个方法为抽象的,而一个抽象的方法不需要编写方法的内容,也就是说当方法声明完后,就直接以分号【;】来结束,不用加上左右大括号。只要有任何一个抽象方法,那么这个类就必须成为一个抽象类,所以我们必须把类的声明加上abstract这个关键字。抽象类不能生成对象实例。abstract只能用在类和方法上。属性和变量上没有意义。
56、接口-一种标准、一些规范。在java中,接口是由一些常量和抽象方法所组成的。关键字是【interface】,使用的格式如下:<限定词>interface<接口名称>[extends<接口名称>s]。解决abstract不能解决的问题。接口中方法的语法声明跟抽象方法的语法声明是一样的,就是是只有方法的声明,而没有方法本身,简单的说就是声明完后直接以分号结束整个语句,而不用再加上大括号。接口中的方法也都全部是抽象方法,只是不需要额外加上abstract这个关键字。extends <类名>,implements <接口名>。简单的说,接口就是一个完全抽象的类。Java用多重接口的方法来完成类的多重继承机制。implements<接口1>,<接口2>......。java中存在一种特殊的接口,它只有接口的声明,而内部是空的,也就是说完全没有任何的常量和方法的声明。这种特殊大额接口称为【标记接口(marker interface)】。

第9章 Object类的常用方法介绍
57、对象之间的比较,第一种观点,对象相等是指对象为同一个。包括使用的内存。直接用==。第二种观点,是两个对象的内容是否相等。用equals方法。
58、理解hash code的含义。
59、引用(reference)与复制(clone)的区别。clone方法的使用。
60、将对象转为字符的方法【toString】。
61、在设计的类的时候,最好也一并的把这几个Object的方法覆盖。

第10章 深入内存
62、变量内存的使用,变量声明后,编译器就分配了内存。
63、对象内存的使用,对象声明后,编译器只是在内存中产生一个对象的引用(reference),它所存放的并不是一个真正的对象实例 ,因为对象的实例我们还没生成。所以当一个对象被声明后,在内存中这个对象引用的初始值会是【null】。我们用new这个关键字,配合调用类的构造函数,来生成对象实例。但,此时对象引用与对象实例并没有产生关联。需要使用复制语句使它们关联。每个对象引用占用4个字节的内存空间。对象的引用所存放的是撒对象实例真正在内存中的地址。对象引用实际占用的内存大小,跟系统(JVM)实现的方法有关,不同的系统大小不一定相同。
64、什么是数组,数组是存放量大、性质相同且需要做相同处理的数据。数组可以用在基本数据类型的变量上,当然也可以用在对象上。数组与对象有点相似,分成两个阶段--数组引用的声明和数组实例的生成。数组声明格式如下:int a[];和int []a;数组声明完后,在内存中表现的方法也跟对象一样,也是个引用,而且初始值也是null。生成数组实例同样要用到new关键字,在这之后要用赋值语句进行关联处理。也可以同时创建和初始化数组,如 int a[]={1,2,3,4}。
65、数组的索引(index),是[]里面的数字,它表示这个数组中的第几笔数据。数组的使用比较简单,就是在数组变量的名称后,加上要访问的索引。索引从0开始到数组的大小减1。
66、数组的length属性获得数组的大小。必须注意数组的大小是不能改变的。使用上比较有弹性。
67、数组的复制。数组不是继承自java.lang.Object类的对象,所以没有clone这个用来复制对象实例的方法。可以利用循环赋值来实现。java中提供了【System.arraycopy】方法。使用这个方法时需要5个参数,依次是源数组、来源数组数据起始位置、目的数组、目的数组数据起始位置、复制数据的个数。使用上比较有弹性。arraycopy方法只适用于基本数据类型的数组。相比而言,第二种方法使用JNI的方法,所以速度上会比较快。arraycopy的三种异常,NULLPointerException,ArrayIndexOutOfBoudsException,ArrayStroeException。
68、Java 本机接口(Java Native Interface (JNI))是一个本机编程接口,它是 Java 软件开发工具箱(Java Software Development Kit (SDK))的一部分。JNI 允许 Java 代码使用以其它语言(譬如 C 和 C++)编写的代码和代码库。Invocation API(JNI 的一部分)可以用来将 Java 虚拟机(JVM)嵌入到本机应用程序中,从而允许程序员从本机代码内部调用 Java 代码。
预备知识
所有示例都是使用 Java、C 和 C++ 代码编写的,并可以移植到 Windows 和基于 UNIX 的平台上。要完全理解这些示例,您必须有一些 Java 语言编程经验。此外,您还需要一些 C 或 C++ 编程经验。严格来说,JNI 解决方案可以分成 Java 编程任务和 C/C++ 编程任务,由不同的程序员完成每项任务。然而,要完全理解 JNI 是如何在两种编程环境中工作的,您必须能够理解 Java 和 C/C++ 代码。
系统需求
浏览器:Netscape 4.x 或更高版本, 或者 Internet Explorer 4.x 或更高版本 ,支持 JavaScript 。 要运行本教程中的示例,您需要下列工具与组件: Java 编译器:随 SDK 一起提供的 javac.exe。 Java 虚拟机(JVM):随 SDK 一起提供的 java.exe。 本机方法 C 文件生成器:随 SDK 一起提供的 javah.exe。 定义 JNI 的库文件和本机头文件。jni.h C 头文件、jvm.lib 和 jvm.dll 或 jvm.so 文件,这些文件都是随 SDK 一起提供的。 能够创建共享库的 C 和 C++ 编译器。最常见的两个 C 编译器是用于 Windows 的 Visual C++ 和用于基于 UNIX 系统的 cc。
68、多维数组在内存中的样子。必须理解。不规则数组的生成。 不规则数组是Java语言的一个重要特点,其他的程序语言像C或是Basic,都只能声明规则的多维数组,而且维数有上限。java没有这个限制。
69、以上讨论基本数据类型的数组,现在来看对象数组。防止NullPointerException异常的产生。
70、变量的访问范围【scope】,有点像类之间属性及方法访问的限制,这些限制是由于访问权限的限定词、package和继承这几种关系组合起来的。变量访问范围大致分为四个等级:第一、类级(static);第二、对象实例级;第三、方法级;第四、局域级。怎么样区分这几个级别,必须注意。必须理解这四个等级。访问范围和视野的关系刚好相反。内存存在的时间。
71、参数的传递。以前的程序语言概念参数的传递有两种方法,一是【传值(call by value)】,另一个是【传址(call by reference)】。但java里面只有传值这种方式。基本数据类型参数值传递与类对象型参数值传递是不同的。
72、内存回收(garbage collection)。负责运行这个机制的就是【garbage collector】。对象声明包括两部分:对象引用和对象实例。如果一个对象实例不被任何对象引用指到的话,但启动GC时,就会把对象实例回收回去,并把内存释放调。取消对象引用,只要将它指定为【null】即可。GC是不定时的启动,也可以手动调用它,方法是【System.gc()】,它会调用【Runtime.getRuntime.gc()】,这两个方法都可以使用。finalize方法同样也是Object类常用的一个方法,与GC有关。它是在对象被回收前,GC所调用的方法。回收顺序与对象实例的生成顺序有关。既是我们手动调用System.gc(),GC也不见得一定运行,GC正确的启动时间无法得知。                                                                                                                               
第11章 Application与Applet
73、Application的输出,System类的out属性是PrintStream对象,有prinlin方法和print方法,也可以用err属性是PrintStream对象,有prinlin方法和print方法,但有区别,out属性可以重定向(redirected),err属性只能输出到默认的设备上。Application默认的就是所打开的命令行窗口,Applet默认的就是Java Console。 可以用>来实现重定向。println和print方法都是属于重载的方法,除了可以接受八种基本数据类型和String类型作为参数外,还可以接受一般的对象作为参数,编译器会自动调用这个对象的【toString】方法,char数组也可以作为这两个方法的参数。
74、Application的输入,分为两种,一是参数输入(命令行参数),Wrapper类的使用。基本数据类型与Wrapper类的对应关系。Wrapper类种有相应的parseXXX方法来实现字符串转换为基本数据类型。二是标准输入,System类的in属性是InputStream对象,有read方法来读取输入,读进来是byte对类型,需要转化为其他类数据型。通常使用InputStreamReader类,然后连接到BufferedReader类,用BufferedReader类提供的读取字符串的方法【readLine】。
75、系统参数的获取用【System.getProperties】方法。必须注意系统参数与命令行参数的区别。
76、System类的使用。setXXX(setOut、setErr、setIn),setProperties和SetProperty的区别。System.exit(n),虚拟机会调用Runtime.getRutime.exit(n)方法。currentTimeMills。
77、Runtime类的使用。可以通过exec这个方法来运行一个外部程序。
78、Appplication必须有一个main的方法,符合四个条件。而一个Applet一定要继承java.applet.Applet类。main方法是Application运行的起始点,而Applet运行的起始点在init方法上。Applet中,System.out或System.err方法来输出,但System.in方法不能用。Applet有自己的输入方式,类似于命令行参数的方式。在HTML文件中,加上参数语法,<param name=<参数名称> value=<参数值>>。然后在java程序中,应用java.applet.Applet类的【getParam】方法。
79、Applet基本方法的使用。init、start、stop、destroy、paint。destroy和finalize方法的区别在于使用地点不同。paint方法的使用。paint方法中有个属性是Graphics对象,注意Graphics类的【drawString】方法的使用。
80、java的安全性,是指Applet满足java指定的重重安全规范。四点限制措施。
81、Application和Applet的结合。Java Web Start是取代Applet的机制。

第12章 异常处理
82、异常是指程序在运行的过程中,由于编写程序的倏忽,外在环境的因素,或是电脑系统本身的问题,都可能导致程序运行时产生错误,造成死机或是计算的结果不正确,这些突发的状况称为异常。 异常处理是指当程序出现异常时,能够作出一些应变的处理。
83、java.lang.Throwable类。Exception类(可以控制)和Error类(无法控制)。
84、RuntimeException常见的有以下几种:ArithmeticException、ArrayIndexOutOfBoundsException、ArrayStoreException、ClassCastException、IllegalArgumentException、NativeArraySizeException、NullPointerException、SecurityException。由于自己编程引起的。
85、CheckedException常见的有以下几种:ClassNotFoundExecption、FileNotFoundException、InterrupedException、IOException、SQLException。一些外部因素引起的。
86、Error有OutOfMemoryError、StackOverflowError、UnknowError、AWTError、ThreadDeath。系统级且非常严重的错误。错误原因是内存不足或者是运行时挂起。
87、捕捉和处理异常。主要是针对CheckedException类的异常。try、catch、finally三个关键字的使用。处理异常包括以下两个步骤:一、把异常的名称及一些相关的信息显示出来,二、用最安全的方法恢复程序的运行。显示异常信息用到,toString、getLocalizedMessage、getMessage、printStackTrace方法。其中printStackTrace方法有三种不同的重载。弹性(flexibility)。 finally关键字的使用注意以下三点:一、没有异常产生-》进finally区块-》方法中剩下未运行的程序代码。二、有异常产生-》捕捉到-》进catch区块-》finally区块-》方法中剩下未运行的程序代码;三、有异常产生-》没有捕捉到-》进finally区块-》方法中剩下未运行的程序代码。                   
88、注意异常捕捉的顺序。越上层的类,越放在下面。
89、throws关键字的使用,在方法声明上抛出异常。throw关键字,在方法内部抛出异常。必须注意抛出RuntimeException和CheckedException两种异常在使用上的差别。
90、定义自己的Exception。
91、抛出异常方法覆盖的问题。注意两点:一、不可抛出原有方法抛出异常类的父类或上层类;二、抛出的异常类数目不能笔原有的方法抛出的还多。主要是因为编译时抛出的异常类无法自动转化为父类中所声明的异常类。

第13章 容器(Container)与布局(Layout)
92、什么是AWT(Abstract Windowing Toolkit),什么是GUI(Graphical User Interface)图形用户接口。AWT包的结构图。
93、Container包括Frame和Panel。Frame是先构造,然后setSize,然后再setVisible。理解Deprecation。Frame的常用方法。记住【Ctrl】+【C】来停止程序的方法。Panel不能独立出现在画面上,必须放在某个Container中才行,例如Frame或浏览器里面。Applet本身就是一个panel。add方法的使用。
94、什么是Layout。【setLayout(<xxxLayout>)】方法。有五个基本的Layout类。Frame默认的布局是BorderLayout类。Panel默认的布局是FlowLayout。另外还有CardLayout、GridLayout、GridBagLayout。也可以设计自己的Layout类。
95、pack和setSize方法的区别。                                                     
96、如果不使用Layout,可以使用【setSize】和【setLocation】方法来代替。最好使用Layout类。                                                                                      
第14章 事件处理
97、什么是事件【event】。事件就是别人给予它的一些操作。明白事件处理结构:事件本身、事件产生的来源、谁来处理事件。
98、什么是委托处理模式【Delegation Model】。事件处理的机制。
99、AWT Event类的结构图。分为两类:Low-level和senmantic。
100、【Listener】这个接口(interface)与【Adapter】类的相对应。
101、一个对象可以委托好几个类来处理相同的事件,一个处理事件的类也可以同时处理不同对象所产生的事件。这种情况称为【multiplexer】。
102、WindowEvent、MouseEvent、KeyEvent类事件处理的接口,类和方法。以及其他常用的Low-level Event类,分别是ContainterEvent和FocusEvent。                           
103、Swing是Java所设计的另外一组更丰富、功能更多的GUI空间。理解Swing和AWT的区别。

posted @ 2006-09-28 10:33 空空 阅读(3) | 评论 (0) | 编辑 收藏
 
java初学者备忘录
 一.异常

  Java对异常的处理同Delphi一样,不是刻意的去避免它的发生,而是等它发生后去补救.

  Delphi的异常处理简单来说就是一下语句

Try
Except//异常发生后就转入此处执行
Finally//不管异常发不发生,都转入此处运行
End

  与此相类似,Java的异常处理的基本形式如下

try{
}catch(ExceptionType1 e){
file&://对/异常情况1的处理
}catch(ExceptionType2 e){
file&://对/异常情况2的处理
throw(e)//抛出异常,和Delphi中的raise是一回事
}

  要补充的是,对大多数的异常,假如你要在正常运行的程序中而不是捕捉异常的程序中明确的抛出,Java的编译器需要你事先对你要抛出的异常作声明,否则不允许编译通过.这个任务是由throws来完成的.

  二.Java的输入输出流

  2.1 输出

System.out.print  file&://这/里out是一个静态方法哦
System.out.println
System.err.print  file&://err/和out一样也是标准输出,至于有什么不同,我目前还不清楚
System.err.println

  2.2 输入

System.in.read()

  2.3 文件的操作

  只需要几个带注释的例子就可以了。

  第一个是一个显示文件基本信息的程序

import java.io.*;//调入和io相关的类
class fileinfo{
file&://注/意,main函数一定是静态方法

 public static void main(String args[])throws IOException{
  File fileToCheck;//使用文件对象创建实例
  if (args.length>0){
   for (int i=0;i
    fileToCheck=new File(args[i]);//为文件对象分配空间
    info(fileToCheck);//这里引用的info一定要是静态方法成员
   }
  }
  else{
   System.out.println("no file given");
  }
 }

 public static void info(File f)throws IOException{
  System.out.println("Name:"+f.getName());
  System.out.println("Path:"+f.getPath());
  if (f.exists()){
   System.out.println("File exists.");
   System.out.print((f.canRead()?" and is Readable":""));//判断函数,如果满足条件,输出前者,否则输出后者
   System.out.print((f.canWrite()?"and is Writable":""));
   System.out.print(".");
   System.out.println("File is"+f.length()+"bytes.");
  }
  else{
   System.out.println("File does not exist.");
  }
 }
}

  第二个例子是一个存储电话信息的小程序,用户输入姓名和电话号码,程序将其存入phone.numbers文件中,通过FileOutputStream来实现

import java.io.*;

class phones{
 static FileOutputStream fos;
 public static final int lineLength=81;
 public static void main(String args[])throws IOException{
  byte[] phone=new byte[lineLength];
  byte[] name=new byte[lineLength];
  int i;
  fos=new FileOutputStream("phone.numbers");
  while(true){
   System.err.println("Enter a name(enter ‘done‘ to quit)");
   readLine(name);
   if ("done".equalsIgnoreCase(new String(name,0,0,4))){
    break;
   }
   System.err.println("Enter the phone number");
   readLine(phone);
   for (i=0;phone[i]!=0;i++){
    fos.write(phone[i]);
   }
   fos.write(‘,‘);
   for (i=0;name[i]!=0;i++){
    fos.write(name[i]);
   }
   fos.write(‘n‘);
  }
  fos.close();
 }

 private static void readLine(byte line[])throws IOException{
  int i=0,b=0;
  while((i<(lineLength-1))&&((b=System.in.read())!=‘n‘)){
   line[i++]=(byte)b;
  }
  line[i]=(byte)(0);
 }
}
2.4 流

  无非是两种

  输出流,让我们来写的

  输入流,给我们来读的

  java.io包中有很多种类的输入输出流:

  1.FileInputStream和FileOutputStream 节点流

  2.BufferedInputStream和BufferedOutputStream 过滤流

  3.DataInputStream和DataOutputStream 增强的过滤流

  4.PipedInputStream和PipledOutputStream 用于线程的流

  掌握了流的概念,就可以开始Sockets的学习了.关于Socket的作用,昨天我已经讲了.

  现在,我们将创建一个简单的通讯程序,以获得对Socket的实质性的认识.该程序包括两个部分,客户机(RemoteFileClient)和服务器(RemoteFileServer).客户机向服务器发出请求,要求读取服务器上的文件信息.服务器将响应请求,将相应的文件信息传给客户机,将相应的文件信息传给客户机.

  首先我们创建RemoteFileClient类:

import java.io.*;//java.io 包提供对流进行读写的工具,也是与 TCP 套接字通信的唯一途径
import java.net.*;//java.net 包提供套接字工具。

public class RemoteFileClient {
  protected String hostIp;
  protected int hostPort;
  protected BufferedReader socketReader;//负责读数据的对象
  protected PrintWriter socketWriter;//负责写数据的对象

  file&://类/的构造器有两个参数:远程主机的 IP 地址(hostIp)和端口号(hostPort)各一个.构造器将它们赋给实例变量

  public RemoteFileClient(String aHostIp, int aHostPort) {
    hostIp = aHostIp;
    hostPort = aHostPort;
  }
  public static void main(String[] args) {
  }
file&://连/接到远程服务器
  public void setUpConnection() {
  }
file&://向/远程服务器请求文件信息
  public String getFile(String fileNameToGet) {
  }
file&://从/远程服务器上断开
  public void tearDownConnection() {
  }
}

  首先来实现main()

public static void main(String[] args) {
  RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000);//为了方便调试,我们把本地服务器当作远程服务器
  remoteFileClient.setUpConnection();//连接。不能直接使用setUpConnection,因为它是非静态变量,需要创建实例后,对实例进行引用,可以看我第一天的日记,上面写的非常详细
  String fileContents =
    remoteFileClient.getFile("RemoteFile.txt");//读取

  remoteFileClient.tearDownConnection();//断开

  System.out.println(fileContents);//输出读取内容
}

  步骤非常清楚.那么我们分别看连接,读取,断开是怎么实现的

  1.连接

public void setUpConnection() {
  try {
    Socket client = new Socket(hostIp, hostPort);//创建Socket对象

     OutputStream outToServerStream=client.getOutputStream();
     InputStream inFromServerStream=client.getInputStream();
     socketReader = new BufferedReader(new InputStreamReader(inFromServerStream));
file&://把/Socket的InputStream包装进BufferedReader 以使我们能够读取流的行

     socketWriter = new PrintWriter(outToServerStream);
file&://把/Socket的OutputStream包装进PrintWriter 以使我们能够发送文件请求到服务器

  } catch (UnknownHostException e) {
    System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);
file&://对/Socket对象创建错误的异常处理
  } catch (IOException e) {
    System.out.println("Error setting up socket connection: " + e);
file&://对/IO错误的异常处理
  }
}

  2.读取

public String getFile(String fileNameToGet) {
  StringBuffer fileLines = new StringBuffer();//StringBuffer对象也是String对象,但是比它更灵活,这里是用来存放读取内容的

  try {
    socketWriter.println(fileNameToGet);
    socketWriter.flush();//文件存放地址输出到socketWriter中,然后清空缓冲区,让这个地址送到服务器中去

    String line = null;
    while ((line = socketReader.readLine()) != null)
      fileLines.append(line + "n");
file&://既/然已经发送到服务器去了,那我们都要等待响应,这里的程序就是等待服务器把我们所需要的文件内容传过来
  } catch (IOException e) {
    System.out.println("Error reading from file&: " + fileNameToGet);
  }

  return fileLines.toString();//别忘了把buffer中的内容转成String再返回
}

  3.断开

public void tearDownConnection() {
  try {
    socketWriter.close();
    socketReader.close();
  } catch (IOException e) {
    System.out.println("Error tearing down socket connection: " + e);
  }
}

  tearDownConnection() 方法只别关闭我们在 Socket 的 InputStream 和 OutputStream 上创建的 BufferedReader 和 PrintWriter。这样做会关闭我们从 Socket 获取的底层流,所以我们必须捕捉可能的 IOException

  好,现在可以总结一下客户机程序的创建步骤了

  1.用要连接的机器的IP端口号实例化Socket(如有问题则抛出 Exception)。

  2.获取 Socket 上的流以进行读写.

  3.把流包装进 BufferedReader/PrintWriter 的实例.

  4.对 Socket 进行读写.具体说来,就是在Writer上传送文件地址信息给服务器,在Reader上读取服务器传来的文件信息
5.关闭打开的流。

  下面是RemoteFileClient 的代码清单

import java.io.*;
import java.net.*;

public class RemoteFileClient {
  protected BufferedReader socketReader;
  protected PrintWriter socketWriter;
  protected String hostIp;
  protected int hostPort;

  public RemoteFileClient(String aHostIp, int aHostPort) {
    hostIp = aHostIp;
    hostPort = aHostPort;
  }
  public String getFile(String fileNameToGet) {
    StringBuffer fileLines = new StringBuffer();

    try {
      socketWriter.println(fileNameToGet);
      socketWriter.flush();

      String line = null;
      while ((line = socketReader.readLine()) != null)
        fileLines.append(line + "n");
    } catch (IOException e) {
      System.out.println("Error reading from file&: " + fileNameToGet);
    }

    return fileLines.toString();
  }
  public static void main(String[] args) {
    RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000);
    remoteFileClient.setUpConnection();
    String fileContents = remoteFileClient.getFile("RemoteFile.txt");
    remoteFileClient.tearDownConnection();

    System.out.println(fileContents);
  }
  public void setUpConnection() {
    try {
      Socket client = new Socket(hostIp, hostPort);

      OutputStream outToServerStream=client.getOutputStream();
      InputStream inFromServerStream=client.getInputStream();
      socketReader = new BufferedReader(new InputStreamReader(inFromServerStream));
      socketWriter = new PrintWriter(outToServerStream);

    } catch (UnknownHostException e) {
      System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);
    } catch (IOException e) {
      System.out.println("Error setting up socket connection: " + e);
    }
  }
  public void tearDownConnection() {
    try {
      socketWriter.close();
      socketReader.close();
    } catch (IOException e) {
      System.out.println("Error tearing down socket connection: " + e);
    }
  }
}

  好了,现在来看服务器端的程序怎么写.

  创建RemoteClientServer类:

import java.io.*;
import java.net.*;

public class RemoteFileServer {
  protected int listenPort = 3000;
  public static void main(String[] args) {
  }
  public void acceptConnections() {
  }
  public void handleConnection(Socket incomingConnection) {
  }
}

  跟客户机中一样,首先导入 java.net 的 java.io。接着,给我们的类一个实例变量以保存端口,我们从该端口侦听进入的连接。缺省情况下,端口是 3000。

acceptConnections()将允许客户机连接到服务器
handleConnection()负责与客户机 Socket 交互以将您所请求的文件的内容发送到客户机。

  首先看main()

public static void main(String[] args) {
  RemoteFileServer server = new RemoteFileServer();
  server.acceptConnections();
}

  非常简单,因为主函数无非是让服务器进入监听状态,所以直接调用acceptConnection().需要注意的是,必须先创建RemoteFileServer()的实例,而不是直接调用.

  那么服务器是怎样通过acceptConnection()来监听客户机的连接呢?并且如果兼听到了,又怎样处理呢?我们来看

public void acceptConnections() {
  try {
    ServerSocket server = new ServerSocket(listenPort);//同客户机的Socket对应,在服务器端,我们需要ServerSocket对象,参数是兼听的端口号
    Socket incomingConnection = null;//创建一个客户端的Socket变量,以接收从客户端监听到的Socket
    while (true) {
      incomingConnection = server.accept();//调用该 ServerSocket 的 accept() 来告诉它开始侦听,
      handleConnection(incomingConnection);
    }
file&://不/断监听直到来了一个连接请求,然后交由handleConnection处理
  } catch (BindException e) {
    System.out.println("Unable to bind to port " + listenPort);
  } catch (IOException e) {
    System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);
  }
}

  无论何时如果创建了一个无法绑定到指定端口(可能是因为别的什么控制了该端口)的 ServerSocket,Java 代码都将抛出一个错误。所以这里我们必须捕捉可能的 BindException。同时,与在客户机端上时一样,我们必须捕捉 IOException,当我们试图在 ServerSocket 上接受连接时,它就会被抛出。可以通过用毫秒数调用 setSoTimeout() 来为 accept() 调用设置超时,以避免实际长时间的等待。调用 setSoTimeout() 将使 accept() 经过指定占用时间后抛出 IOException

  最关键的处理在handleConnection()中,这时已经连接到了客户端的Socket,要从该Socket中读取客户端的请求并且响应。

public void handleConnection(Socket incomingConnection) {
  try {
    OutputStream outputToSocket = incomingConnection.getOutputStream();
    InputStream inputFromSocket = incomingConnection.getInputStream();

file&://首/先获取同Socket相关联的流outputToSocket和InputStream
file&://其/中outputToSocket是要返回给客户端Socket的流
file&://InputStream/是客户端发来的请求,在这里就是文件路径,即"RemoteFile.txt"

    BufferedReader streamReader =
      new BufferedReader(new InputStreamReader(inputFromSocket));

file&://首/先要将InputStream转换到BufferedReader中

    FileReader fileReader = new FileReader(new File(streamReader.readLine()));
file&://从/BufferedReader中读出文件路径,建立新对象FileReader

    BufferedReader bufferedFileReader = new BufferedReader(fileReader);

file&://再/次建立BufferedReader对象,这一次它读取得是文件里面的内容

    PrintWriter streamWriter =
      new PrintWriter(OutputStream);

file&://把/Socket的outputToSocket流包装进PrintWriter 以使我们能够发送文件信息到客户端

    String line = null;
    while ((line = bufferedFileReader.readLine()) != null) {
      streamWriter.println(line);
    }
file&://从/bufferedFileReader中读出文件信息,再经由streamWriter输出到客户端

    fileReader.close();
    streamWriter.close();//注意Socket的两个流关闭的顺序
    streamReader.close();
file&://完/成之后关闭所有流

  } catch (Exception e) {
    System.out.println("Error handling a client: " + e);
  }
}

  请注意完成所有操作之后关闭流的顺序,streamWriter的关闭在streamReader的关闭之前。这不是偶然的,假如将关闭次序颠倒过来,客户端将不会获取到任何文件信息,你可以调试一下看看.这是为什么呢?原因是如果你在关闭 streamWriter 之前关闭 streamReader,则你可以以往 streamWriter中写任何东西,但没有任何数据可以通过通道(通道被关闭了).但奇怪的是,我不是已经在之前的streamWriter.println()中输出了吗?难道非要等到所有的流关闭之后输出到客户端的信息的东西才能到达?我试着将

streamWriter.close();
streamReader.close();

  屏蔽掉,看是否依然能够实现正常的通信,结果发现不行,程序死机.可能是因为通道没有闭合导致的.那么至少可以说明,只有将通道按某种顺序正常关闭,才能完成通讯数据的传输,否则客户端收不到信息.

  最后依然是总结一下创建服务器端程序的步骤

  1.用一个你想让它侦听传入客户机连接的端口(比如程序中的3000)来实例化一个 ServerSocket(如有问题则抛出 Exception)。

  2.循环调用ServerSocket的accept()以监听连接

  3.获取客户端的Socket流以进行读写操作

  4.包装流

  5.对客户端的Socket进行读写

  6.关闭打开的流(切记,永远不要在关闭 Writer 之前关闭 Reader),完成通信

  下面是

RemoteFileServer 的代码清单

import java.io.*;
import java.net.*;

public class RemoteFileServer {
  int listenPort;
  public RemoteFileServer(int aListenPort) {
    listenPort = aListenPort;
  }
  public void acceptConnections() {
    try {
      ServerSocket server = new ServerSocket(listenPort);
      Socket incomingConnection = null;
      while (true) {
        incomingConnection = server.accept();
        handleConnection(incomingConnection);
      }
    } catch (BindException e) {
      System.out.println("Unable to bind to port " + listenPort);
    } catch (IOException e) {
      System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);
    }
  }
  public void handleConnection(Socket incomingConnection) {
    try {
      OutputStream outputToSocket = incomingConnection.getOutputStream();
      InputStream inputFromSocket = incomingConnection.getInputStream();

      BufferedReader streamReader = new BufferedReader(new InputStreamReader(inputFromSocket));

      FileReader fileReader = new FileReader(new File(streamReader.readLine()));

      BufferedReader bufferedFileReader = new BufferedReader(fileReader);
      PrintWriter streamWriter = new PrintWriter(outputToSocket);
      String line = null;
      while ((line = bufferedFileReader.readLine()) != null) {
        streamWriter.println(line);
      }

      fileReader.close();
      streamWriter.close();
      streamReader.close();
    } catch (Exception e) {
      System.out.println("Error handling a client: " + e);
    }
  }
  public static void main(String[] args) {
    RemoteFileServer server = new RemoteFileServer(3000);
    server.acceptConnections();
  }
}