Streams
Stream Classes Overview
输出流的继承类图:
输入流的继承类图:
LineNumberInputStream
和StringBufferInputStream
由于不支持字符编码,已经废弃掉了。
PrintStream
也是不支持字符编码的,但由于大量应用,暂时还没有废弃。
Touring the Stream Classes
OutputStream and InputStream
Java提供了抽象的OutputStream
和InputStream
类来进行I/O操作。
OutputStream
是所有输出流的超类:
Method | Description |
---|---|
void close() | 关闭流,释放资源 |
void flush() | 将缓冲中的数据写入目标位置。由于JVM和具体文件之间还有操作系统这一层,因此,该方法仅刷新到操作系统,不能保证能一定写入具体文件。 |
void writer(byte[] b) | 将字节数组b写入输出流 |
void write(byte[] b, int off, int len) | 将指定长度的字节数写入输出流 |
void write(int b) | 将字节b写入输出流,即将32位整数的低八位写入,其余24位忽略 |
flush()
方法在close()
调用前会自动调用。
InputStream
方法:
Method | Description |
---|---|
int available() | 可以从输入流中读取的字节数 |
void close() | 关闭输入流,释放资源 |
void mark(int readlimit) | 标记输入流中的当前位置,reset() 会重置该标记,readlimit是从此标记开始能读取的最大 |
boolean markSupported() | 是否支持标记 |
int read() | 返回从输入流中读入的先一个字节,阻塞方法 |
int read(byte[] b) | 读入字节数组b,返回实际读入 |
int read(byte[] b,int off ,int len) | 将指定长度的字节数读入字节数组 |
void reset() | 重置标记位置 |
long skip(long n) | 忽略n个字节数,继续读后面的字节 |
ByteArrayOutputStream and ByteArrayInputStream
public class UsingByteArrayStream {
ByteArrayInputStream bis;
ByteArrayOutputStream bos;
@Before
public void setup(){
bis = new ByteArrayInputStream("hello world, this is from jintao in nokia shanghai bell".getBytes());
bos = new ByteArrayOutputStream();
}
@Test
public void byteInMethods() throws Exception {
int firstByte = bis.read();
System.out.println("firstByte is : "+firstByte);
byte[] bb = new byte[3];
bis.read(bb);
System.out.println(new String(bb));
int availBytes= bis.available();
System.out.println("now the available bytes are : "+availBytes);
bis.skip(5);
System.out.println((char)bis.read());
}
@Test
public void byteOutMethods() throws Exception {
bos.write("today is monsday".getBytes());
System.out.println("now the size is "+bos.size());
bos.writeTo(System.out);
}
}
FileOutputStream and FileInputStream
我们分别用输入输出流复制一个文件内容:
public class UsingFileStream {
FileInputStream fis;
FileOutputStream fos;
@Before
public void setup() throws FileNotFoundException {
fis = new FileInputStream(new File("./File.md"));
fos = new FileOutputStream(new File("./out.md"));
}
@After
public void teardown() throws IOException {
fis.close();
fos.close();
}
@Test
public void copeContents() throws Exception {
byte[] buffer = new byte[16];
int read;
while ((read = fis.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
}
}
PipedOutputStream and PipedInputStream
主要用于线程间通信,如果在单线程中使用,会死锁!
examples:
public class UsingPipedStream {
PipedInputStream pis;
PipedOutputStream pos;
@Before
public void setup() throws IOException {
pis = new PipedInputStream();
pos = new PipedOutputStream(pis);
}
@Test
public void commuBetweenThread() throws Exception {
Thread source = new Thread("source") {
@Override
public void run() {
try {
pos.write("hello, this is from thread souce!".getBytes());
pos.close();//写完数据要记得关闭
} catch (Exception e) {
e.printStackTrace();
}
}
};
Thread sink = new Thread("sink") {
public void run() {
byte[] buffer = new byte[4];
int read;
StringBuffer sb = new StringBuffer();
try {
TimeUnit.SECONDS.sleep(1);
while ((read = pis.read(buffer)) != -1) {
sb.append(new String(buffer), 0, read);
}
pis.close();
System.out.println(sb.toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
};
source.start();
source.join();
sink.start();
sink.join();
}
如果写完数据不关闭,会抛出IOException
,异常信息为“write end dead”。
FilterOutputStream and FilterInputStream
Java提供了过滤流的支持,如压缩/解压,加密/解密。
examples:
public class UsingFiterStream {
static class MyFilterInputStream extends FilterInputStream {
private SimpleCipher cipher;
protected MyFilterInputStream(InputStream in) {
super(in);
cipher = new SimpleCipher();
}
public MyFilterInputStream(InputStream in, SimpleCipher cipher) {
this(in);
this.cipher = cipher;
}
@Override
public int read() throws IOException {
int r = super.read();
return this.cipher.decrypt((byte) r);
}
}
static class MyFilterOutputStream extends FilterOutputStream {
private SimpleCipher cipher;
public MyFilterOutputStream(OutputStream out) {
super(out);
cipher = new SimpleCipher();
}
public MyFilterOutputStream(OutputStream out, SimpleCipher cipher) {
super(out);
this.cipher = cipher;
}
@Override
public void write(int b) throws IOException {
int r = cipher.encrypt((byte) b);
super.write(r);
}
}
static class SimpleCipher {
public byte encrypt(byte b) {
return (byte) (b + 1);
}
public byte decrypt(byte b) {
return (byte) (b - 1);
}
}
@Test
public void usingMyFilterStreams() throws Exception {
MyFilterOutputStream fos = new MyFilterOutputStream(new FileOutputStream(new File("./filter.dat")));
fos.write(113);
fos.close();
MyFilterInputStream fis = new MyFilterInputStream(new FileInputStream(new File("./filter.dat")));
FileInputStream fi = new FileInputStream(new File("./filter.dat"));
int r = fis.read();
System.out.println("read by myfilterInputStream : " + (char) r);
int rr = fi.read();
System.out.println("read by fileinputStream: " + (char) rr);
}
}
好吧,例子其实比较垃圾,但是它的大致功能就是这样的,主要是准备写的时候,看了看 javax.crypto
那一块,结果卡住了,过段时间得学些javax.crypto
这个包。其实这里有个错误,就是当read()
方法读到-1的时候,到底是文件结束呢?还是加密后的结果?这里要小心,-1一定得代表文件末尾。
BufferedOutputStream and BufferedInputStream
带有缓冲区的流,提高读写性能。
DataOutputStream and DataInputStream
能直接读写java基本类型的流。
public class UsingDataStream {
@Test
public void rwConcreteTypes() throws Exception {
try (DataInputStream dis = new DataInputStream(new FileInputStream(new File("./data.dat")));
DataOutputStream dos = new DataOutputStream(new FileOutputStream(new File("./data.dat")));) {
dos.writeInt(12344);
dos.writeBoolean(true);
dos.writeDouble(12.2123412);
int int1 = dis.readInt();
boolean b1 = dis.readBoolean();
double d1 = dis.readDouble();
System.out.println("int: " + int1);
System.out.println("boolean : " + b1);
System.out.println("double: " + d1);
}
}
}
注意这里有个顺序的问题,即写入不同类型的顺序和读取的顺序要一致。
Object Serialization and Deserialization
对象序列化最好是看java对象序列化规范。
Default Serialization and Deserialization
首先使用java.io.Serializable
接口来实现最基本的序列化。该接口是一个标记接口(没有具体方法声明)。主要是为了避免不加限制的序列化。
不加限制的序列化是指序列化整个对象图(即和该对象有关的对象都序列化),这样限制的原因是:
- 安全:防止将敏感信息也序列化
- 性能:序列化使用反射API,性能较差
- 没有序列化意义:如某些对象只在运行时有意义。
transient
修饰的字段不会被序列化。
example1:
public class Employee implements Serializable {
private String name;
private int age;
public Employee(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
public class Employee1 implements Serializable {
private String name;
private int age;
private transient String address = "boshan";
public Employee1(String name, int age, String address) {
super();
this.name = name;
this.age = age;
this.address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "Employee1 [name=" + name + ", age=" + age + ", address=" + address + "]";
}
}
public class UsingObjectStream {
FileInputStream fis;
FileOutputStream fos;
ObjectOutputStream oos;
ObjectInputStream ois;
@Before
public void setup() throws Exception {
fis = new FileInputStream(new File("./object.dat"));
fos = new FileOutputStream(new File("./object.dat"));
oos = new ObjectOutputStream(fos);
ois = new ObjectInputStream(fis);
}
@Test
public void the1stSer() throws Exception {
Employee employee = new Employee("jintao", 123);
oos.writeObject(employee);
Employee employee2 = (Employee) ois.readObject();
Assert.assertFalse(employee.equals(employee2));
}
@Test
public void the2rdSer() throws Exception {
Employee1 employee1 = new Employee1("jintao", 123, "zibo");
oos.writeObject(employee1);
Employee1 employee12 = (Employee1) ois.readObject();
System.out.println(employee12);
}
}
没有保证当一个对象被反序列化后,与它对应的那个类就是它被序列化时的那个类。在反序列化阶段,当检测到反序列化对象和它的类不一致时,会抛出java.io.InvalidClassException
。
每一个序列化对象有一个标识符。在反序列化时,反序列化机制或检查该标识符与序列化它的那个类的标识符是否一致,同样有可能会抛出上述异常。
有时,你给某个类添加了新的字段,但是当你反序列化它之前的一个对象时会抛出异常,要解决这个问题,可以给类添加serialVersionUID
,只要在序列化后,类的serialVersionUID
不变,就可以反序列化,哪怕你把原来的类字段都删除。
无论何时你修改了某个类文件,一定记得创建一个新的SUID。
Custom Serialzation and Deserialization
这一节,我们来自定义序列化,反序列化
有时候我们需要自定义序列化反序列化操作,比如你想要序列化的类并没有实现Serializable
接口,为了达到目的,你可以继承该类,让子类实现Serializable
接口,然后让子类构造器调用超类构造器。尽管这样可以让你序列化子对象,但是如果超类没有无参构造器,你就不能反序列化它。
看这个例子:
public class SerializationDemo {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./employee.dat")));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./employee.dat")));) {
SerEmployee se = new SerEmployee("jintao");
System.out.println(se);
oos.writeObject(se);
SerEmployee see = (SerEmployee) ois.readObject();
System.out.println(see);
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Employee3 {
private String name;
public Employee3(String name) {
super();
this.name = name;
}
@Override
public String toString() {
return "Employee [name=" + name + "]";
}
}
class SerEmployee extends Employee3 implements Serializable {
public SerEmployee(String name) {
super(name);
}
}
//output:
Employee [name=jintao]
java.io.InvalidClassException: com.jintao.java.io.serializition.SerEmployee; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:150)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:768)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1775)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
at com.jintao.java.io.serializition.SerializationDemo.main(SerializationDemo.java:17)
看输出异常可知,是因为没有有效的构造器,这是因为超类Employee3
没有无参构造器。
我们可以通过适配器设计模式,然后提供private void writeObject(ObjectOutputStream oos)
和private void readObject(ObjectInputStream ois)
,当我们通过对象流读写对象的时候,会优先调用我们提供的读写方法。
public class SerializationDemo1 {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./employee1.dat")));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./employee1.dat")));) {
SerEmployee1 serEmployee1 = new SerEmployee1("jintao");
oos.writeObject(serEmployee1);
System.out.println(serEmployee1);
SerEmployee1 se1 = (SerEmployee1) ois.readObject();
System.out.println(se1);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class Employee4 {
private String name;
public Employee4(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
class SerEmployee1 implements Serializable {
private Employee4 employee4;
private String name;
public SerEmployee1(String name) {
this.name = name;
employee4 = new Employee4(name);
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.writeUTF(name);
}
private void readObject(ObjectInputStream ois) throws IOException {
name = ois.readUTF();
employee4 = new Employee4(name);
}
@Override
public String toString() {
return name;
}
}
我们提供的writeObject()
和readObject()
方法可以用于序列化/反序列化对象正常状态之外的数据,比如我们可以通过该途径序列化一个静态字段。然而,在序列化/反序列化额外数据时,你需要告诉序列化/反序列化系统,让它将对象的正常状态给序列化掉。对象流提供类两个默认读写对象的方法:
Method | Description |
---|---|
defaultWriteObject() | 写出对象正常状态,你自己的writeObejct()方法会首先调用该方法,然后在写出额外数据。 |
defaultReadObject() | 原理同上。 |
Externalization
java还提供了一个更强的接口——Externalization
,外部化接口,让你完全自定义序列化/反序列化任务。
外部化改进了基于反射的序列化/反序列化的性能,而且给你完全的控制。完整接口名为java.io.Externalizable
。该接口具有如下方法:
Method | Description |
---|---|
void writeExternal(ObjectOutput out) | 写出对象内容 |
void readExternal(ObjectInput in) | 读取对象内容 |
一个对象要序列化时,考虑到反序列化,需要提供无参构造函数。
package com.jintao.java.io.serializition;
import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
public class Employee5 implements Externalizable {
private String name;
private int age;
public Employee5() {
System.out.println("Employee5() called");
}
public Employee5(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
System.out.println("writeExternal() called");
out.writeUTF(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
System.out.println("readExternal() called");
name = in.readUTF();
age = in.readInt();
}
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./externalize.dat")));
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(new File("./externalize.dat")));) {
Employee5 employee5 = new Employee5("jintao", 23423);
System.out.println(employee5);
oos.writeObject(employee5);
System.out.println("===================");
Employee5 employee52 = (Employee5) ois.readObject();
System.out.println(employee52);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在序列化一个对象前,序列化系统会检查该对象的对应类是否实现Externalizable
接口,如果实现了,则调用writeExternal()
方法,否则,它会寻找private writeObject(ObjectOutputStream oos)
方法并调用,若没有提供,就执行默认的序列化机制,当然该机制仅序列化non-transient
的实例字段。
而在反序列化一个对象时,首先检查该对象对应类是否实现Externalizable
接口,如果实现了,就查找它的无参公共构造器,然后成功就调用readExternal()
方法,若没有实现该接口,就查找private readObject(ObjectInputStream ooi)
方法。若没有该方法,就执行默认的反序列化机制,仅包含non-transient
的实例字段。
PrintStream
按照之前的命名惯例,它应该叫PrintOutputStream
。它主要就是将字符串输出到输出流。这个就不写了,比较常用。注意:PrintStream
不会抛出IOException
异常。