day21(上)_IO包中其他类(对象序列化/反序列化,管道流,随机访问文件,数据流,数组流)

 

1.对象序列化与反序列化

/*
ObjectInputStream
ObjectOutputStream
可以对对象进行操作,
我们一般在堆内存创建对象,没有引用指向该对象时,会被垃圾回收机制回收
为了保存对象,以及下次直接使用对象中封装的数据
将对象存储到硬盘上.
称作 对象的持久化存储/对象的序列化

api解释:
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。
可以使用 ObjectInputStream 读取(重构)对象。
通过在流中使用文件可以实现对象的持久存储
ObjectInputStream
   ObjectInputStream 对以前使用 ObjectOutputStream
   写入的基本数据和对象进行反序列化
*/

自定义Person类作为序列化目标:

package testclass;
import java.io.Serializable;
public class Person implements Serializable{
 private static final long serialVersionUID=100L;
  private String name;
 int age;
 public Person(String name,int age){
 this.name=name;
 this.age=age;
 }
 public String toString(){
  return name+"..."+age;
 }
 public String getName(){
 
 return name;
 }
}
/*
这里遇到个小问题:
java中如何用有名包中的类来调用默认包(无名包)中的类?
  经过查找跟jdk版本有关,目前用的1.7不允许这样做,只能加上包名
另行参照:http://unmi.cc/java-cannot-import-default-package
*/
package objectstream;
import testclass.Person;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
class ObjectStreamDemo{
    
    public static void writeObj()throws IOException{
      ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("Person.txt"));//创建写入指定 OutputStream 的 ObjectOutputStream。
                                                                                        
//
此构造方法将序列化流部分写入底层流
    
      oos.writeObject(new Person("zhang",12));//将指定的对象写入 ObjectOutputStream
      /*
      对象的类、类的签名(SerialVersionUID),以及类及其所有父类的非瞬态和非静态字段的值都将被写入
      */
      
      oos.close();
    }
    
    public static void readObj()throws IOException,ClassNotFoundException{
      ObjectInputStream ois=new ObjectInputStream(new FileInputStream("Person.txt"));//创建从指定 InputStream 读取的 ObjectInputStream。
      
      Person p=(Person)ois.readObject();//将读取流中的一个对象返回,当有多个对象可以采用循环读取
                                       //注意该方法上声明两个异常(IOException,ClassNotFoundException)
      System.out.println(p.toString());
      
      
    }
    public static void main(String[] args)throws IOException,ClassNotFoundException{
       
       writeObj();
       readObj();
    
    }
}

运行结果一:如果Person未实现Serializable接口:

NotSerializableException

/*
NotSerializableException(不可序列化异常)
     原因:Person对象不可序列化
     解决方法:令Person实现Serializable接口
    对于Serializable接口(标记接口:接口中没有方法)描述:
      类通过实现 java.io.Serializable 接口以启用其序列化功能。
      未实现此接口的类将无法使其任何状态序列化或反序列化。
*/
/*
readObject上声明的异常:
    ClassNotFoundException - 找不到序列化对象的类。 
                            当文件中存储的不是对象封装的数据,而是其它数据
*/

运行结果二:

  当对Person类进行修改后,重新编译生成新的Person.class(①)
      而Person.txt中保存的依然未修改的Person.class所new的对象(②)
          ①与②的serialVersionUID不同导致->InvalidClassExcetption

IvalidClassException

/*
 原因:该类的序列版本号(local class serialVersionUID)与从流中读取的类描述符的版本号(stream classdesc serialVersionUID)不匹配 
          当对Person类进行修改后,重新编译生成新的Person.class(①)
          而Person.txt中保存的依然未修改的Person.class所new的对象(②)
          ①与②的serialVersionUID不同导致->readObject方法抛出InvalidClassExcetption
    解决方法:
      在Person类中显式声明:
       ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;//ANY-ACCESS-MODIFIER:任意的访问控制修饰符(public,private,protect,default)
                                                                    //serialVersionUID 可以根据需要而定            
     在API中描述概括:
       1.如果未显式声明serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值.
       2.建议显式声明,因为计算默认的 serialVersionUID 对类的详细信息具有较高的敏感性(可以理解对原类稍加修改,就会导致serialVersionUID改变)
       3.建议使用private修饰, 原因是这种声明仅应用于直接声明类 -- serialVersionUID 字段作为继承成员没有用处

*/

正确运行结果:

序列化正确结果

                静态成员是否可以被序列化? 非静态成员不想被序列化怎么办?

将Person类修改为(如下),同时对于ObjectStreamDemo中writeObject(new Person(“zhangsan”,12,”kr”))//加上国籍

package testclass;
import java.io.Serializable;
public class Person implements Serializable{
 private static final long serialVersionUID=100L;
  private String name;
  int age;
  private static String country="cn";//添加一个静态成员
 public Person(String name,int age,String country){
 this.name=name;
 this.age=age;
 this.country=country;//非静态可以访问静态
 }
 public String toString(){
  
  return name+"..."+age+"..."+country;
 }
}

先writeObj(),然后再readObj()结果是:

  静态不可序列化

 

/*
 1.writeObj(),ReadObj()
   由于两者在同一个JVM中执行,导致
   new Person(zhang,12,haha)此时在方法区中静态字段
   country被赋值为haha
   但是写入Person.txt文件中的内容为:
   name=zhang,age=12
   
   接着ReadObj()执行,从文件中读出Person类对象(对象中封装的数据)
   也就是name=zhang age=12
   System.out.println(p.toString());
   调用Person.class中toString输出name,age还有country
   静态成员被当前运行的该类的所有对象共享,一个对象改变了,另一个对象
   使用改变后的值,因此此时country输出haha

   一句话: readObj读出的对象的country输出的是writeObj中new Person(zhang,12,haha)
           所改变后的值
2.把writeObj()注释掉,只运行readObj()
   会读出zhang..12..cn
  这是因为重启了JVM,方法区中的静态字段重新分配内存
   ,country=cn,静态字段没有任何改变因此依然为cn
3.为了更清晰:
  在main方法中加入
        writeObj();
       //new Person("li",10,"success");
       readObj();//可以看到输出结果country=success
*/

 

 

/*
对于private static String country="cn";
 即使oos.write(new Person("zhang",10,"kr"))
 打印结果中的country依然为cn,说明static成员不可被序列化(持久化存储)
 因为只能序列化堆内存中的数据,而静态在方法区中
如果不想序列化非静态成员呢?
  使用关键字 transient(短暂的)
  例如:transient int age;//其值只在堆内存中存在而不在文本中存在
  那么oos.write(new Person("zhang",10,"kr"))
  打印结果为 zhang...0...cn
*/

2.管道流:

/*
结合多线程技术的管道流(PipedInputStream,PipedOutputStream):
    对于过去读取流和写入流两者之间无任何关系
    经常通过数组(缓冲区)做为两者中转站.
 而对于管道流输入输出可以直接连接,通过结合线程使用 
api描述:
管道输入流应该连接到管道输出流;
管道输入流提供要写入管道输出流的所有数据字节。
通常,数据由某个线程从 PipedInputStream 对象读取,
并由其他线程将其写入到相应的 PipedOutputStream。
不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程
(
单线程情况下如果没有数据可读,该线程会一直等待,类比键盘录入
使用多线程使 读和写不相互影响,也就是说当读等待,可以执行写.
)
*/

 

package pipedstream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.IOException;
/*读线程*/
class Read implements Runnable{
 private PipedInputStream pis;
  public Read(PipedInputStream pis){
  this.pis=pis;
 }
 public void run(){
   try{
     byte[] buff=new byte[1024];
     int len=pis.read(buff);//即使cpu先切换读线程执行,由于没有数据可读,导致read方法堵塞,该线程等待,使
                            //cpu切换到写线程
     System.out.println(new String(buff,0,len));//从pis中读取到的字节使用GBK解码成字符串打印
     pis.close();
   }
   catch(IOException e){
      e.printStackTrace();
   }
 } 
}
/*写线程*/
class Write implements Runnable{
 private PipedOutputStream pos;
 public Write(PipedOutputStream pos){
  this.pos=pos;
 }
 public void run(){
   try{
      pos.write("管道流的读写".getBytes());//使用GBK将此 String 编码为 byte 序列
      pos.close();
   }
   catch(IOException e){
     throw new RuntimeException("管道写入流失败");
   }
 } 
}
class PipedStreamDemo{
   public static void main(String[] args)throws IOException{
    PipedInputStream pis=new PipedInputStream();
    PipedOutputStream pos=new PipedOutputStream();
    pis.connect(pos);//连接两个管道流,也可以在创建对象时连接
                      //不连接将发生IOException
    new Thread(new Read(pis)).start();
    new Thread(new Write(pos)).start();
    
   }
}

 

管道流

          

3.RandomAccessFile(随机访问文件)

/*
RandomAccessFile(随机访问文件)
 该类不是算是IO体系中子类
 而是直接extends Object
 但是它是IO包中的成员.因为它具备读写功能
 内部封装了一个数组,而且通过指针对数组的元素进行操作
 可以通过getFilePointer获取指针位置
 同时可以通过seek改变指针位置
其实读写的原理就是内部封装了字节输入流和输出流.
局限性:
  通过构造函数可以看出,该类只能操作文件
  操作模式:r,rw等

如果模式为"r"->不会创建文件.会去读取一个已存在的文件,文件不存在->FileNotFoundException
如果模式为"rw"->文件不存在会自动创建.已存在不会覆盖原文件
*/

示例:

package randomaccessfile;
import java.io.RandomAccessFile;
import java.io.IOException;
class RandomAccessFileDemo{
public static void write()throws IOException{
  RandomAccessFile raf=new RandomAccessFile("2.txt","rw");
  

  raf.write("李四".getBytes());
  //raf.write(258);//
只写入int型的低8位,也就是1byte,同OutputStream的write(int)方法一样 //258(二进制:23个0+100000010),只写入后8bit->为2 //
可以把258四个字节存放在一个byte数组中,逐个写入文件中来防止数据丢失
  raf.writeInt(97);
  raf.write("王五".getBytes());
  raf.writeInt(99);//将99以四个字节写入
  raf.close();

}
public static void read()throws IOException{
 /*
  首先明白:对于notepad.exe:ANSI是默认的编码方式。
  对于英文文件是ASCII编码,对于简体中文文件是GB2312编码
  (只针对Windows简体中文版,如果是繁体中文版会采用Big5码)。
 1.txt中的十六进制表现形式:
   C0 EE CB C4 00 00 00 61  CD F5 CE E5 00 00 00 63
     李    四     空 空 空 a     王    五     空 空 空 c
*/
 RandomAccessFile raf=new RandomAccessFile("1.txt","r");
   
    //raf.seek(8);//通过设置指针,read从字节数组第8个位置(第9个字节)开始读取,也就是王五
    //raf.skipBytes(8);//跳过前8个byte(read从第9个字节开始读取),可以实现和上面同样的效果,缺点:skipBytes不能往回跳
    byte[] byteArr=new byte[4];
    raf.read(byteArr);
    System.out.println("name="+new String(byteArr));//取出了 李四
    
    int age=raf.readInt();//利用已有方法将直接读取有符号的32bit(4byte)
    System.out.println("age="+age);
}

/*
该对象的构造函数要操作的文件不存在,会自动创建
如果存在,则不会重新创建一个新文件覆盖原有文件.

*/
 public static void write_2()throws IOException{
  RandomAccessFile raf=new RandomAccessFile("1.txt","rw");
  raf.seek(8*2);//紧跟在c后写入->实现了文件自动扩展
  //raf.seek(8*0);//则会覆盖掉 李四 c
  raf.write("马六".getBytes());
  raf.writeInt(123);
  raf.close();
 }


public static void main(String[] args)throws IOException{
    write();
    read();
    write_2();
}

}

RandomAccessFile应用:

/*
随机读写访问应用:
  实现数据的分段读写
 例如: byte:0    ~    10   11    ~    20    21   ~   30
               Thread-0       Thread-1        Thread-2
      每个线程负责一段数据写入(下载原理)
 如果对一般流使用多线程发生:
    byte:0 12 23....
    数据虽然存完,但是数据不连续->解码错误
*/
RandomAccessFile

4.对基本数据操作的IO流

/*
对基本数据类型的读取:
DataOutputStream:
 数据输出流允许应用程序以适当方式将基本 Java 数据类型写入输出流中
DataInputStream:
 数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型

*/
package datastream;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.FileOutputStream;
import java.io.FileInputStream;
class DataStreamDemo{
 public static void write()throws IOException{
   DataOutputStream dos=new DataOutputStream(new FileOutputStream("data.txt"));
   dos.writeInt(234);// 将一个 int 值以 4-byte 值形式写入基础输出流中,先写入高字节。
   dos.writeBoolean(true);//  将一个 boolean 值以 1-byte 值形式写入基础输出流。
   dos.writeDouble(32.31);//使用 Double 类中的 doubleToLongBits 方法将 double 参数转换为一个 long 值,然后将该 long 值以 8-byte 值形式写入基础输出流中,先写入高字节。
   dos.close();
 }
  public static void read()throws IOException{
   DataInputStream dis=new DataInputStream(new FileInputStream("data.txt"));
   System.out.println(dis.readInt()+" "+dis.readBoolean()+" "+dis.readDouble());
   dis.close();
 }
 
 /*
 以与机器无关方式使用 UTF-8 修改版编码将一个字符串写入基础输出流.
 writeUTF,只能用对应的readUTF
 */
 public static void writeUTF()throws IOException{
   DataOutputStream dos=new DataOutputStream(new FileOutputStream("utfUpdate.txt"));
   dos.writeUTF("你好");
   dos.close();
 }
  public static void readUTF()throws IOException{
    DataInputStream dis=new DataInputStream(new FileInputStream("utfUpdate.txt"));
   System.out.println(dis.readUTF());//
如果utfUpdate.txt(8byte)并不是utf-8修改版的编码方式 //
例如采用了utf-8编码的文件为6byte,则readUTF()发生EOFIOException
                                     //原因:本来需要读取8个字节,读了6个就结束了
   dis.close();        
  }
 public static void main(String[] args)throws IOException{

  write();
  Runtime.getRuntime().exec("notepad.exe data.txt");
  read();
 
  writeUTF();
  readUTF();
 }

}

DataStream

5.数组流:内存->内存

/*
内存->内存
ByteArrayInputStream:
  传入一个字节数组做为内部缓冲区以及数据源进行读取
ByteArrayOutputStream:
  将数据写入到内部封装的字节数组中.
注意一点:
  这两个流对象的源和目的均为字节数组,
  没有涉及到系统资源,因此不用close(),即使close()无意义,依然可以使用.

流操作规律中:
源设备:
 键盘(System.in),硬盘(FileStream),内存(ArrayStream)
目的设备: 
 控制台(System.out),硬盘(FileStream),内存(ArrayStream)
*/
package bytearrstream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
class ByteArrStreamDemo{
   public static void main(String[] args){
    ByteArrayInputStream bis=new ByteArrayInputStream("妹纸".getBytes());
    ByteArrayOutputStream bos=new ByteArrayOutputStream();
    int aByte=0;
    while((aByte=bis.read())!=-1)
       bos.write(aByte);
   System.out.println(bos.toString());
 }
}
/*
这两个流使用流的思想操作数组
因为对于数组只有两种操作:遍历(读取)与存储(写入)

此外还有提供类似功能的:
  CharArrayReader,CharArrayWriter(方便中文等其它字符操作)
  StringReader,StringWriter
*/
posted @ 2013-07-02 20:25  伊秋  阅读(416)  评论(0编辑  收藏  举报