标签:
在Java中,可以提供字节序列的对象,或者可以接收字节序列的对象,都可以抽象成流。系统中的文件,网络,内存这些设备都可以读入或者写入字节,自然也可以用流的方式来操作。能向程序中提供字节序列,即可以从其中读入字节序列,这样的对象显然就是输入流。相反的,能够接收程序送来的字节序列,也就是可以向其中写入字节序列,就是输出流。
Java提供丰富的流类家族,实现了各种常用的输入输出操作功能,如文件、内存读写等。InputStream和OutputStream类分别是字节输入/输出流继承体系的基类。
字节流都是以字节为单位读写的,而Java中字符采用的是Unicode形式,一个Unicode元码是两个字节,不同字符的字节数目并不一致。有时候我们不关心字节与字符的转化,希望读写都按字符为单位进行,就可以使用字符流类家族,Reader和Writer是字符输入/输出流的基类。
InputStream
OutputStream
Reader和Writer的大部分方法与InputStream和OutputStream类似,只不过参数类型从byte变成了char。
Reader
Writer
各个子类中提供了更方便实用的方法,尽可能使用子类提供的那些方法。
InputStream
OutputStream
Reader
Writer
接口 AutoCloseable 声明void close()方法。 try-with-resource结构自动调用close()方法,可以抛出任何异常
接口 Flushable 声明void flush()方法
接口 Readable 声明int read(CharBuffer cb)方法
接口 Appendable 声明Appendable append(xxx)方法
接口 DataInput 声明了xxx readxxx()方法
接口 DataOutput 声明了void writexxx(xxxx)方法
DataOutput分别提供了写出字符串的方法。
接口 CharSequence 声明了char序列的基本方法length,charAt...
java.nio.CharBuffer 抽象类,通过warp方法包装码元数组实例化对象。字符码元缓冲区,支持顺序或随机读写,表示内存缓冲区或内存映象文件
java.nio.ByteBuffer 抽象类,通过allocate或warp方法实例化对象
java.lang.StringBuffer
java.lang.StringBuilder
java.lang.String
java.util.Scanner
基本用法是使用构造器或其他方法获得一个流对象。然后使用流对象的方法读入或者写出字节或字符。
Java提供了很多过滤器流,过滤器流可以灵活的包装其他的流对象,为其提供额外的功能。流可以无限制的嵌套包装,以达到预期的效果。
文件字节流写出+按类型写出
OutputStream is=new FileOutputStream("test.txt", true); DataOutputStream dis=new DataOutputStream(is); dis.writeDouble(3.14); dis.close();
文件字节流读入+转化为字符流+缓冲并按行读入字符串
InputStreamReader ir=new InputStreamReader(new FileInputStream("test.txt"), "UTF-8"); BufferedReader br=new BufferedReader(ir); br.readLine(); br.close();
二进制数据文件 数据文件是将各种数据在内存中的字节序列直接存储到文件中,效率很高。直接使用FileInputStream,FileOutputStream读写字节序列。使用DataInputStream和DataOutputStream可以更方便的读写基本类型数据。
字符文件 但是很多时候为方便人阅读,通常会将内存中Unicode字符按某种本地字符集(GBK,UTF-8等)编码,将编码后得到字节序列存储到文件中(可以按字符显示)。读入时还需要将字节序列解码为Unicode字符。
(1)字符流与字节流转化(字符编码/解码)
使用InputStreamReader(解码)和OutputStreamWriter(编码)可以自动将输入字节流解码成输入字符流,或者是把输出字符流编码成输出字节流。按某种编码得到的输出字节流逐字节写入的文件就是该编码格式的字符文件。
OutputStreamWriter oswriter=new OutputStreamWriter(new FileOutputStream("test.txt"),"UTF-8"); oswriter.write("asdfdf"); oswriter.close();
(2)字符文本输出
PrintWriter类似于System.out,提供了一组print方法,print方法不会抛出异常,需要调用checkError方法检查流是否出现异常。
PrintWriter pw=new PrintWriter(new FileWriter("test.txt"), true); //第2个参数表示是否自动冲刷缓冲区,默认为false。 PrintWriter pw1=new PrintWriter("test.txt"); //可以直接指定输出的文件,而不用创建文件流
PrintStream类也支持print系列方法,即可以输出字节也可以输出字符 ,是System.out,System.err所属类型。
(3)字符文本读入
BufferedReader可以按行读取。
Scanner是一个工具类,类似于DataInputStream,提供了按行按类型等读入数据的方法,Scanner可以使用Readable,InputStream,File,String等多种对象构造(内部都会转化为Readable对象)。
Scanner sc=new Scanner(new FileInputStream("test.txt")); sc.nextLine(); sc.nextDouble();
随机访问文件读写(二进制数据文件)
RandomAccessFile类实现了DataInput和DataOutput接口,即可以读入也可以写出,也可以在指定位置读入或写出字节数据。如果存储的数据格式大小一致,可以精确将文件指针移动到指定数据记录位置随机读写,效率非常高。
RandomAccessFile
RandomAccessFile in=new RandomAccessFile("test.txt","rw"); //r:只读, "rw":可读可写,"rws","rwd" in.seek(1000); in.writeDouble(3.14);
in.seek(2000);
double d=in.readDouble();
in.close();
其他相关的问题
换行符: System.getProperty("line.separator"); windows默认为"\r\n", UNIX默认为"\n"
相对路径解析:相对路径都是基于 System.getProperty("user.dir"),默认为程序的启动路径。
路径内部分隔符:java.io.File.separator, windows为"\\"(转义)或"/", Unix为"/"
路径间分隔符(如Path环境变量):java.io.File.pathSepparator, windows为";", Unix为":"
Java中,所有值都是高位在前的方式写出,java数据文件与平台无关。
字符集
字符集建立了Java内部两个字节Unicode码元序列与本地字符编码的字节序列之间的映射。使用字符集可以在本地字符编码和Unicode编码之间进行转化。每个字符集有一个在IANA(字符集注册中心)注册的官方名字,还可能有若干个别名。Java中java.nio.Charset类实现了字符集相关的功能。
Charset
Zip文档通常以压缩格式存储一个或多个文件。每个Zip文档有一个头好汉了每个文件名和使用的压缩算法等信息。Zip文件可以用ZipInputStream和ZipOutputStream类读写,Zip文档中的每个文件用ZipEntry描述。
Jar文档是一个带有清单项的Zip文件,使用JarInputStream和JarOutputStream可以读写Jar文档的清单项。
ZipInputStream
ZipOutputStream
ZipEntry
ZipInputStream zis=new ZipInputStream(new BufferedInputStream(new FileInputStream("H:\\zipTest\\zipTest.zip")));
ZipEntry entry=null;
while((entry=zis.getNextEntry())!=null)
{
System.out.println((entry.isDirectory()?"dir":"file")+": "+entry.getName()+" "+entry.getCompressedSize()+"/"+entry.getSize());
ByteArrayOutputStream baos=new ByteArrayOutputStream();
byte[] buf=new byte[1024];
int readSize=-1;
while((readSize=zis.read(buf))>=0)
baos.write(buf, 0, readSize);
System.out.println(baos.toString("GBK"));
zis.closeEntry();
}
ZipOutputStream zos=new ZipOutputStream(new BufferedOutputStream(new FileOutputStream("zipTest.zip")));
Date now=new Date();
ZipEntry entry=new ZipEntry("time/"+now.getTime()+".txt");
zos.putNextEntry(entry);
zos.write(Charset.defaultCharset().encode("current time: "+now.toString()).array());
zos.closeEntry();
zos.close();
Zip文件操作也可以使用ZipFile类替代ZipInputStream类。
ZipFile
ZipFile zfile=new ZipFile("zipTest.zip");
Enumeration<ZipEntry> enumers=(Enumeration<ZipEntry>) zfile.entries();
while(enumers.hasMoreElements())
{
ZipEntry entry=enumers.nextElement();
System.out.println((entry.isDirectory()?"dir":"file")+": "+entry.getName()+" "+entry.getMethod()+" "+entry.getCompressedSize()+"/"+entry.getSize());
InputStream is=zfile.getInputStream(entry);
ByteArrayOutputStream baos=new ByteArrayOutputStream();
byte[] buf=new byte[1024];
int readSize=-1;
while((readSize=is.read(buf))>=0)
baos.write(buf, 0, readSize);
System.out.println(baos.toString("GBK"));
is.close();
}
zfile.close();
有时我们需要将内存中的数据存储到文件系统中或者是变成数据流在网络间传输。如果是相同类型的数据,可以转化为固定长度的记录,读写还算方便。但是绝大多数情况下使用的数据都涉及各种不同的类型,不可能用固定长度来表示。
Java提供了一种通用的对象序列化机制,可以将任何对象序列化并写入到流中,或者是从流中读入序列化数据还原成对象。使用ObjectInputStream和ObjectOutputStream类可以将任何实现了Serializable接口(标记接口,无方法)的类对象序列化到流中或从流中反序列化。这两个类会自动浏览对象的所有域,并存储其中的内容。序列化的每个对象都有一个唯一的序列号作为标识,通过引用已有的序列号,一个对象被多个对象引用的关系也能完整保留和恢复。
序列化会存储对象的类,类指纹,该类及其超类中的所有非静态和非瞬时域的值。瞬时域是使用transient关键字标示的域,这种域在序列化时会被跳过,一个域属于不可序列化的类或者反序列化后会失效,则应该标为瞬时域。
序列化处理过程比较复杂,效率比较低。
ObjectOutputStream
ObjectInputStream
class TestClass implements Serializable { public TestClass(String name){this.name=name;} private String name; public String getName(){return name;} }; ByteArrayOutputStream bos=new ByteArrayOutputStream(); ObjectOutputStream oos=new ObjectOutputStream(bos); oos.writeObject(new TestClass("aaa")); ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); TestClass tc=(TestClass) ois.readObject(); System.out.print(tc.getName());
1.使用transient关键字禁止一些域被序列化
2.覆盖private void readObject(ObjectInputStream in)和private void writeObject(ObjectOutputStream out)方法,数据域部分的序列化和反序列化过程就可以在这两个方法中自定义。在这两个方法中还可以分别调用in.defaultReadObject()和out.defualtWriteObject()方法来完成默认的序列化过程,然后进行额外的操作(如将不可序列化的域用别的方式保存关键数据和恢复)。
3.通过实现Externalizable接口(可外部化),类可以定义自己的序列化机制,其中定义了public void readExternal(ObjectInputStream in)和public void writeExternal(ObjectOutputStream out)方法。这两个方法对包括超类数据在内的整个对象的存储和恢复负全责,序列化机制仅仅在流中记录该对象所属的类,然后调用writeExternal方法。在读入时,对象流用无参构造器(无论类中是否提供)创建一个对象,然后调用readExternal方法。
enum结果序列化不会产生问题。但是在enum为推出之前,枚举的就是将类的构造器设为私有,然后仅在类的内部创建几个唯一的对象,其他地方都只能引用类里固定的内部对象,不可能再创建新的对象,单例模式也是这个原理,使用==可以判断某个变量引用的是否就是类里固定的对象。但是反序列化时,可以无视类中的构造器,直接创建出新的对象类,使用==比较就会出现问题,例如下面的例子。单例模式也是类似的,单例类反序列化后会出现同时存在多个实例的情况。
class State implements Serializable { public static final State GOOD=new State(1); public static final State BAD=new State(2); public static final State NORAML=new State(3); private State(int value){this.value=value;} private int value; }; State good=State.GOOD; ByteArrayOutputStream bos=new ByteArrayOutputStream(); ObjectOutputStream oos=new ObjectOutputStream(bos); oos.writeObject(good); ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())); State nstate= (State) ois.readObject(); assertTrue(good==nstate);
解决方法就是在这种类中实现protected Object readResolve()方法。单例模式只需要在该方法中返回内部实例即可。
class State implements Serializable { ...... protected Object readResolve() { switch(value) { case 1:return GOOD; case 2:return BAD; case 3:return NORMAL; } return null; } }
对象流中保存了类的指纹信息,指纹信息是对类、超类、接口、域类型、方法签名安装规范方式排序的SHA码的前8位。读入对象时,如果流中的类指纹与实际类计算出来的指纹不一致,序列化机制认为类型不兼容,会拒绝加载对象。但是一个类会不断演进,如果希望改进后的类仍然可以读取早起版本的序列化数据,就必须要在类中加入一个public static final long serialVersionUID=xxxxxxL 的域,以标明它支持早起版本的指纹xxxxxx。
计算一个类的指纹可以使用%JAVA_HOME%\bin目录下的serialver工具。 命令行: serial class_name。 GUI: serial -show
只要在类中加入这个静态域,并且该域值和流中的类指纹是一致的,序列化系统就认为他们是兼容的,如果类的实际结构有差别,序列化系统会尝试进行转换。转换时,如果同名域的类型不同,则转换会因为不兼容而失败;如果流中有多余的域,则忽略掉;如果流中没有某个域的值,该域就为默认值(null,0,false)。有些类会将每个域都初始化为特定的值,反序列化的得到的对象域中的默认值可能会导致问题,需要在类的readObject方法中进行修正,或者是在处理null数据时足够健壮。
序列化用于克隆
序列化然后反序列化对象,可以得到对象的深拷贝。但是序列化的效率较低,会比创建新对象然后复制数据域要慢的多。
标签:
原文地址:http://www.cnblogs.com/pixy/p/4779820.html