java.io

Java.io

学习目标

Ø 掌握文件类(File类)的使用

Ø IO输入/输出流的概念

Ø IO的分类

Ø 掌握IO包中的流的基本应用

Ø 了解字符编码问题

Ø 掌握对象序列化(串行化)

1.      File

v  什么是文件?

               文件可认为是相关记录或放在一起的数据的集合

v  文件一般存储在哪里?

v  JAVA程序一般通过什么去访问文件属性?  

          JAVA API java.io.File

 

         File类是IO包中唯一代表磁盘文件本身的对象,File类定义了一些与平台无关的方法来操作文件,通过调用File类提供的各种方法,能够创建,删除,重命名文件,判断文件的读写权限及是否存在,设置和查询文件的最近修改时间。

File file = newFile(“d://a.text”);

System.out.println("文件或目录是否存在:" + file.exists());

System.out.println("是文件吗:" +  file.isFile());

System.out.println("是目录吗:" + file.isDirectory());

System.out.println("名称:" +  file.getName());

System.out.println("路径: " + file.getPath());

System.out.println("绝对路径: " + file.getAbsolutePath());

System.out.println("最后修改时间:" + file.lastModified());   

           System.out.println(“文件大小:” + file.length()+ “ 字节”);

           …………………

不同的操作系统对于文件系统路径的设置各有差别。例如在Windows中,一个路径的表示法可能是:

"C:\\Workspace\\CH14\\"

而在Linux下的路径设置可能会像是:

"/home/justin/workspace/ch14"

Windows的路径指定是使用UNC(Universal Naming Convention)路径名,以\\开始表示硬盘根目录。如果没有以\\开始表示相对于当前工作目录的路径,C是可选的硬盘指定,后面跟随着:字符。而UNIX-LikeLinux)系统没有Windows系统的CDE这样的硬盘驱动器概念,UNIX-Like系统的路径指定以/开始表示从根目录开始的绝对路径,不以/开始表示相对于当前工作目录的路径。

在程序中设置路径时会有系统相依性的问题,java.io.File类提供一个抽象的、与系统独立的路径表示。给它一个路径字符串,它会将其转换为与系统无关的抽象路径表示,这个路径可以指向一个文件、目录或是URI(Uniform Resource Identifier)

一个File的实例被建立时,它就不能再被改变内容。File实例除了用作一个文件或目录的抽象表示之外,它还提供了不少相关操作方法:可以用它来对文件系统作一些查询与设置的动作。要注意的是,不管是文件还是目录,在Java中都是以File的实例来表示。

范例--是一个设置与操作File实例的简单示范,可以指定查询某个目录下的所有文件与目录名称。

File类范例 FileDemo.java

package onlyfun.caterpillar;
 
  import java.io.*;
  import java.util.*;

public class FileDemo{
      
public static void main(String[]  args){
            try {
                 File file = new  File(args[0]);
                 if(file.isFile()){ // 是否为文件

                     
System.out.println(args[0]  + " 文件");
                     
System.out.print(file.canRead()  ? "可读 " : "不可读
");
                     
System.out.print(file.canWrite()  ? "可写 " : "不可写
");
              
       System.out.println(file.length() +  "字节
");
                
}else {
                      // 列出所有的文件及目录

                     
File[] files =  file.listFiles();
                      ArrayList<File>  fileList = new ArrayList<File>();
                      for(int i = 0; i  < files.length; i++) {
                           // 先列出目录
                          
if(files[i].isDirectory())  { //是否为目录

                               
// 取得路径名
                               
System.out.println("["  + files[i].getPath() + "]");
                           }else {
                                // 文件先存入fileList,待会再列出
                               
fileList.add(files[i]); 
                           }
                      }
                      // 列出文件
                     
for(File f: fileList)  {
                           System.out.println(f.toString());
                      }
                      System.out.println(); 
                 }
            }  catch(ArrayIndexOutOfBoundsException e) {
                 System.out.println("using:  java FileDemo pathname");
            }
       }
  }

执行结果:

java onlyfun.caterpillar.FileDemo C:\
  [C:\WINDOWS]
  [C:\workspace]
  [C:\Documents and Settings]
  [C:\Program Files]
  [C:\System Volume Information]
  C:\pagefile.sys
  C:\A3N_A3L
.10
C:\bootfont.bin
  C:\ntldr
  ...

这里先简单地介绍一下File类。File类主要是文件的抽象代表,若要作文件输出/输入,必须配合其他相关类来使用。接下来会配合各小节的内容并适时地使用File类。

作业

使用File列出C盘中的所有文件夹及子文件夹的所有文件,并打印输出。

2.      输入/输出流的概念

什么是数据流?

Ø  流是指一连串流动的字符,是以先进先出方式发送信息的通道

Ø  java中有关流的操作使用java.io.*

Ø  处于安全的考虑,applet不能实现文件I/O

输入/输出(Input/Output)泛指对某个设备或环境进行数据的输入或输出。例如对硬盘进行输入/输出、对视频设备进行输入/输出、对网络主机进行输入/输出等,可以想象,因设备或环境的不同,会有各式各样的输入/输出问题与解决方案。输入/输出问题在程序设计中实际上是一个很复杂的问题。

对于输入/输出问题,Java将之抽象化为流(Stream)对象来解决。对不同的输入/输出问题,会有相应的流对象提供解决的方案。本章就是要学习Java中各式各样解决输入/输出问题的对象。然而输入/输出问题所涉及的领域相当广,基于学习的角度来看,必须选择一个主题来专门讨论,所以本章主题会围绕在文件输入/输出。有了本章的基础,在了解其他领域的输入/输出问题时就不难入手。

流的读写过程

打开一个流

打开一个流

如果有信息,则读入

如果有信息,则写入

关闭流

关闭流

 

3.      RandomAccessFile

ü  在正式介绍如何使用Java的输入/输出相关类来进行文件存取前,先简单地通过使用java.io.RandomAccessFile来存取文件,以认识一些文件存取时所必须注意的概念与事项。

ü  1:文件存取通常是循序的,每在文件中存取一次,文件的读取位置就会相对于目前的位置前进一次。

2:然而有时必须指定文件的某个区段进行读取或写入的动作,也就是进行随机存取(Random Access),即可以跳转到文件的任意位置处读写数据。这时可以使用RandomAccessFile,使用它的seek()方法来指定文件存取的位置。

ü  new RandomAccessFill(f,rw);  // 读写方式

new RandomAccessFill(f,r);  // 只读方式

ü  支持随机文件操作的方法:

readXXX():读

writeXXX():写

skipBytes():将指针向下移动若干字节

seek():将指针调到所需位置

getFilePointer():返回指针当前位置

length():返回文件长度

ü  当程序需要以读写的方式打开一个文件时,如果这个文件不存在,程序会创建一个文件。

ü  为了移动存取位置时的方便,通常在随机存取文件中会固定每一个数据的长度值称之为“指示器”指向当前读写处的位置,当低于n个字节后,文件指示器指向这个n个字节的下一个字节处,该类仅限于操作文件。例如长度固定为每一个学生个人数据,Java中并没有直接的方法可以写入一个固定长度数据,所以在固定每一个长度方法必须自行设计。

ü  范例先设计一个学生数据的类。

范例 Student.java

package onlyfun.caterpillar;
  public class Student

{
     
private String name;
      private int score;
      public Student()

{
         
setName("noname");
      }
      public Student(String name, int  score)

{
         
setName(name);
          this.score = score;
      }
      public void setName(String name)

{
         
StringBuilder builder = null;
          if(name != null)
              builder = new  StringBuilder(name);
          else
              builder = new  StringBuilder(15);
          builder.setLength(15); // 最长 15 字符

         
this.name = builder.toString();
      }
      public void setScore(int score)

{
         
this.score = score;
      }
      public String getName()

{
         
return name;
      }
      public int getScore()

{
         
return score;
      }
      // 每个数据固定写入34字节

     
public static int size()

{
         
return 34;
      }
  }

ü  对于每一个学生数据的实例在写入文件时,会固定以34字节的长度写入,也就是15个字符(30字节)加上一个int整数的长度(4字节)。范例中是使用StringBuilder来固定字符长度,可以使用size()方法来取得长度信息。范例如何使用RandomAccessFile来写入文件,并可随机指定一个所想读出的数据。

范例 RandomAccessFileDemo.java

package onlyfun.caterpillar;
  import java.io.*;
  import java.util.*;
  public class RandomAccessFileDemo

{
     
public static void main(String[]  args) {
          Student[] students = {new  Student("Justin", 90), new Student("momor", 95),
                          new  Student("Bush", 88), new Student("caterpillar", 84)};
          try {
              File file = new  File(args[0]);
              // 建立RandomAccessFile实例并以读写模式打开文件

             
RandomAccessFile  randomAccessFile = new RandomAccessFile(file, "rw");
              for(int i = 0; i <  students.length; i++) {
                  // 使用对应的write方法写入数据
                 
randomAccessFile.writeChars(students[i].getName());
                  randomAccessFile.writeInt(students[i].getScore()); 
              }
              Scanner scanner = new  Scanner(System.in);
              System.out.print("读取第几个数据?");
             
int num =  scanner.nextInt();


             
// 使用seek()方法操作存取位置
             
randomAccessFile.seek((num-1)  * Student.size());
              Student student = new  Student();

 

            // 使用对应的read方法读出数据
             
student.setName(readName(randomAccessFile));
              student.setScore(randomAccessFile.readInt());

            System.out.println("姓名:" + student.  GetName());
             
System.out.println("分数:
" +  student.getScore());
 
             
// 设置关闭文件

             
randomAccessFile.close();
          }
          catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("请指定文件名称");
         
} catch(IOException e) {
              e.printStackTrace();
          }
      }
      private static String readName(RandomAccessFile  randomAccessfile)throws IOException

    {
          char[] name = new char[15];
          for(int i = 0; i <  name.length; i++)
              name[i] =  randomAccessfile.readChar();

        // 将空字符取代为空格符并返回
         
return new  String(name).replace('\0', ' ');
      }
  }

ü  执行结果:

java  onlyfun.caterpillar.RandomAccessFileDemo student.dat
 
读取第几个数据?
2
 
姓名:
momor
 
分数:95

ü  RandomAccessFile上的相关方法实现中可以看到读写文件时几个必要的流程:

l  打开文件并指定读写方式

        Java中,当实例化一个与文件相关的输入/输出类时,就会进行打开文件的动作。在实例化的同时要指定文件是要以读出(r)、写入(w)或可读可写(rw)的方式打开,可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖。

l  使用对应的写入方法

        对文件进行写入,要使用对应的写入方法。在Java中通常是write的名称作为开头,在低级的文件写入中,要写入某种类型的数据,就要使用对应该类型的方法,如writeInt()writeChar()等。

l  使用对应的读出方法

        对文件进行读出,要使用对应的读出方法。在Java中通常是read的名称作为开头,在低级的文件读出中,要读出某种类型的数据,就要使用对应该类型的方法,如readInt()readChar()等。

l  关闭文件

       可以将文件看作是一个容器,要读出或写入数据都必须打开容器的瓶盖,而不进行读出或写入时,就要将瓶盖关闭。对于某些文件存取对象来说,关闭文件的动作意味着将缓冲区(Buffer)的数据全部写入文件,如果不作关闭文件的动作,某些数据可能没有写入文件而遗失。

4.      流:字节流与字符流

v  Java流的分类

   字节流

                   8

           InputStream  OutputStream

   字符流

           16 Unicode

           Reader   Writer

在初学流时至少掌握以下几点内容:

File类的使用

InputStreamOutputStream

FileInputStreamFileOutputStream

BufferedInputStreamBufferedOutputStream

DataInputStreamDataOutputStream

ReaderWriter

InputStreamReaderOutputStreamWriter

FileReaderFileWriter

BufferedReaderBufferedWriter

 

字节流

计算机中的数据都是以01的方式来存储,如果要在两个装置之间进行数据的存取,当然也是以01位的方式来进行,Java将数据于目的地及来源之间的流动抽象化为一个流(Stream),而流当中流动的则是位数据。(一般指二进制)

InputStreamOutputStream

Java SE中有两个类用来作流的抽象表示:java.io.InputStreamjava.io.OutputStream

Ø  InputStream是所有表示位输入流的类之父类,它是一个抽象类,继承它的子类要重新定义其中所定义的抽象方法。InputStream是从装置来源地读取数据的抽象表示,例如System中的标准输入流in象就是一个InputStream类型的实例。Java程序开始之后,in流对象就会开,目的是从标准输入装置中读取数据,这个装置通常是键盘或是用户定义的输入装置。

Ø  OutputStream是所有表示位输出流的类之父类,它是一个抽象类。子类要重新定义其中所定义的抽象方法,OutputStream是用于将数据写入目的地的抽象表示。例如System中的标准输出流对象out其类型是java.io.PrintStream,这个类是OutputStream的子类(java.io.FilterOutputStream继承OutputStream PrintStream再继承FilterOutputStream)。在程序开始之后,out流对象就会开启,可以通过out来将数据写至目的地装置,这个装置通常是屏幕显示或用户定义的输出装置。

 

Ø  下面范例可以读取键盘输入流,in对象的read()方法一次读取一个字节的数据,读入的数据以int类型返回。所以在使用out对象将数据显示出来时,就是10进制方式。

Ø  范例StreamDemo.java

package onlyfun.caterpillar;
  import java.io.*;
  public class StreamDemo {
     
public static void main(String[]  args) {
          try {
              System.out.print("输入字符
: ");
             
System.out.println("输入字符十进制表示
: " +
             
System.in.read()); 
          }catch(IOException e) {
              e.printStackTrace();
          }
      }
  }

Ø  执行结果:

输入字符: A
 
输入字符十进制表示: 65

字符A输入后由标准输入流in读取,A的位表示以十进制来看就是65,这是A字符的编码(查查ASCII编码表就知道了)

一般来说,很少直接实现InputStreamOutputStream上的方法,因为这些方法比较低级,通常会实现它们的子类。这些子类上所定义的方法在进行输入/输出时更为方便。

 

FileInputStreamFileOutputStream(文件输入/输出流)

Ø  java.io.FileInputStreamInputStream的子类。从开头File名称上就可以知道,FileInputStream与从指定的文件中读取数据至目的地有关。而java.io.FileOutputStreamOutputStream的子类,顾名思义,FileOutputStream主要与从来源地写入数据至指定的文件中有关。

Ø  当建立一个FileInputStreamFileOutputStream的实例时,必须指定文件位置及文件名称如果名称或路径不正确那么则报此异常:FileNotFoundException2.必须要关闭流),实例被建立时文件的流就会开启;而不使用流时,必须关闭文件流,以释放与流相依的系统资源,完成文件读/写的动作。

Ø  FileInputStream可以使用read()方法一次读入一个字节,并以int类型返回,或者是使用read()方法时读入至一个byte数组,byte数组的元素有多少个,就读入多少个字节。在将整个文件读取完成或写入完毕的过程中,这么一个byte数组通常被当作缓冲区,因为这么一个byte数组通常扮演承接数据的中间角色。

Ø  范例是使用FileInputStreamFileOutputStream的一个例子。程序可以复制文件,它会先从来源文件读取数据至一个byte数组中,然后再将byte数组的数据写入目的文件。

Ø  范例FileStreamDemo.java

package onlyfun.caterpillar;

import java.io.*;

public class FileStreamDemo  {
 
   
public static void main(String[] args) {
 
        
try {
 
             byte[] buffer = new byte[1024];

       // 来源文件
             FileInputStream fileInputStream =new  FileInputStream(new File(args[0]));
 
             // 目的文件

             FileOutputStream fileOutputStream =new  FileOutputStream(new File(args[1]));

       // available()可取得未读取的数据长度
             System.out.println("复制文件:" +fileInputStream.available()  + "字节");

       while(true) {
 
               
if(fileInputStream.available() < 1024) {
 
                    // 剩余的数据比1024字节少

                    // 一位一位读出再写入目的文件
                    int remain = -1;
 
                   
while((remain = fileInputStream.read())!= -1) {
 
                       
fileOutputStream.write(remain);
 
                   
}
 
                   
break;
 
               
}else {
 
                     // 从来源文件读取数据至缓冲区

                    fileInputStream.read(buffer);
 
                    // 将数组数据写入目的文件

                    fileOutputStream.write(buffer);
 
               
}
 
             }

       // 关闭流
             fileInputStream.close();
 
             fileOutputStream.close();

       System.out.println("复制完成");
 
        
}catch(ArrayIndexOutOfBoundsException  e) {
 
            
System.out.println("using: java  FileStreamDemo src des");
 
            
e.printStackTrace();
 
        
}catch(IOException e) {
 
            
e.printStackTrace();
 
        
}
 
   
}
  }

Ø  程序中示范了两个read()方法,一个方法可以读入指定长度的数据至数组,另一个方法一次可以读入一个字节。每次读取之后,读取的光标都会往前进,如果读不到数据则返回-1使用available()方法获得还有多少字节可以读取。除了使用File来建立FileInputStreamFileOutputStream的实例之外,也可以直接使用字符串指定路径来建立。

// 来源文件
  FileInputStream fileInputStream =new FileInputStream(args[0]);
  //
目的文件
  FileOutputStream fileOutputStream =new FileOutputStream(args[1]);

Ø  在不使用文件流时,记得使用close()方法自行关闭流,以释放与流相依的系统资源。一个执行的结果范例如下,它将FileDemo.java复制为FileDemo.txt

java onlyfun.caterpillar.FileStreamDemo  FileDemo.java FileDemo.txt
 
复制文件:1723字节

复制完成

Ø  FileOutputStream默认会以新建文件的方式来开启流。如果指定的文件名称已经存在,则原文件会被覆盖如果想以附加的模式来写入文件,则可以在构建FileOutputStream实例时指定为附加模式。例如:

FileOutputStream  fileOutputStream =new FileOutputStream(args[1], true);

Ø  构建方法的第二个append参数如果设置为true,在开启流时如果文件不存在则会新建一个文件,如果文件存在就直接开启流,并将写入的数据附加至文件末端。

Ø  虽然我一向不喜欢使用过长的范例来作程序示范(也不喜欢看很长的范例),不过本章的范例与其他各章的比起来相对长了一些,我会在程序中多用注释解释程序的逻辑。因为解释输入/输出操作最好的方式,是呈现一个具实用性的范例,本章的范例除了练习的作用之外,日后需要某些输入/输出功能时,也可以来参考看看如何实现。

 

BufferedInputStreamBufferedOutputStream // 称文件缓冲流

Ø  在介绍FileInputStreamFileOutputStream的例子中,使用了一个byte数组来作为数据读入的缓冲区,以文件存取为例,硬盘存取的速度远低于内存中的数据存取速度。为了减少对硬盘的存取,通常从文件中一次读入一定长度的数据,而写入时也是一次写入一定长度的数据,这可以增加文件存取的效率。

Ø  java.io.BufferedInputStreamjava.io.BufferedOutputStream可以为InputStreamOutputStream类的对象增加缓冲区功能。构建BufferedInputStream实例时,需要给定一个InputStream类型的实例,实现BufferedInputStream时,实际上最后是实现InputStream实例。同样地,在构建BufferedOutputStream时,也需要给定一个OutputStream实例,实现BufferedOutputStream时,实际上最后是实现OutputStream实例。

Ø  BufferedInputStream的数据成员buf是一个位数组默认为2048字节。当读取数据来源时,例如文件,BufferedInputStream会尽量将buf填满。当使用read()方法时,实际上是先读取buf中的数据,而不是直接对数据来源作读取。当buf中的数据不足时,BufferedInputStream才会再实现给定的InputStream对象的read()方法,从指定的装置中提取数据,如图所示:

 

 :BufferedInputStream在内部有buf成员作为缓冲区

Ø  BufferedOutputStream的数据成员buf是一个位数组,默认为512字节。当使用write()方法写入数据时,实际上会先将数据写至buf中,buf已满时才会实现给定的OutputStream对象的write()方法,将buf数据写至目的地,而不是每次都对目的地作写入的动作。(buf数据没有达到缓冲区大小时,有可能不会再写,这是我们就使用flush方法)

Ø  下面将范例FileStreamDemo做个改写,这次不用自行设置缓冲区,而使用BufferedInputStreamBufferedOutputStream让程序看来简单一些,也比较有效率。

Ø  范例BufferedStreamDemo.java

package onlyfun.caterpillar;
  import java.io.*;
  public class BufferedStreamDemo {
     
public static void main(String[]  args) {
          try {
              byte[] data = new byte[1];

            File srcFile = new File(args[0]); 
              File desFile = new  File(args[1]);

            BufferedInputStream  bufferedInputStream = new BufferedInputStream(new FileInputStream(srcFile));
              BufferedOutputStream  bufferedOutputStream = new BufferedOutputStream(
                                                                   new FileOutputStream(desFile));
              System.out.println("复制文件:" + srcFile.length()  + "字节");

            while(bufferedInputStream.read(data)  != -1) {
                  bufferedOutputStream.write(data); 
              }
              // 将缓冲区中的数据全部写出

             
bufferedOutputStream.flush();
              // 关闭流

             
bufferedInputStream.close(); 
              bufferedOutputStream.close();

            System.out.println("复制完成");
         
} catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("using:  java UseFileStream src des");
              e.printStackTrace();
          } catch(IOException e) {
              e.printStackTrace();
          }
      }
  }

Ø  为了确保缓冲区中的数据一定被写出至目的地,建议最后执行flush()将缓冲区中的数据全部写出目的流中。这个范例的执行结果与范例FileStreamDemo是相同的。

Ø  BufferedInputStreamBufferedOutputStream并没有改变InputStream OutputStream的行为,读入或写出时的动作还是InputStreamOutputStream负责BufferedInputStreamBufferedOutputStream只是在操作对应的方法之前,动态地为它们加上一些额外功能(像缓冲区功能),在这里是以文件存取流为例,实际上可以在其他流对象上也使用BufferedInputStreamBufferedOutputStream功能。

DataInputStreamDataOutputStream

Ø  java.io.DataInputStreamjava.io.DataOutputStream可提供一些对Java基本数据类型写入的方法,像读写intdoubleboolean等的方法。由于Java的数据类型大小是规定好的,在写入或读出这些基本数据类型时,就不用担心不同平台间数据大小不同的问题。

Ø  这里还是以文件存取来进行说明。有时只是要存储一个对象的成员数据,而不是整个对象的信息,成员数据的类型假设都是Java的基本数据类型,这样的需求不必要使用到与Object输入、输出相关的流对象,可以使用DataInputStreamDataOutputStream来写入或读出数据。(基本数据输入输出流)

Ø  下面使用范例来介绍如何使用DataInputStreamDataOutputStream。先设计一个Member类。

Ø  范例Member.java

package onlyfun.caterpillar;

public class Member {
     
private String name;
      private int age;

public Member() {}

public Member(String name,  int age) {
         
this.name = name;
          this.age = age;
      }

public void setName(String  name) {
         
this.name = name;
      }

public void setAge(int age)  {
         
this.age = age;
      }

public String getName() {
         
return name;
      }

public int getAge() {
         
return age;
      }
  }

Ø  打算将Member类实例的成员数据写入文件中,并打算在读入文件数据后,将这些数据还原为Member对象。范例如何实现这个需求。

Ø  范例DataStreamDemo.java

package onlyfun.caterpillar;
  import java.io.*;
  public class DataStreamDemo {
     
public static void main(String[]  args) {
          Member[] members = {new  Member("Justin", 90), new Member("momor", 95), new  Member("Bush", 88)};

        try {
              DataOutputStream  dataOutputStream = new DataOutputStream( new FileOutputStream(args[0]));
              for(Member member :  members) {
                  // 写入UTF字符串

         
        dataOutputStream.writeUTF(member.getName()); 
                  // 写入int数据
                 
dataOutputStream.writeInt(member.getAge()); 
              }
              // 读出所有数据至目的地

            dataOutputStream.flush();
              // 关闭流

             
dataOutputStream.close();
              DataInputStream  dataInputStream = new DataInputStream( new FileInputStream(args[0]));
              // 读出数据并还原为对象
             
for(int i = 0; i <  members.length; i++) {
                  // 读出UTF字符串
                 
String name =  dataInputStream.readUTF();
                  // 读出int数据
                 
int score =  dataInputStream.readInt();
                  members[i] = new  Member(name, score);
              }
              // 关闭流
             
dataInputStream.close();

            // 显示还原后的数据
             
for(Member member :  members) {
                  System.out.printf("%s\t%d%n",  member.getName(), member.getAge());
              }
          } catch(IOException e) {
              e.printStackTrace();
          }
      }
  }

Ø  在从文件中读出数据时,不用费心地自行判断读入字符串时或读入int类型时何时该停止,使用对应的readUTF()readInt()方法就可以正确地读入完整类型数据。同样地,DataInputStreamDataOutputStream并没有改变InputStreamOutputStream的行为,读入或写出时的动作还是InputStreamOutputStream负责。DataInputStreamDataOutputStream只是在实现对应的方法时,动态地为它们加上类型判断功能,在这里虽然是以文件存取流为例,实际上可以在其他流对象上也使用DataInputStreamDataOutputStream功能。

序列化/反序列化ObjectInputStreamObjectOutputStream

ObjectOutputStream 中的WriteStreamHeader()作用,为添加一个流头,附加模式会产生多个多个流头。

ObjectInputStream只认一个流头,如果有多个流头时,就会抛异常。

Ø  Java程序执行的过程中,很多数据都是以对象的方式存在于内存中。有时会希望直接将内存中整个对象存储至文件,而不是只存储对象中的某些基本类型成员信息,而在下一次程序运行时,希望可以从文件中读出数据并还原为对象。这时可以使用java.io.ObjectInputStreamjava.io.ObjectOutputStream来进行这项工作。

Ø  如果要直接存储对象,定义该对象类必须实现java.io.Serializable接口,(test 类必须实现)不过Serializable接口中并没有规范任何必须实现的方法,所以这里所谓实现的意义,其实像是对对象贴上一个标志,代表该对象是可序列化的(Serializable)

Ø  为了说明如何直接存储对象,先来实现一个User类。

Ø  范例User.java

package onlyfun.caterpillar;

import java.io.Serializable;

public class User implements  Serializable {
     
private static final long  serialVersionUID = 1L;

private String name;
     
private int number;

public User() {}

public User(String name, int  number) {
         
this.name = name;
          this.number = number;
      }

    public void setName(String name) {
          this.name = name;
      }

    public void setNumber(int number) {
          this.number = number;
      }

    public String getName() {
          return name;
      }

    public int getNumber() {
          return number;
      }
  }

Ø  注意到serialVersionUID,它代表了可序列化对象的版本。如果没有提供这个版本信息,则实现Serializable接口的类会自动依类名称、实现的接口、成员等来产生。如果是自动产生的,则下次更改User类,自动产生的serialVersionUID也会跟着变更,           java.io.InvalidClassException。如果想要维持版本信息的一致,则要明确声明serialVersionUID

Ø  ObjectInputStreamObjectOutputStreamInputStreamOutputStream的实例加上了可以让用户写入对象与读出对象的功能。在写入对象时,要使用writeObject()法,读出对象时则使readObject()方法,被读出的对象都是以Object的类型返回所以必须将之转换为对象原来的类型,才能正确地实现被读回的对象。范例14.10示范了如何存储User对象至文件中,然后再将它读回并还原为User实例。

Ø  范例ObjectStreamDemo.java

package onlyfun.caterpillar;

import java.io.*;
  import java.util.*;

public class  ObjectStreamDemo {
     
public static void main(String[]  args) {
          User[] users = {new  User("cater", 101),new User("justin", 102)};
          // 写入新文件

         
writeObjectsToFile(users,  args[0]);

           try {
              // 读取文件数据

             
users = readObjectsFromFile(args[0]);
              // 显示读回的对象
             
for(User user : users) {
                  System.out.printf("%s\t%d%n",  user.getName(), user.getNumber());
              }
              System.out.println();

            users = new User[2];
              users[0] = new  User("momor", 103);
              users[1] = new  User("becky", 104);

            // 附加新对象至文件
             
appendObjectsToFile(users,  args[0]);

            // 读取文件数据
             
users =  readObjectsFromFile(args[0]);
              // 显示读回的对象
       
      for(User user : users) {
                  System.out.printf("%s\t%d%n",user.getName(),  user.getNumber());
              }
          }catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("没有指定文件名");
         
}catch(FileNotFoundException e)  {
              e.printStackTrace();
          }
      }

    // 将指定的对象写入至指定的文件
     
public static void  writeObjectsToFile(Object[] objs, String filename) {
          File file = new File(filename);

        try {
              ObjectOutputStream  objOutputStream =new ObjectOutputStream(new FileOutputStream(file));
              for(Object obj : objs) {
                  // 将对象写入文件

                 
objOutputStream.writeObject(obj);
              }
              // 关闭流
             
objOutputStream.close();
          }catch(IOException e) {
              e.printStackTrace();
          }
      }

    // 将指定文件中的对象数据读回
     
public static User[]  readObjectsFromFile(String filename)throws FileNotFoundException {
          File file = new File(filename);

        // 如果文件不存在就丢出异常
         
if(!file.exists())
              throw new  FileNotFoundException();

        // 使用List先存储读回的对象
         
List<User> list = new  ArrayList<User>();

        try {
              FileInputStream  fileInputStream =new FileInputStream(file);
              ObjectInputStream objInputStream  =new ObjectInputStream(fileInputStream);

            while(fileInputStream.available()  > 0) {
                  list.add((User)  objInputStream.readObject());
              }
              objInputStream.close();
          }catch(ClassNotFoundException  e) {
              e.printStackTrace();
          }catch(IOException e) {
              e.printStackTrace();
          }

        User[] users = new User[list.size()];
          return list.toArray(users);
      }

    // 将对象附加至指定的文件之后
     
public static void appendObjectsToFile(Object[]  objs, String filename)throws FileNotFoundException {

        File file = new File(filename);

        // 如果文件不存在则丢出异常
         
if(!file.exists())
              throw new  FileNotFoundException();

        try {
              // 附加模式

             
ObjectOutputStream objOutputStream  =new ObjectOutputStream(new FileOutputStream(file, true)) {

                        // 如果要附加对象至文件后
                         
// 必须重新定义这个方法
                         
protected void  writeStreamHeader()throws IOException {}};

            for(Object obj : objs) {
                  // 将对象写入文件

                 
objOutputStream.writeObject(obj);
              }
              objOutputStream.close();
          }catch(IOException e) {
              e.printStackTrace();

        }
      }
  }

 

Ø  范例中必要的地方都已加上注释,虽然程序很长,但范例中已经将写入对象至文件、从文件读出对象、附加对象至文件的程序逻辑集中在writeObjectsToFile()readObjectsFromFile()appendObjectsToFile()3个方法中。执行结果如下:

cater   101
  justin  102

cater   101
  justin  102
  momor   103
  becky   104

Ø  注意,在试图将对象附加至一个先前已写入对象的文件时,由于ObjectOutputStream在写入数据时,还会加上一个特别的流头(Stream Header),所以在读取文件时会检查这个流头。如果一个文件中被多次附加对象,那么该文件中会有多个流头,这样读取检查时就会发现不一致,这会丢出java.io.StreamCorrupedException。为了解决这个问题,可以重新定义ObjectOutputStream writeStreamHeader()方法,如果是以附加的方式来写入对象,就不写入流头:

ObjectOutputStream objOutputStream  =new ObjectOutputStream(new FileOutputStream(file, true)) {
     
protected void writeStreamHeader()throws  IOException {}
  };

Ø  序列化对象的功能并不只应用于文件的读取或写入,也可以应用于其他领域。例如将对象直接通过网络进行传送或是传送影像的位数组数据等。

Ø  这里只是对序列化的一个简介,记得看看API文件中有关ObjectInputStreamObjectOutputStream的介绍,了解更多有关对象写入与读出的细节。使用Google查询关键词序列化也可以找到不少文件。

管道流:PipedOutPutStreamPipedInputStream  

Ø  管道流的主要作用可以进行两个线程间的通讯,分为管道输入流PipedOutPutStream),管道输入流(PipedInputStream),如果要想进行管道输出则必须把输出流连接在输入流之上,在PipedOutPutStream类上有如下的一个方法用于连接管道:

public void connect(PipedInputStream snk) throws IOExcetion

 

Ø  范例PipedDemo.java

import java.io.* ;

class Send implements  Runnable{               // 线程类

       private PipedOutputStream pos = null ;       // 管道输出流

       public Send(){

              this.pos = new PipedOutputStream() ;  // 实例化输出流

       }

       public void run(){

              String str = "Hello World!!!" ;     // 要输出的内容

              try{

                     this.pos.write(str.getBytes()) ;

              }catch(IOException e){

                     e.printStackTrace() ;

              }

              try{

                     this.pos.close() ;

              }catch(IOException e){

                     e.printStackTrace() ;

              }

       }

       public PipedOutputStream getPos(){    // 得到此线程的管道输出流

              return this.pos ;     

       }

};

class Receive implements  Runnable{

       private PipedInputStream pis = null ;   // 管道输入流

       public Receive(){

              this.pis = new PipedInputStream() ;     // 实例化输入流

       }

       public void run(){

              byte b[] = new byte[1024] ;   // 接收内容

              int len = 0 ;

              try{

                     len = this.pis.read(b) ;    // 读取内容

              }catch(IOException e){

                     e.printStackTrace() ;

              }

              try{

                     this.pis.close() ;      // 关闭

              }catch(IOException e){

                     e.printStackTrace() ;

              }

              System.out.println("接收的内容为:" + new  String(b,0,len)) ;

       }

       public PipedInputStream getPis(){

              return this.pis ;

       }

};

public class PipedDemo{

       public static void main(String args[]){

              Send s = new Send() ;

              Receive r = new Receive() ;

              try{

                     s.getPos().connect(r.getPis()) ;      // 连接管道

              }catch(IOException e){

                     e.printStackTrace() ;

              }

              new Thread(s).start() ;   // 启动线程

              new Thread(r).start() ;   // 启动线程

       }

};

 

 

Ø  java开发中很少直接去开发多线程流程序,以上示例只是为了加深一下流读写的操作过程。

SequenceInputStream  (顺序输入流)

Ø 若要将一个文件分割为数个文件,再将之组合还原为一个文件,最基本的作法是使用数个FileInputStream来打开分割后的文件,然后一个一个文件的读取,并使用同一个FileOutputStream实例写到同一个文件中。必须要自行判断每一个分割文件的读取是否完毕,如果完毕就读取下一个文件。

Ø  如果使用java.io.SequenceInputStream就不用这么麻烦,SequenceInputStream可以看作是数个 InputStream对象的组合。当一个InputStream对象的内容读取完毕后,它就会取出下一个InputStream对象,直到所有的 InputStream对象都读取完毕为止。

Ø  可以将指定的文件进行分割,也可以将分割后的文件还原为一个文件。

Ø  范例SequenceStreamDemo.java

package onlyfun.caterpillar;

import java.util.*;
  import java.io.*;

public class  SequenceStreamDemo {
     
public static void main(String[]  args) {
          try {
              // args[0]:
指定分割(s)或连接(c)
             
switch (args[0].charAt(1))  {
                  case 's':
                      // args[1]: 每个分割文件的大小

                     
int size =  Integer.parseInt(args[1]);
                      // args[2]: 指定要被分割的文件名称
                     
seperate(args[2],  size);
                      break;
                  case 'c':
                      // args[1]: 指定要被组合的文件个数
                     
int number =  Integer.parseInt(args[1]);
                      // args[2]: 组合后的文件名称
                     
concatenate(args[2],  number);
                      break;
                  }
          } catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("Using:  java UseSequenceStream [-s/-c]" + " (size/number) filename");
              System.out.println("-s:  分割文件\n-c: 组合文件");
         
} catch(IOException e) {
              e.printStackTrace();
          }
      }

    // 分割文件
     
public static void seperate(String  filename, int size) throws IOException {
          FileInputStream fileInputStream  = new FileInputStream(new File(filename));
          BufferedInputStream  bufInputStream = new BufferedInputStream(fileInputStream);

        byte[] data = new byte[1];
          int count = 0; 
          // 还原文件大小及指定分割的大小

         
// 决定要分割为几个文件
         
if(fileInputStream.available()  % size == 0)
              count =  fileInputStream.available() / size;
          else
              count =  fileInputStream.available() / size + 1;
 
          // 开始进行分割

         
for(int i = 0; i < count;  i++) {
              int num = 0;
              // 分割的文件加上下划线与编号
             
File file = new  File(filename + "_" + (i + 1));
            BufferedOutputStream bufOutputStream = new BufferedOutputStream( new FileOutputStream(file));
              while(bufInputStream.read(data)  != -1) {
                  bufOutputStream.write(data);
                  num++;
                  if(num == size) { // 分割出一个文件
                     
bufOutputStream.flush(); 
                      bufOutputStream.close(); 
                      break;
                  }
              }
              if(num < size) {
                  bufOutputStream.flush(); 
                  bufOutputStream.close(); 
              }
          }
          System.out.println("分割为" +  count + "个文件");
     
}

    // 连接文件
     
public static void concatenate(String  filename, int number) throws IOException {
          // 收集文件用的List
         
List<InputStream> list =  new ArrayList<InputStream>();
          for(int i = 0; i < number;  i++) {
              // 文件名必须为下划线加上编号

             
File file = new  File(filename + "_" + (i+1));
              list.add(i, new  FileInputStream(file));
          }
          final Iterator<InputStream>  iterator = list.iterator();
          // SequenceInputStream 需要一个Enumeration对象来构建
         
Enumeration<InputStream>  enumation = new Enumeration<InputStream>() {
              public boolean hasMoreElements() {
                  return  iterator.hasNext();
              }

            public InputStream nextElement()  {
                  return iterator.next();
              }
          };
          // 建立SequenceInputStream  并使用
BufferedInputStream
         
BufferedInputStream bis= new  BufferedInputStream( new SequenceInputStream(enumation),8192);

        BufferedOutputStream bos=new  BufferedOutputStream( new FileOutputStream(filename), 8192);

        byte[] data = new byte[1];
          // 读取所有文件数据并写入目的地文件

         
while(bis.read(data) != -1)
              bos.write(data);

        bufInputStream.close();
          bos.flush();
          bos.close();
          System.out.println("组合" +  number + "个文件
OK!!");
    
 }
  }

Ø  分割文件时必须指定-s和分割后的每个文件大小。执行的一个例子如下:

java  onlyfun.caterpillar.SequenceStreamDemo -s 1048576 test.zip

Ø  分割为6个文件

Ø  分割后的文件名是原文件名加上下划线与编号,例如test.zip_1test.zip_2等。合并文件时必须要指定-c、合并的文件数与来源文件名称,例如将之前分割过后的文件合并为一个文件:

java  onlyfun.caterpillar.SequenceStreamDemo -c 6 test.zip

Ø  组合6个文件 OK!!

PrintStream

Ø  之前所介绍过的OutputStream对象,都是直接将内存中的数据原封不变地写至目的地(例如一个文件)。举个例子来说,如果将int类型1使用之前介绍的OutputStream对象输出至文件,则文件中所存储的是int类型1在内存中的值,例如范例的输出结果。

Ø  范例14.12 StreamTest.java

package onlyfun.caterpillar;

import java.io.*;

public class StreamTest {
     
public static void main(String[]  args)throws IOException {
          FileOutputStream file =new  FileOutputStream(new File("test.txt"));
        file.write(1);
          file.close();
      }
  }

Ø  执行范例后会产生一个test.txt文件,打开文件之后,不会看到显示1,而可能看到一个怪异的符号。由于使用write()方法,这会将1在内存中的值的低字节0000001写入文件中,而纯文件文件在显示文件内容时,会以操作系统的默认编码显示对应的字符。如果使用文字编辑软件(viUltraEdit)观看test.txt的十六进制表示,其结果会显示 01(十六进制表示),如图所示。

 

使用UltraEdit十六进制编辑模式观看

Ø  有时所想要存储的结果是数据转换为字符之后的结果,例如程序的执行结果是3.14159,您会希望使用3.14159等字符来存储,也就是俗称的存储为纯文本文件。这样当使用简单的纯文字编辑器观看时,就可以直接看到以3.14159等字符显示的画面。在范例14.12中,若想使用纯文本文件看到test.txt的显示结果是1,则必须先将内存中的整数1,也就是二进制00000000 00000000 00000000 00000001转换为对应的1字符编码,也就是0x31(十进制表示为49)并加以存储。

Ø  使用java.io.PrintStream可以自动进行字符转换的动作,默认会使用操作系统的编码来处理对应的字符转换动作。

Ø  范例PrintStreamDemo.java

package onlyfun.caterpillar;

import java.io.*;

public class PrintStreamDemo  {
     
public static void main(String[]  args)throws FileNotFoundException {
          PrintStream printStream = new  PrintStream(new FileOutputStream(

                                                    new File("test.txt")));

        printStream.println(1);

        printStream.close();
      }
  }

Ø  执行程序之后使用纯文字编辑器打开test.txt,其内容会是显示字符1了。print()println()接受intcharStringdouble等数据类型,println()会在输出之后加上换行字符,而print()则不会。

Ø  注意在文件存储上实际并没有二进制文件或是纯文本文件的区别,所有的文件所存储的都是二进制的数据,一般俗称的纯文本文件。其实正确地说,是指存储的结果是经过字符转换。例如将int类型1转换为字符1的编码结果并加以存储。

 

ByteArrayInputStreamByteArrayOutputStream

特点:是已byte 数组为目的。

Ø  流的来源或目的地不一定是文件,也可以是内存中的一个空间,例如一个位数组。java.io.ByteArrayInputStreamjava.io.ByteArrayOutputStream即是将位数组当作流输入来源、输出目的地的类。

Ø  ByteArrayInputStream可以将一个数组当作流输入的来源,而ByteArrayOutputStream则可以将一个位数组当作流输出的目的地。在这里举一个简单的文件位编辑程序作为例子,您可以打开一个简单的文本文件,其中有简单的ABCDEFG等字符,在读取文件之后,可以直接以程序来指定文件中位的位置来修改所指定的字符。作法是将文件读入数组中,指定数组索引修改元素,然后重新将数组存回文件。范例14.14是实现的程序内容。

Ø  范例ByteArrayStreamDemo.java

package onlyfun.caterpillar;

import java.io.*;
  import java.util.*;

public class  ByteArrayStreamDemo {
     
public static void main(String[]  args) {
          try {
              File file = new  File(args[0]);
              BufferedInputStream  bufferedInputStream =new BufferedInputStream(new FileInputStream(file));

            ByteArrayOutputStream  arrayOutputStream =new ByteArrayOutputStream();

            byte[] bytes = new byte[1];

            // 将文件内容写入位数组流
             
while(bufferedInputStream.read(bytes)  != -1) {
                  arrayOutputStream.write(bytes);
              }
              arrayOutputStream.close();
              bufferedInputStream.close();

            // 以字符方式显示位数组内容
             
bytes =  arrayOutputStream.toByteArray();
              for(int i = 0; i <  bytes.length; i++) {
                  System.out.print((char)  bytes[i]);
              }
              System.out.println();

            // 让用户输入位置与字符修改位数组内容
             
Scanner scanner = new  Scanner(System.in);

            System.out.print("输入修改位置:");
             
int pos =  scanner.nextInt();
              System.out.print("输入修改字符:
");
             
// 修改数组中对应的字符

             
bytes[pos-1] = (byte) scanner.next().charAt(0);

            // 将位数组内容存回文件
             
ByteArrayInputStream  byteArrayInputStream =new ByteArrayInputStream(bytes);
              BufferedOutputStream  bufOutputStream =new BufferedOutputStream(new FileOutputStream(file));
              byte[] tmp = new byte[1];
              while(byteArrayInputStream.read(tmp)  != -1){
                  bufOutputStream.write(tmp);

            }
              byteArrayInputStream.close();
              bufOutputStream.flush();
              bufOutputStream.close();
          }catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("请指定文件名称
");
         
}catch(IOException e) {
              e.printStackTrace();

        }
      }
  }

Ø  执行结果:

java  onlyfun.caterpillar.ByteArrayStreamDemo test.txt
  ABCDEFG
 
输入修改位置:
3
 
输入修改字符:X

Ø  再打开test.txt时,会发现C已经被覆盖为X

Ø  范例只是一个简单的示范,实际上由于字符在Java中使用Unicode,是两个位组长度,所以这个范例只能用于修改ASCII字符(因为Unicode的前128位与ASCII兼容)。范例14.21会介绍可以修改汉字字符与ASCII字符的程序。

PushbackInputStream

Ø  java.io.PushbackInputStream拥有一个PushBack缓冲区,从PushbackInputStream读出数据后,只要PushBack缓冲区没有满,就可以使用unread()将数据推回流的前端。

Ø  举个使用PushbackInputStream的例子。假设一个文本文件中同时存储有ASCII码范围的英文字符与BIG5码范围的中文字符。您希望判断出哪些位置是ASCII而哪些位置是BIG5中文字符的位置,BIG5中文字符使用两个字节来表示一个中文字,而ASCII只使用一个字节来表示英文字符。

Ø  BIG5中文为了与ASCII兼容,低字节范围为0xA40xF9,而高字节为0x400x7E以及0xA10xFE。存储时低字节先存,再存高字节,所以读取时只要先读到字节是在0xA40xF9,就表示它可能是一个中文字的前半数据。

Ø  为了说明PushbackInputStream的功能,在范例14.15中一次从文件中读取两个字节,并检查两个字节合并后的整数值是否在0xA4400xFFFF之间,这样可以简单地判断其两个字节合并后是否为BIG5码。如果是BIG5码则使用这两个字节产生String实例以显示汉字字符;如果不在这个范围之内,则可能是个ASCII范围内的字符,您可以显示第一个字节的字符表示,并将读出的第二个字节推回流,以待下一次可以重新读取。

Ø  范例PushbackStreamDemo.java

package onlyfun.caterpillar;

import java.io.*;

public class  PushbackStreamDemo {
     
public static void main(String[]  args) {
          try {
            PushbackInputStream pushbackInputStream =new PushbackInputStream(new FileInputStream(args[0]));
              byte[] array = new byte[2];
              int tmp = 0;
              int count = 0;

            while((count = pushbackInputStream.read(array))!=  -1) {
                  // 两个字节转换为整数

                 
tmp = (short)((array[0]  << 8) |(array[1] & 0xff));
                  tmp = tmp & 0xFFFF;

                       // 判断是否为BIG5,如果是则显示BIG5中文字
                 
if(tmp >= 0xA440  && tmp < 0xFFFF) {
                      System.out.println("BIG5:  " +
                      new String(array));
                  }else {
                      // 将第二个字节推回流

                    pushbackInputStream.unread(array,  1, 1);
                      // 显示ASCII范围的字符

                     
System.out.println("ASCII:  " +(char)array[0]);
                  }
              }

            pushbackInputStream.close();
          }catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("请指定文件名称
");
         
}catch(IOException e) {
              e.printStackTrace();
          }
      }
  }

Ø  假设指定的文本文件test.txt中有以下的文字:这Test测试。则执行的结果如下:

java onlyfun.caterpillar.  PushbackStreamDemo test.txt
  BIG5:

  ASCII: T
  BIG5:

  ASCII: e
  BIG5:

  ASCII: s
  BIG5:

  ASCII: t
  BIG5:

  BIG5:

字符流

java.io.Readerjava.io.Writer其子类等是处理字符流(Character Stream)的相关类。简单地说,就对流数据以一个字符(两个字节)的长度为单位来处理(0~65 5350x00000xffff)并进行适当的字符编码转换处理ReaderWriter与其子类可以用于进行所谓纯文本文件的字符读/

ReaderWriter

Ø  java.io.Readerjava.io.Writer支持Unicode标准字符集(CharacterSet)(字节流则只支持ISO-Latin-18-bit)。在处理流数据时,会根据系统默认的字符编码来进行字符转换,ReaderWriter是抽象类,在进行文本文件的字符读写时真正会使用其子类,子类通常会重新定义相关的方法。

Ø  在范例14.15中读入一个含BIG5中文字及ASCII字符的文本文件,当时对BIG5中文字的处理是新建一个String实例。这里改写一下该范例,使用Reader的子类 java.io.InputStreamReader来转换读入的两个字节为中文字符,并显示在屏幕上

Ø  范例ReaderDemo.java

package onlyfun.caterpillar;

import java.io.*;

public class ReaderDemo {
     
public static void main(String[]  args) {
          try {
              PushbackInputStream  pushbackInputStream =new PushbackInputStream(new FileInputStream(args[0]));
              byte[] array = new byte[2];   

            ByteArrayInputStream  byteArrayStream =new ByteArrayInputStream(array);

            // reader会从已读的位数组中取出数据InputStreamReader reader =new InputStreamReader(byteArrayStream);

            int tmp = 0;
              int count = 0;

            while((count = pushbackInputStream.read(array))!=  -1) {
                  // 两个字节转换为整数

                 
tmp = (short)((array[0]  << 8) |(array[1] & 0xff));
                      tmp = tmp &  0xFFFF;

                    // 判断是否为BIG5,如果是则显示BIG5中文字
                     
if(tmp >= 0xA440  && tmp < 0xFFFF) {
                          System.out.println("BIG5:  " +(char) reader.read());
                          // 重置ArrayInputStream的读取光标
                         
// 下次reader才会再重头读取数据
                         
byteArrayStream.reset();
                      }else {
                          // 将第二个字节推回流
                         
pushbackInputStream.unread(array,  1, 1);
                          // 显示ASCII范围的字符
                         
System.out.println("ASCII:  " +(char)array[0]);
                      }
                  }

                pushbackInputStream.close();
              }catch(ArrayIndexOutOfBoundsException  e) {
                  System.out.println("请指定文件名称
");
             
}catch(IOException e) {
                  e.printStackTrace();
              }
       }
  }

Ø  对于范例要注意的是Readerread()方法,该方法每次会读入一个字符的数据,并以系统默认的编码作字符转换,然后以int类型返回。也可以直接在构建Reader的实例时,自行指定读取时的编码。例如:

InputStreamReader reader =new  InputStreamReader(byteArrayStream, "BIG5");

InputStreamReaderOutputStreamWriter (字符的输入输出流)

Ø 若想对InputStreamOutputStream进行字符处理,可以使用java.io.InputStreamReaderjava.io.OutputStreamWriter为其加上字符处理的功能,它们分别为ReaderWriter的子类。

Ø  举个例子来说,若想要显示纯文本文件的内容,不用费心地自行判断字符编码(例如范例14.15中要费心地自行判断是ASCII英文字母或BIG5中文字),只要将InputStreamOutputStream的实例作为构建InputStreamReaderOutputStreamWriter时的变量,就可以操作InputStreamReaderOutputStreamWriter来进行文本文件的读取,让它们为您做字符判断与转换的动作。

Ø  范例可以打开一个纯文本文件,显示其中的字符内容。为了示范OutputStreamWriter的功能,范例会将文本文件复制为另一个文本文件"backup_原文件名"

Ø  范例StreamReaderWriterDemo.java

package onlyfun.caterpillar;
  import java.io.*;
  public class StreamReaderWriterDemo {
     
public static void main(String[]  args) {
          try {
              FileInputStream  fileInputStream = new FileInputStream(args[0]);
              // FileInputStream加上字符处理功能

             
InputStreamReader  inputStreamReader = new InputStreamReader(fileInputStream);

            FileOutputStream fileOutputStream  = new FileOutputStream("backup_" + args[0]);
              // FileOutputStream加上字符处理功能

             
OutputStreamWriter  outputStreamWriter = new OutputStreamWriter(fileOutputStream);
              int ch = 0;
              // 以字符方式显示文件内容
             
while((ch =  inputStreamReader.read()) != -1) {
                  System.out.print((char)  ch);
                  outputStreamWriter.write(ch); 
              }
              System.out.println();

                 inputStreamReader.close();
              outputStreamWriter.close(); 
          } catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("没有指定文件
");
         
} catch(IOException e) {
              e.printStackTrace();
          }
      }
  }

Ø  在这里使用FileInputStreamFileOutputStream,但InputStreamReaderOutputStreamWriter可以分别以任何InputStreamOutputStream子类的实例作为构建对象时的变量。之前提过InputStreamReaderOutputStreamWriter在存取时是以系统的默认字符编码来进行字符转换也可以自行指定字符编码。例如指定读取文件时的字符编码为BIG5

InputStreamReader inputStreamReader =new  InputStreamReader(fileInputStream, "BIG5");

Ø  关于可设置的编码,可以参考支持的编码(Supported Encodings)

FileReaderFileWriter (字符文件流)

Ø  如果想要存取的是一个文本文件,可以直接使用java.io.FileReaderjava.io.FileWriter类,它们分别继承自InputStreamReaderOutputStreamWriter。可以直接指定文件名称或File对象来打开指定的文本文件,并读入流转换后的字符字符的转换会根据系统默认的编码(若要指定编码,则还是使用InputStreamReaderOutputStreamWriter)

Ø  FileReaderFileWriter的使用非常简单,下面举个例子。在Linux下编写的文本文件,其断行字符是\n,而在Windows下编写的文本文件其断行是\r\n两个连续字符。如果在Windows下使用记事本打开一个Linux下编写的文本文件,其在显示上并不会有断行的效果,且\n字符会被用一个黑色方块来显示。

Ø  可以读入Linux下编写的文本文件,再写入另一个文件。在读取过程中若遇到\n字符,就取代为\r\n两个连续字符,这样新的文件在Windows的记事本程序中,就可以有断行显示的效果。

Ø  范例FileReaderWriterDemo.java

package onlyfun.caterpillar;
  import java.io.*;
  public class FileReaderWriterDemo {
     
public static void main(String[]  args) {
          try {
              FileReader fileReader = new  FileReader(args[0]);
              FileWriter fileWriter = new  FileWriter(args[0] + ".txt");
              int in = 0;
              char[] wlnChar = {'\r',  '\n'};
              while((in =  fileReader.read()) != -1) {
                  if(in == '\n') {
                      // 写入
"\r\n"
                     
fileWriter.write(wlnChar); 
                  }else{
                      fileWriter.write(in); 
                  }

            }
              fileReader.close();
              fileWriter.close();
          }catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("请指定文件
");
         
} catch(IOException e) {
              e.printStackTrace();
          }
      }
  }

 

BufferedReaderBufferedWriter

Ø  java.io.BufferedReaderjava.io.BufferedWriter类各拥有8192字符的缓冲区。当BufferedReader在读取文本文件时,会先尽量从文件中读入字符数据并置入缓冲区,而之后若使用read()方法,会先从缓冲区中进行读取。如果缓冲区数据不足,才会再从文件中读取,使用BufferedWriter时,写入的数据并不会先输出至目的地,而是先存储至缓冲区中。如果缓冲区中的数据满了,才会一次对目的地进行写出。例如一个文件,通过缓冲区可减少对硬盘的输入/输出动作,以提高文件存取的效率。

Ø  之前在介绍取得用户输入时,就使用过BufferedReader。从标准输入流System.in中直接读取用户输入时,用户每输入一个字符,System.in就读取一个字符。为了能一次读取一行用户的输入,使用了BufferedReader来对用户输入的字符进行缓冲。readLine()方法会在读取到用户的换行字符时,再一次将整行字符串传入。

Ø  System.in是一个位流,为了转换为字符流,可使用InputStreamReader为其进行字符转换,然后再使用BufferedReader为其增加缓冲功能。例如:

BufferedReader reader =new  BufferedReader(new InputStreamReader(System.in));

 

Ø  范例示范了BufferedReaderBufferedWriter的使用。可以在文字模式下输入字符,程序会将输入的文字存储至指定的文件中,如果要结束程序,输入quit字符串即可。

Ø  范例BufferedReaderWriterDemo.java

package onlyfun.caterpillar;

import java.io.*;

public class  BufferedReaderWriterDemo {
     
public static void main(String[]  args) {
          try {
              // 缓冲System.in输入流

             
BufferedReader bufReader  =new BufferedReader(new InputStreamReader(System.in));
              // 缓冲FileWriter字符输出流
             
BufferedWriter bufWriter =new  BufferedWriter(new FileWriter(args[0]));

            String input = null;

            // 每读一行进行一次写入动作
             
while(!(input =bufReader.readLine()).equals("quit"))  {
                  bufWriter.write(keyin);
                  // newLine()方法写入与操作系统相依的换行字符
                 
bufWriter.newLine();
              }

            bufReader.close();
              bufWriter.close();
          }catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("没有指定文件
");
         
}catch(IOException e) {
              e.printStackTrace();
          }
      }
  }

Ø  由于换行字符依操作系统不同而有所区别,在Windows下是\r\n,在Linux下是\n,在Mac OS下是\r,您可以使用newLine()方法,由执行环境依当时的操作系统决定该输出哪一种换行字符。

PrintWriter

Ø  前面介绍过PrintStream,它可以将Java的基本数据类型等数据,直接转换为系统默认编码下对应的字符,再输出至OutputStream中。而这里要介绍的 java.io.PrintWriter在功能上与PrintStream类似,除了接受OutputStream实例作为变量之外,PrintWriter还可以接受Writer对象作为输出的对象。当原先是使用Writer对象在作字符处理,而现在想要套用println()之类的方法时,使用PrintWriter会是比较方便的作法。

Ø  范例显示了PrintStreamPrintWriter两个对象在处理相同输出目的时的作法,程序会输出简体中文GB2312编码的文字至指定的文件中。

Ø  范例StreamWriterDemo.java

package onlyfun.caterpillar;

import java.io.*;

public class  StreamWriterDemo {
     
public static void main(String[]  args) {
          try {
              // "简体中文"四个字的 GB2312 编码

             
byte[] sim = {(byte)0xbc,  (byte)0xf2,(byte)0xcc, (byte)0xe5,(byte)0xd6, (byte)0xd0,(byte)0xce,  (byte)0xc4};
              // 数组作为流来源
             
ByteArrayInputStream  byteArrayInputStream =new ByteArrayInputStream(sim);
              InputStreamReader  inputStreamReader =new InputStreamReader(byteArrayInputStream,  "GB2312");

            // PrintWriter还接受Writer实例作为变量
             
PrintWriter printWriter  =new PrintWriter(new OutputStreamWriter(new FileOutputStream(args[0]),  "GB2312"));

            int in = 0;

            printWriter.print("PrintWriter:  ");
              // 写入数组内容

             
while((in =  inputStreamReader.read()) != -1)  {
                  printWriter.print((char)in);
              }
              printWriter.println();

            printWriter.close();
              byteArrayInputStream.reset();

            // PrintStream 接受OutputStream实例作为变量
             
PrintStream printStream =new  PrintStream(new FileOutputStream(args[0], true),true, "GB2312");

            printStream.print("PrintStream:  ");
              // 写入数组内容

             
while((in =  inputStreamReader.read()) != -1)  {
                  printStream.print((char)in);
              }
              printStream.println();

            inputStreamReader.close();
              printStream.close();
          }catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("没有指定文件
");
         
}catch(IOException e) {
              e.printStackTrace();

        }
      }
  }

Ø  由于执行后文件中的文字是GB2312编码,所以必须使用指定编码的文字检视器才可以正确看到文字内容。最简单的方式就是使用浏览器并指定视图编码为GB2312,输出的文本文件内容如图所示。

 

使用Internet  Explorer查看范例14.20的输出文件

CharArrayReaderCharArrayWriter

Ø  前面介绍了ByteArrayInputStreamByteArrayOutputStream,它们是将位数组当作流输入来源、输出目的地的工具类。与其类似的是java.io.CharArrayReaderjava.io.CharArrayWriter,使用它们可以将字符数组当作字符数据输出或输入的来源。

Ø  举个应用的例子,通常您很少会对文本文件进行随机存取的动作,因为要与ASCII兼容,一个文本文件中可能会有ASCII与非西欧语系的双字节字符。也就是说,在文本文件中,英文字符与中文字符的长度不一定相同,对文本文件进行随机存取容易发生错误。

Ø  由于Java中的字符是Unicode字符,通过这个特性,可以将文本文件的内容读入字符数组,对字符数组作随机存取,然后再将之写回文件。这样对于文本文件也可以达到类似随机存取的功能。

Ø  范例可以打开一个简单的文本文件,其中有简单的英文与中文字符。在读取文件之后,可以直接以程序来指定文本文件的字符位置,以修改所指定的字符。程序的作法是将字符读入字符数组中,修改指定索引处的数组元素。在修改完数组内容之后,重新将字符数组存回文件。

Ø  范例 CharArrayReaderWriterDemo.java

package onlyfun.caterpillar;

import java.io.*;
  import java.util.*;

public class  CharArrayReaderWriterDemo {
     
public static void main(String[]  args) {
          try {
              File file = new File(args[0]);
              BufferedReader  bufInputReader =new BufferedReader(new FileReader(file));

            // 将文件读入字符数组
             
CharArrayWriter  charArrayWriter =new CharArrayWriter();
              char[] array = new char[1];
              while(bufInputReader.read(array)  != -1) {
                  charArrayWriter.write(array);
              }

            charArrayWriter.close();
              bufInputReader.close();

            // 显示字符数组内容
             
array =  charArrayWriter.toCharArray();
              for(int i = 0; i <  array.length; i++)
                  System.out.print(array[i]  + " ");
                  System.out.println();

                // 让用户输入位置与字符修改字符数组内容
                 
Scanner scanner = new  Scanner(System.in);

                System.out.print("输入修改位置:");
                 
int pos =  scanner.nextInt();
                  System.out.print("输入修改字符:
");
                 
char ch =  scanner.next().charAt(0);
                  array[pos-1] = ch;

                // 将字符数组内容存回文件
                 
CharArrayReader charArrayReader  =new CharArrayReader(array);
                  BufferedWriter  bufWriter =new BufferedWriter(new FileWriter(file));
                  char[] tmp = new  char[1];
                  while(charArrayReader.read(tmp)  != -1) {
                      bufWriter.write(tmp);
                  }

                charArrayReader.close();
                  bufWriter.flush();
                  bufWriter.close();
              }catch(ArrayIndexOutOfBoundsException  e) {
                  System.out.println("没有指定文件
");
             
}catch(IOException e) {
                  e.printStackTrace();
          }
      }
  }

Ø  假设文本文件中为:"一个test!一个测试!",一个执行程序的范例做如下修改后,文本文件的内容会是:"一个Test!一个测试!"

T e s t
输入修改位置:3
输入修改字符:t

PushbackReader

Ø  java.io.PushbackReader14.2.9节介绍的PushbackInputStream类似,都拥有一个PushBack缓冲区,只不过PushbackReader所处理的是字符。从这个对象读出数据后,如果愿意的话,只要PushBack缓冲区没有满,就可以使用unread()将数据推回流的前端。

Ø  范例可以将文本文件中的一些数学符号:<><=>=!==转换为BIG5码中的<、>、≦、≧、、=等全角符号并另存新文件。

Ø  范例PushbackReaderDemo.java

package onlyfun.caterpillar;

import java.io.*;

public class  PushbackReaderDemo {
     
public static void main(String[]  args) {
          char[] symbols = {'', '','', '','≠', ''};

        try {

            PushbackReader pushbackReader  =new PushbackReader(new FileReader(args[0]));

            FileWriter fileWriter =new  FileWriter("math_"+ args[0]);

            int c = 0;

            while((c = pushbackReader.read())  != -1) {
                  int poss = -1;

                switch(c) {
                      case '<': poss =  0; break;
                      case '>':poss = 1; break;
                      case '!':poss = 2;  break;
                      case '=':poss = 5;  break;
                      default: fileWriter.write(c);
                  }

                if(poss != -1) {
                      if((c = pushbackReader.read())  == '=') {
                          fileWriter.write(symbols[poss  + 2]);
                          fileWriter.write('  ');
                      }else {
                          pushbackReader.unread(c);
                          fileWriter.write(symbols[poss]);
                      }
                  }
              }

            pushbackReader.close();
              fileWriter.close();
          }catch(ArrayIndexOutOfBoundsException  e) {
              System.out.println("请指定文件
");
         
}catch(IOException e) {
              e.printStackTrace();
          }
      }
  }

Ø  假设文本文件中有以下内容:

x + y <= 3
  y + z != w
  1 + x >= 4
  x + y > 3
  x - y < 4

Ø  则执行程序后的另存新文件内容会是:

x + y   3
  y + z ≠  w
  1 + x
  4
  x + y
3
  x - y
4

Ø  即使本章已经花了很长的篇幅介绍Java的输入/输出,但事实上还只是介绍了输入/输出的基本应用而已。虽然输入/输出的应用遍及各个领域,但基本上就是对流的操作概念的延伸。以本章为基础,在涉及其他相关的输入/输出议题时就比较容易上手。

5.      字符集

字符集的概念

Ø  字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。

Ø  字符集是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,字符集名称:ASCII字符集、GB2312字符集、BIG5字符集、 GB 18030字符集、Unicode字符集等。计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。

Ø  中文文字数目大,而且还分为简体中文和繁体中文两种不同书写规则的文字,而计算机最初是按英语单字节字符设计的,因此,对中文字符进行编码,是中文信息交流的技术基础。

Ø  ASCII(American Standard Code for InformationInterchange,美国信息互换标准代码),是基于常用的英文字符的一套电脑编码系统。我们知道英文中经常使用的字符、数字符号被计算机处理时都是以二进制码的形式出现的。这种二进制码的集合就是所谓的ASCII码。每一个ASCII码与一个8位(bit)二进制数对应。其最高位是0,相应的十进制数是0-127。如,数字“0”的编码用十进制数表示就是48。另有128个扩展的ASCII码,最高位都是1,由一些制表符和其它符号组成。ASCII是现今最通用的单字节编码系统。

Ø  GB2312GB2312码是中华人民共和国国家汉字信息交换用编码,全称《信息交换用汉字编码字符集-基本集》。主要用于给每一个中文字符指定相应的数字,也就是进行编码。一个中文字符用两个字节的数字来表示,为了和ASCII码有所区别,将中文字符每一个字节的最高位置都用1来表示。

Ø  GBK:为了对更多的字符进行编码,国家又发布了新的编码系统GBK(GBKK是“扩展”的汉语拼音第一个字母)。在新的编码系统里,除了完全兼容GB2312 外,还对繁体中文、一些不常用的汉字和许多符号进行了编码。

Ø  ISO-8859-1:是西方国家所使用的字符编码集,是一种单字节的字符集,而英文实际上只用了其中数字小于128的部分。

Ø  Unicode:这是一种通用的字符集,对所有语言的文字进行了统一编码,对每一个字符都用2个字节来表示,对于英文字符采取前面加“0”字节的策略实现等长兼容。如 “a” ASCII码为0x61UNICODE就为0x000x61

Ø  UTF-8Eight-bit UCS Transformation Format(UCSUniversalCharacter Set,通用字符集UCS 是所有其他字符集标准的一个超集)一个7位的ASCII码值,对应的UTF码是一个字节。如果字符是0x0000,或在0x00800x007f之间,对应的UTF码是两个字节,如果字符在0x08000xffff之间,对应的UTF码是三个字节。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值