学习笔记之IO系统

Java IO系统

前言:

"对语言设计人员来说,创建好的输入/输出系统是一项特别困难的任务."

原因:

1、有三种不同的种类的IO需要考虑(文件,控制台,网络连接)
2、需要大量不同的方式与它们通信(顺序,随机访问,二进制,字符,按行,按字等等..)

PS: deprecate ['deprikeit]
 vt.
 抗议, 不赞成, 抨击, 反对, 藐视, 轻视


输入和输出

1、可将Java库的IO类分割为输入与输出两个部分
   如:
   InputStream  -- 与输入有关的所有类的基类
   OutputStream -- 与输出有关的所有类的基类
  
2、我们之所以感到Java的流库(Stream Library)异常复杂,正是由于为了创建单独的一个结果流,却需要创建多个对象的缘故.
3、输入流类型
   字节数组 -- ByteArrayInputStream
   字符串 -- StringBufferInputStream
   文件  -- FileInputStream
   管道  -- PipedInputStream
   其他  -- 如Internet连接
  
   相关描述:
   ByteArrayInputStream -- 将内存中的一个缓冲区作为数据源来使用,从中提取字节.
   StringInputStream  -- 同上
   FileInputStream  -- 用于从文件读取信息,可将其与FilterInputStream对象连接,提供一个有用的接口.
   PipedInputStream -- 对PipedOutputStream产生写的数据,作为一个数据源使用.可将其与FilterInputStream连接
   SequenceInputStream -- 将两个或更多的InputStream对象转换为单个InputStream使用.可将其与FilterInputStream连接
4、输出流类型
   字节数组 -- ByteArrayOutputStream
   文件  -- FileOutputStream
   管道  -- PipedOutputStream

   相关描述:
   ByteArrayOutputStream -- 将内存中的一个缓冲区作为数据置入目的地.可选缓冲区的初始大小.FilterOutputStream
   FileOutputStream   -- 将信息发给一个文件.与FilterOutputStream对象连接起来,可提供一个有用的接口.
   PipedOutputStream  -- 我们写给它的任何信息都会自动成为相关的PipedInputStream的输出.
  


添加属性和有用的接口


1、装饰器方案
   概念:利用层次化对象动态和透明地添加单个对象的做法
   规定:封装于初始化对象中的所有对象都拥有相同的接口.
  
   抽象的"过滤器"类是所有装饰器的基础类.
   FilterInputStream 和FilterOutputStream提供了相应的装饰器接口,用于控制一个特定的输入输出流.它们都属于抽象类.
2、通过FilterInputStream从InputStream里读取数据
   FilterInputStream子类的两种行为:
   -- 读取数据 (如DataInputStream读取不同的基本类型数据以及String对象 ..)
   -- 修改InputStream的内部行为方式.包括:是否进行缓冲,是否跟踪读入的数据行,是否推回一个字符等..
   FilterInputStream的类型
   DataInputStream   -- 与DataOutputStream联合使用,读取流中的基本数据类型.
   BufferedInputStream  -- 要求使用缓冲区同一个接口对象连接到一起
   LineNumberInputStream -- 跟踪输入流中的行号,可调用getLineNumber()及setLineNumber(int)添加对数据行编号的能力.
   PushbackInputStream   -- 有一个字节的后推缓冲区
3、通过FilterOutputStream向OutputStream里写入数据
   DataOutputStream  -- 对各个基本数据类型以及String对象进行格式化,并将其置入一个数据流中.
   PrintStream      -- 用于产生格式化输出(静态对象System.out 是一个PrintStream).
   BufferedOutputStream -- 避免每次发出数据的时候都要进行物理性的写入
  
   PS:DataOutputStream控制的是数据的"存储",而PrintStream控制的是"显示"
     
     

本身的缺陷:RandomAccessFile

1、RandomAccessFile不属于InputStream或者OutputStream分层结构的一部分,它仅仅是实现了DataOutput以及DataInputStream接口.
2、一些方法
  getFilePointer()  -- 获取当前读写位置
  seek()     -- 设置文件读写位置
  length()     -- 获取文件长度
 
 

File 类

File类并非只是代表一个特定文件,它亦可以表示一个目录路径.用list()方法查询此目录内的文件.

1、目录列表器

   在采用list方法时,若不带"目录过滤器"则将列出File对象所包含的所有完整列表.

例:

import java.io.*;

public class DirList {
 public static void main(final String[] args)
 {
  
  File path = new File(".");
  String[] list;
  
  if (args.length == 0)
   list = path.list();
  else
   list = path.list(new FilenameFilter()
   {
    public boolean accept(File pach,String name)
    {
     return name.endsWith(args[0]) ;
    }
   });
   
  for (int i = 0; i < list.length; i ++)
   System.out.println(list[i]);  
 }
}

   FillenameFilter接口

   public interface FilenameFilter {
 boolean accept(File 文件目录,String 文件名);
   }


2、格式化内存输入
   StringBufferInputStream类的方法有限,通常将其封装到一个DataInputStream内,从而增强它的能力  
   import java.io.*;  
   public class TestEOF {
    public static void main(String[] args)
    {
     try{
      DataInputStream in = new DataInputStream(new BufferedInputStream(
         new FileInputStream("TestEOF.java")));
      
      while(in.available() != 0)
       System.out.print((char)in.readByte());
     }catch(IOException e){
     System.err.println("IO Exception");
    }
    }
   }
3、首先创建一个FileOutputStream,用它同一个文件连接,考虑到效率方面的原因,它需生成一个BufferedOutputStream.
4、从标准输入中读取数据
   System.in -- 标准输入 InputStream对象
   System.out -- 标准输出 PrintStream对象
   System.err -- 标准错误输出 PrintStream对象
  
   我们希望用readLine()每次读取一行输入信息,所以需要将System.in封装到一个DataInputStream中  
   import java.io.*;  
   public class Echo {
    public static void main(String[] args){
     DataInputStream in = new DataInputStream(new BufferedInputStream(System.in));
     String s;
     try{
      while((s=in.readLine()) != null){
       if (s.equalsIgnoreCase("exit")) break;
       System.out.println(s);
      }
     }catch(IOException e){
      e.printStackTrace();
     }
    }
   }
5、管道数据流
   管道化的数据流用于线程之间的通信  
  
StreamTOkenizer
   未记录

Java1.1 的IO流

1、老式IO流层次只支持8位字节流,为了提供16位Unicode面向国际化支持,Java1.1里添加了Reader和Writer层次
2、"桥"类,连接新类和老类
   InputStreamReader -- 将InputSream转换为Reader
   OutputStreamReader -- 将OutputStream转换为Writer
3、数据的发起与接收
   PS:在旧流库的基础上新加了java.util.zip库,它们依赖旧的流组件;故尝试性地使用Reader和Writer类,若代码不能通过编译,便知道必须换回老式库
  
   旧库与新库对应的信息发起与接收  
   java 1.0 class  java 1.1 class
   InputStream   Reader
   OutputStream   Writer
   FileInputStream  FileReader
   FileOutputStream  FileWriter
   StringBufferInputStream StringReader
   no class   StringWriter
   ByteArrayInputStream  charArrayReader
   ByteArrayOutputStream charArrayWriter
   PipedInputStream  PipedReader
   PipedOutputStream  PipedWriter
4、修改数据流的行为
   过滤器
   Java 1.0 class  Java 1.1 class
   FilterInputStream  FilterReader
   FilterOutputStream  FilterWriter(没有子类的抽象类)
   BufferedInputStream  BufferedReader(也有readLine()方法)
   BufferedOutputStream  BufferedWriter
   DataInputStream  DataInputStream
   PrintStream   PrintWriter
   LineNumberInputStream LineNumberReader
   StreamTokenizer  StreamTokenizer (构造函数接收Reader对象)
   PushBackInputStream  PushBackReader
  
   PS:若想使用readLine(),应使用BufferedReader,而不是用一个DataInputStream来实现.
5、未改变的类
   没有对应Java 1.1类的Java 1.0类
   -- DataOutputStream
   -- File
   -- RandomAccessFile
   -- SequenceInputStream
   特别是未加改动的DataOutputStream,所以为了用一种可转移的格式保存和获取数据,必须沿用InputStream和OutputStream层次结构
6、程序片段
   BufferedReader stdin =
        new BufferedReader(
          new InputStreamReader(System.in));
   此片段封装System.in,以便读取控制台输入的新方法,由于System.in是一个DataInputStream,而且BufferedReader需要一个Reader参数,所以要用
   InputStreamReader来进行转换
7、Java 1.1 存在的一个错误
   import java.io.*;  
   public class IOBug {
    public static void main(String[] args){
     try{
      DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("data.txt")));
      out.writeDouble(3.14159);
      out.writeBytes("That was the value of pi/n");
      out.writeBytes("This is pi/2/n");
      out.writeDouble(3.14159 / 2);
      out.close();
      
      DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("data.txt")));
      BufferedReader br = new BufferedReader(new InputStreamReader(in));
      
      System.out.println(in.readDouble());
      System.out.println(br.readLine());
      System.out.println(br.readLine());
      System.out.println(in.readDouble());
     }  catch(FileNotFoundException e){
      e.printStackTrace();
     }catch(IOException e){
      e.printStackTrace();
     }
    }
   }
   说明:
    -- 对于一个writeBytes()写入的任何东西不是都能够恢复的.
    -- 若将 br 更改为 in ,则会有不赞成的警告(但可以恢复数据)
    -- 尽量使用BufferedReader的readLine()方法,而不是DataInputStream
8、重导向标准IO
   Java 1.1在System类中添加特殊的静态方法,允许我们重新定向标准输入、输出以及错误IO流.
   setIn(InputStream)
   setOut(PrintStream)
   setErr(PrintStream)
  
   import java.io.*;
   public class Redirecting {
 public static void main(String[] args){
  try{
   BufferedInputStream in = new BufferedInputStream(new FileInputStream("Redirecting.java"));
   PrintStream out = new PrintStream(new BufferedOutputStream(new FileOutputStream("test.out")));
   System.setIn(in);
   System.setOut(out);
   System.setErr(out);
   
   BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
   String s;
   while((s = br.readLine()) != null)
    System.out.println(s);
   out.close();
  }catch(IOException e){
   e.printStackTrace();
  }
 }
   }  
   程序的作用是将标准输入同一个文件连接起来,并将标准输出和错误重定向至另一个文件
   疑问:无论是System.setOut()还是System.setErr()都要求一个PrintStream作为参数使用.既然Java 1.1通过反对构建器而反对了整个PrintStream,为什么库的设计人员在添加这个反对的同时,依然为System添加新方法并指明要求用PrintStream,而不是用PrintWriter呢?毕竟,后者是一个崭新的替换措施啊!

压缩

1、Java 1.1 压缩类   功能  描述
   CheckedInputStream   getCheckSum() 为任何InputStream产生校验和(不仅是压缩)
   CheckedOutputStream  getCheckSum() 为任何OutputStream产生校验和(不仅是解压)
   DeflaterOutputStream    压缩类的基础类
   ZipOutputStream    DeflaterOutputStream子类,将数据压缩成ZIP格式
   GZIPOutputStream    DeflaterOutputStream子类,将数据压缩成GZIP格式
   InflaterInputStream    解压的基础类
   ZipInputStream    InflaterInputStream子类,解压ZIP文件
   GZIPInputStream    InflaterInputStream子类,解压GZIP文件
2、用GZIP进行简单压缩
   import java.io.*;
   import java.util.zip.*;  
   public class GZIPcompress {
    public static void main(String[] args){
     try{
      BufferedReader in = new BufferedReader(new FileReader(args[0]));
      BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(
       new FileOutputStream("GZip.gz")));// 压缩
      System.out.println("Writing file...");
      int c;
      while((c = in.read()) != -1)
       out.write(c);
      in.close();
      out.close();
      System.out.println("Reading file");
             BufferedReader in2 = new BufferedReader(new InputStreamReader(new GZIPInputStream(
       new FileInputStream("GZip.gz"))));// 解压
      String s;
      while((s = in2.readLine())!= null)
       System.out.println(s);
     }catch(Exception e){
      e.printStackTrace();
     }
    }
   }
3、Java归档(jar)实用程序
   JAR文件是跨平台的,所以不必关心涉及具体平台的问题.除了可以包含声音和图象文件以外,也可以在其中包括类文件
   一个JAR文件又一系列采用ZIP压缩格式的文件构成,同时还有一张"详情单"(Manifest),对所有这些文件进行描述
  
   jar  [选项] 说明 [详情单] 输入文件
  

对象序列化

1、对象的序列化处理非常简单,只需对象实现了Serializable接口即可.
2、序列化过程:
   创建某些OutputStream对象 --> 封装到ObjectOutputStream对象内 -->调用writeObject()
   创建某些InputStream对象  --> 封装到ObjectInputStream对象内  -->调用readObject()
  
   PS:对象序列化特别"聪明"的一个地方是它不仅保存了对象的"全景图",而且能追踪对象内包含的所有句柄并保存那些对象;
   接着又能对每个对象内包含的句柄进行追踪;以此类推。我们有时将这种情况称为"对象网",单个对象可与之建立连接。而
   且它还包含了对象的句柄数组以及成员对象。若必须自行操纵一套对象序列化机制,那么在代码里追踪所有这些链接时可能
   会显得非常麻烦。在另一方面,由于Java对象的序列化似乎找不出什么缺点,所以请尽量不要自己动手,让它用优化的算法
   自动维护整个对象网
  
   // file 1
   import java.io.*;
   public class Alien implements Serializable {}
  
   // file 2
   import java.io.*;  
   public class FresszeAlien {
    public static void main(String[] args) throws Exception{
     ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("file.x"));
     Alien zorcon = new Alien();
     out.writeObject(zorcon);
     
     ObjectInputStream in = new ObjectInputStream(new FileInputStream("file.x"));
     Object mystery = in.readObject();
     System.out.println(mystery.getClass().toString());
    }
   }
  
3、序列化的控制
   描述:涉及到安全问题,我们可能不希望对象的某一部分序列化,或者某一子对象完成不必序列化.对象恢复以后那部分才需要重新创建
   解决:通过实现Externalizable接口,代替Serializable接口
   Externalizable方法:
   -- writeExternal()
   -- readExternal()
   注意:采用Externalizable,在序列化和重新装配的过程中,会自动调用构造函数和这两个方法
  
   import java.io.*;
   class Blip1 implements Externalizable {
 Blip1()
 {
  System.out.println("Construct in Blip1");
 }
 public void writeExternal(ObjectOutput in)
 {
  System.out.println("Blip1 writeExternal");
 }
 public void readExternal(ObjectInput out)
 {
  System.out.println("Blip1 readExternal");
 }
   }

   class Blip2 implements Externalizable {
 public Blip2()
 {
  System.out.println("Construct in Blip2");
 }
 public void writeExternal(ObjectOutput in)
 {
  System.out.println("Blip2 writeExternal");
 }
 public void readExternal(ObjectInput out)
 {
  System.out.println("Blip2 readExternal");
 }
   }

   public class TestExternalizable {
 public static void main(String[] args) throws Exception{
  Blip1 bp1 = new Blip1();
  Blip2 bp2 = new Blip2();
  
  ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("bp.x"));
  out.writeObject(bp2);
  out.writeObject(bp1);
  
  ObjectInputStream in = new ObjectInputStream(new FileInputStream("bp.x"));
  
  System.out.println("Recovering ..");  
  bp2 = (Blip2)in.readObject();
  //bp1 = (Blip1)in.readObject();  // 此处由于Blip1构造函数为friend访问控制,故无法在客户端恢复对象
 }
   }
  
  
   若从一个Externalizable对象继承,通常需要调用writeExternal()和readExternal()的基础类版本,以便正确地保存和恢复基础类组件.
   所以为了让一切正常运作起来,千万不可仅在writeExternal()方法执行期间写入对象的重要数据(没有默认的行为可用来为一个Externalizable对象写 
   入所有成员对象)的,而是必须在readExternal()方法中也恢复那些数据.如下:
  
   import java.io.*;                               
   import java.util.*;                                                         
   class Blip3 implements Externalizable {         
     int i;                                        
     String s;                     
     public Blip3() {                              
       System.out.println("Blip3 Constructor");       // s, i 未初始化                      
     }                                             
     public Blip3(String x, int a) {               
       System.out.println("Blip3(String x, int a)");
       s = x;                                      
       i = a;                                                       
     }                                             
     public String toString() { return s + i; }    
     public void writeExternal(ObjectOutput out) throws IOException {                      
       System.out.println("Blip3.writeExternal");  
       // You must do this:                        
       out.writeObject(s); out.writeInt(i);        
     }                                             
     public void readExternal(ObjectInput in)  throws IOException, ClassNotFoundException {
       System.out.println("Blip3.readExternal");   
       // You must do this:                        
       s = (String)in.readObject();                
       i =in.readInt();                            
     }                                             
     public static void main(String[] args) {      
       System.out.println("Constructing objects:");
       Blip3 b3 = new Blip3("A String ", 47);      
       System.out.println(b3.toString());          
       try {                                       
         ObjectOutputStream o =                    
           new ObjectOutputStream(                 
             new FileOutputStream("Blip3.out"));   
         System.out.println("Saving object:");     
         o.writeObject(b3);                        
         o.close();                                
         // Now get it back:                       
         ObjectInputStream in =                    
           new ObjectInputStream(                  
             new FileInputStream("Blip3.out"));    
         System.out.println("Recovering b3:");     
         b3 = (Blip3)in.readObject();              
         System.out.println(b3.toString());        
       } catch(Exception e) {                      
         e.printStackTrace();                      
       }                                           
     }                                             
   }      
4、transient(临时)关键字
   问题描述:Java的序列化机制会自动保存和恢复子对象,因此对于一些敏感信息,即使它在对象中具有'private'属性,但
        一旦经序列化处理,人们也可以通过读取一个文件,或者拦截网络传输得到它.
   解决方法:
   -- 将自己的类实现Externalizable接口
   -- 用transient逐个字段关闭序列化
  
   例如,对于一个登陆会话有关的信息,校验登陆的合法性时,一般都想将数据保存下来,但不包括密码
   import java.io.*;
   import java.util.*;  
   class Logon implements Serializable {
    private Date dt = new Date();
    private String username ;
    private transient String password;
    
    Logon(String name,String pwd){
     username = name;
     password = pwd;
    }
    
    public String toString(){
     String pwd = (password == null)?"(n/a)" : password;
     
     return "/nlogon info:/n" + "username:/t" + username +
      "/ndate:/t/t" + dt.toString() +
      "/npassword:/t" + pwd ;
    }
    
    public static void main(String[] args){
     Logon a = new Logon("Hulk","myLittlePony");
     System.out.println("/nlogon a = " + a);
     try{
      ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("Logon.out"));
      o.writeObject(a);
      o.close();
      
      // 延迟
      int seconds = 5;
      long t = System.currentTimeMillis() + seconds*1000;
      while(System.currentTimeMillis() < t);
      
      // 提取
      ObjectInputStream in = new ObjectInputStream(new FileInputStream("Logon.out"));
      
      System.out.println("Recovering object at " + new Date());
      a = (Logon)in.readObject();
      System.out.println("/nlogon a = " + a);
     }catch(Exception e){
      e.printStackTrace();
     }
    } 
   }
   PS:由于Externalizable对象默认时不保存它的'任何'字段,所以transient关键字只能伴随Serializable使用
5、Externalizable的替代方法
   实现Serializable接口,并添加(非"覆盖"或"实现")以下方法.一旦对象被序列化或者重新分配.就会分别调用
   那两个方法.即:只要提供了这两个方法,就会优先使用它们,而不考虑默认的序列化机制
  
   private void writeObject(ObjectOutputStream stream) throws IOException ;
   private void readObject(ObjectInputStream stream) throws IOException ,ClassNotFoundException;
  
   引用:大家可能奇怪ObjectOutputStream和ObjectInputStream如何有权访问我们的类的private方法----只能认为这是序列化机制玩的一个把戏.
  
   程序片段
   private transient String b;
   private void writeObject(ObjectOutputStream stream) throws IOException {                       
       stream.defaultWriteObject();   // 写入非transient部分               
       stream.writeObject(b);                       
   }                                              
   private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
       stream.defaultReadObject();                  
       b = (String)stream.readObject();             
   }
  附:
   import java.io.*;
   public class SerialCtrl implements Serializable{
 private String a ;
 private transient String b; 
 public SerialCtrl(String aa,String bb){
  a = aa;
  b = bb;
 } 
 public String toString(){
  return "[" + a + " , " + b + "]";
 }
 // 私有方法 writeObject 和 readObject 
 private void writeObject(ObjectOutputStream stream) throws IOException{
  stream.defaultWriteObject();  // 写入非transient字段
  stream.writeObject(b);   // 写入transient字段
 }
 private void readObject(ObjectInputStream stream) throws IOException ,ClassNotFoundException{
  stream.defaultReadObject();  // 恢复非transient字段
  b = (String)stream.readObject();  // 恢复transient 字段
 }
 // 主程序 
 public static void main(String[] args){
  SerialCtrl sc = new SerialCtrl("test1","test2");
  System.out.println("Before:/t" + sc);
  
  ByteArrayOutputStream buf = new ByteArrayOutputStream();
  try{
   ObjectOutputStream out = new ObjectOutputStream(buf);
   out.writeObject(sc); // 写入对象
   
   ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(buf.toByteArray()));
   SerialCtrl sc2 = (SerialCtrl)in.readObject(); // 恢复对象
   System.out.println("After:" + sc2);
  }catch(Exception e){
   e.printStackTrace();
  }
 } 
   }
6、序列化处理也会将private数据保存下来。若有需要保密的字段,应将其标记成transient                                              

你可能感兴趣的:(学习笔记之IO系统)