ObjectInputStream与ObjectOutputStream
------- android培训、java培训、期待与您交流! ----------
由这两个类所操纵的对象,必须得实现Serializable序列化接口。Serializable接口属于标记接口,该接口中没有声明任何成员。其主要作用,是通过ID标识来注明可以进行某种活动的类型,以避免错误调用。这种标识是常量(final)、长整型(long)、静态(static)的,是根据具体实现该接口的类的成员来设定的。这也就是说,每次调用输出流所生成的对象文件,其标识也会随着成员变量性质的不同而不同(性质即变量声明的类型特征)。如果为了使现有新定义的类,能够通过对象流操纵过去定义过的类,那么我们可以将两个类的UID都固定为相同的定值。固定为定值,就意味着不使用jvm系统自动生成的UID,而是在定义的实现Serializable接口的类中,模仿UID的特点,固定的声明UID值,模版如下:
public static finallong serialVersionUID = 123L;
Serializable接口除了标识用于序列化读取,还会带来一个特点。那就是序列化操作时,只能序列化未经static和transient修饰的成员变量。static和transient都会使得我们通过ObjectOutputStream类生成的可持续存储对象的对应被修饰成员变量无法改变,而只能拥有该对象的类中声明时所赋予的值。因此,这两个关键字常用来保证对象中一些我们不想通过序列化复制改变的成员变量,static用于静态,transient用于非静态。
补充:有关transient、volatile、strictfp这三个关键字
这里我们补充一点额外的知识,有关transient、volatile、strictfp这三个关键字的不同:
transient声明一个实例变量时,当对象存储,它的值不需要维持。如果T类的一个对象写入一个持久的存储区域,transient声明的内容不被保存,但其他的将被保存。
volatile修饰符告诉编译器被volatile修饰的变量可以被程序的其他部分改变。在多线程程序中,有时两个或更多的线程共享一个相同的实例变量。考虑效率问题,每个线程可以自己保存该共享变量的私有拷贝。实际的变量副本在不同的时候更新,如当进入synchronized方法时。
用strictfp修饰类或方法,可以确保浮点运算(以及所有切断)正如早期的Java版本那样准确。切断只影响某些操作的指数。当一个类被strictfp修饰,所有的方法自动被strictfp修饰。
strictfp的意思是FP-strict,也就是说精确浮点的意思。在Java虚拟机进行浮点运算时,如果没有指定strictfp关键字时,Java的编译器以及运行环境在对浮点运算的表达式是采取一种近似于我行我素的行为来完成这些操作,以致于得到的结果往往无法令你满意。而一旦使用了strictfp来声明一个类、接口或者方法时,那么所声明的范围内Java的编译器以及运行环境会完全依照浮点规范IEEE-754来执行。因此如果你想让你的浮点运算更加精确,而且不会因为不同的硬件平台所执行的结果不一致的话,那就请用关键字strictfp。
你可以将一个类、接口以及方法声明为strictfp,但是不允许对接口中的方法以及构造函数声明strictfp关键字,例如下面的代码即为正确写法:
strictfp class A { }
strictfp interface B{}
public static strictfpvoid Manual ( ){ }
ObjectOutputStream可以通过该类,将本应存在于堆中的对象,通过流的方式存储到硬盘当中,实现可持续存储。如果我们是通过 ObjectOutputStream类创建对象,那么由于对象创建后不再位于堆中,当需要动用的时候,我们就必须通过ObjectInputStream将该对象文件,通过流的方式传输到程序使用堆中。这样,我们就无法像非持久存储情况下,随建随用的使用对象,而是通过固定的格式来使用对象了。因此,在该结构中,ObjectOutputStream建立,对应ObjectInputStream读取使用,两者呼应。两个类中对应的对象写入、读取方法分别为writeObject与readObject。我们有下例,可以说明这两个类的具体使用方法:
import java.io.*;
class ObjectIODemo
{
public static void main(String[] args)throws IOException
{
objectWrite(); //写入对象到硬盘对应位置
objectRead(); //读取对应非临时存储的对象
}
//此方法为创建非临时存储对象所使用
public static void objectRead() throwsIOException
{
//读取存储文件
ObjectInputStream in =
new ObjectInputStream(newFileInputStream("ObjTest.txt"));
Student t =(Student)in.readObject();
System.out.println(t); //这里输出时会自动调用t的toString方法
in.close();
}
public static void objectWrite() throwsIOException
{
//创建输出流,指定文件存储位置
ObjectOutputStream out =
new ObjectOutputStream(newFileOutputStream("ObjTest.txt"));
Student t = newStudent("Lee",18,"BJ");
//将对象写入到文件中
out.writeObject(t);
out.close();
}
}
被创建的对象所对应的student类为:
import java.io.*;
class Studentimplements Serializable
{
public static final long serialVersionUID= 123L; //类的序列号
transient int age; //transient的使用,使得age无法通过序列化赋值
String name;
static String province = "L";
Student(String name,int age,String pro)
{
this.name = name;
this age = age;
this.province = pro;
}
public String toString()
{
return name+" ||"+age+" || "+province;
}
}
如果存多个对象,则在调用ObjectOutputStream的writeObject方法写入的时候,我们直接多次调用该方法写入多个对象即可。因为每个对象后面都会自动添加标识,所以,当我们通过ObjectInputStream的readObject方法读取对象时,该方法即会自动的一则一则读取。
管道流PipedInputStream和PipedOutputStream
原有的读写操作,我们需要额外定义一个暂存,用来作为数据通道。管道流Piped系列则能够实现输入输出的直接沟通。即管道输入流提供写到管道输出流中的数据,管道输出流连接到管道输入流来创建通信管道。两者是相互关联同时使用的,使用一个,就必须使用另一个。
我们从它们两个类中的构造方法可以看出,这两个类有两种关联方法。第一种,通过构造函数PipedInputStream(PipedOutputStream out)与PipedOutputStream(PipedInputStream in)来直接关联;第二种,可以通过Piped系字节流类中的connect(PipedInputStream in)与connect(PipedOutputStream out)来间接的建立联系。
我们也可以通过在初始化的时候,通过构造函数参数int pipeSize来设定管道大小。需要注意的是,Piped系列类,它们随着程序的结束而结束,中间不会留下任何文件形式的持久存储。因此,这种使用方法常常用于建立通信时使用。
随机访问文件类RandomAccessFile
所谓的随机访问,指的是在一个时间复杂度内,能够按照条件查找到所需数据的能力。随机访问文件类RandomAccessFile内部封装了一个数组作为文件暂存,并通过内部方法实现了输入流readLine( )、read系列、readInt(int i )系列方法,和输出流writeUTF( String str )系列、write系列方法。
随机访问文件类RandomAccessFile中,我们通过指针来确定具体的信息插入位置。于是有了,随机访问文件类RandomAccessFile能够通过seek(int step)来改变指针位置,也可以通过skipBytes( int n )来跳过n个字节。seek和skipBytes方法的不同在于,seek方法既可以实现向前跳跃也可以实现向后跳跃,它是直接将指针指定到对应字节位置,而skipBytes方法只能实现向后跳跃,是从当前位置开始,向后跳转n个字节。利用这个特点,我们可以将指针定义到我们想要修改的之前位置,来对文件中的内容进行修改。
随机访问文件类RandomAccessFile的构造函数RandomAccessFile(File file, String mode)和RandomAccessFile(String file, String mode)。通过它们,我们可以实现对文件的读写操作。是读是写,最关键的就看mode参数的具体值。mode参数可以取4个值,分别是:
"r"只读,无对应文件则抛出FileNoFoundExecption异常;
"rw"读写,如果没有文件,则尝试创建,存在则不会覆盖,下面相同;
"rws"读写同时,将元数据和内容信息同步至底层存储;
"rwd"读写同时,将内容信息同步至底层存储,因无元数据同步,它的速度比"rws"要快;
元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(dataabout data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。元数据算是一种电子式目录,为了达到编制目录的目的,必须在描述并收藏数据的内容或特色,进而达成协助数据检索的目的。
元数据的存在,主要是为了便于检索。
随机访问文件类RandomAccessFile的存在,使得分段同步多线程存储的实现有了工具,同时使得指定字节大小的分段文件传输有了可能。分段传输前常常需要知道文件大小,然后再根据具体情况进行分段,所以,该类分段多线程存储的使用,常常与该类中的length方法配合。由length得到文件大小,再分段进行传输。随机访问文件类RandomAccessFile是通过字节流实现write,因此不需要flush刷新操作。
需要注意,所有的writeUTF方法写入的数据,编码格式都是修改版的8字节UTF,只能够与之对应的readUTF方法读取,其他方法无法获取信息。
数据操控类
除去以上介绍的各种功能细化的衍生类外,java.io包中还拥有其他的细化类如:可用于操控基本数据类型的数据操控类DataInputStream与DataOutputStream,可用于操纵字节数组的字节数组控制类ByteArrayInputStream和ByteArrayOutputStream,可用于操作字符数组的字符数组控制类CharArrayReader和CharArrayWriter,可用于操作字符串数组的StringReader和StringWriter。
当我们用数据操控类DataInputStream读取数据时,我们必须保证读取数据的基本数据类型,与用DataOutputStream存储时的顺序保持一致,否则将会因为占用字节长度不同而发生乱码。该类存在的意义主要在于他实现了的基本数据的持久性存储。
字节数组控制类ByteArrayInputStream和ByteArrayOutputStream
字符数组控制类CharArrayReader和CharArrayWriter
字符串数组控制类StringReader和StringWriter
这些类中比较特殊的是字节数组控制类。因为字节数组控制类的ByteArrayInputStream和ByteArrayOutputStream没有使用系统资源,因此不需要使用close方法关闭流。所以这种流可以在任何时间使用。这种数组控制类,它们的数据源是实例化时所传的参数;它们的目的是内存中的临时存储区,或是由类封装的writeTo( OutputStreamout )方法指向的out位置。不过,最常用的是第一种目的指向,这种指向相当于将原有的直接定义数组划分内存暂存的方式,封装为了一个IO类来控制的形式。这种方式常用于当需要一个可变长度的IO相关联数组的时候。
我们将数组封装为类不仅是为了更好的程序封装性,更高的代码复用性和功能效率,最主要的是为了使得数组这种特殊的数据类型(较基本数据类型)能够更好的适应于IO环境。便于数据的传输及使用。这就是从流的思想,即数据传输的思想,来考虑数组的问题。通过这种方法,
数组控制类的优点,在于它实现了动态数组大小。
------- android培训、java培训、期待与您交流! ----------