Java I/O,java
Java I/O,java
Java的I/O功能通过java.io包下的类和接口来支持,在java.io包下主要包括输入/输出两种IO流,每种输入/输出流又可分为字节流和字符流两大类.字节流支持以字节(8位)为单位的IO操作,而字符流则以字符(16位-Java中)为单位进行IO操作.
除此之外,Java的IO流还使用装饰者模式,将IO流分成底层节点流和上层处理流,节点流直接和底层的物理存储节点关联,虽然不同的物理节点获取的节点流可能存在差异,但程序可以把不同的物理节点包装成统一的处理流,从而允许程序使用统一的IO代码来操作不同的物理节点.
File
Java使用java.io.File来提供对底层文件的抽象, File能够新建/删除/重命名文件和目录,但不能访问文件内容本身(需要使用IO流).
File类提供了很多实用的方法,我们可以将其分为以下几类:
- 文件名相关方法
getAbsoluteFile() getAbsolutePath() getName() getParent() getParentFile() getPath() renameTo(File dest) - 文件状态相关方法
exists() canExecute() canRead() canWrite() isFile() isDirectory() isAbsolute()(UNIX/Linux中是否以/开头) isHidden() lastModified()length() - 文件操作
createNewFile() createTempFile(String prefix, String suffix) delete() deleteOnExit() setExecutable(boolean executable) setReadOnly() - 目录操作
如: mkdir() mkdirs() list() list(FilenameFilter filter) listFiles() listFiles(FileFilter filter) listRoots()
这儿只是大致介绍File类提供的功能,细节请参考JDK文档.
/** * 模拟tree命令 * * @author jifang * @since 16/1/6下午5:20. */ public class Tree { public static void main(String[] args) { // 打印当前目录 if (args == null || args.length == 0) { displayFiles(new File("."), 0); } else { displayFiles(new File(args[0]), 0); } } private static void displayFiles(File directory, int depth) { File[] files = directory.listFiles(); if (files != null) { for (File file : files) { for (int i = 0; i < depth - 1; ++i) { System.out.print("| "); } if (depth != 0) { System.out.print("|-- "); } System.out.println(file.getName()); if (file.isDirectory()) { displayFiles(file, depth + 1); } } } } }
I/O流体系
InputStream / Reader
InputStream与Reader是所有输入流的抽象基类, 虽然本身不能创建实例来执行输入, 但作为所有输入流的模板, 他们的方法是所有输入流都通用的.
InputStream包含如下三个方法:
- int read()
- int read(byte[] b)
- int read(byte[] b, int off, int len)
Reader包含如下三个方法:
- int read()
- int read(char[] cbuf)
- int read(char[] cbuf, int off, int len)
对比InputStream和Reader所提供的方法, 这两个类的功能基本相同, 只是一个提供了字节byte读, 一个提供了字符char读.
除此之外, InputStream和Reader还支持几个方法来移动记录指针以实现跳读, 重复读等操作.
方法 |
释义 |
void mark(int readlimit) |
Marks the current position in this input stream. |
boolean markSupported() |
Tests if this input stream supports the mark and reset methods. |
void reset() |
Repositions this stream to the position at the time the mark method was last called on this input stream. |
long skip(long n) |
Skips over and discards n bytes of data from this input stream. |
OutputStream / Writer
两个流都提供了如下三个方法:
- void write(byte[]/char[] b)
- void write(byte[]/char[] b, int off, int len)
- void write(int b)
因为字符流直接以字符作为操作单位, 所以Writer可以用字符串来代替字符数组(以String对象作为参数). Writer还包含如下方法:
- void write(String str)
- void write(String str, int off, int len)
- Writer append(char c)
- Writer append(CharSequence csq)
- Writer append(CharSequence csq, int start, int end)
/** * 模拟copy功能 * * @author jifang * @since 16/1/6下午7:52. */ public class Copy { private static final int BUFFER_LENGTH = 1024; public static void main(String[] args) throws IOException { if (args == null || args.length != 2) { throw new RuntimeException("Use like: copy <src-file> <dest-file>"); } Reader reader = new FileReader(args[0]); Writer writer = new FileWriter(args[1]); char[] buffer = new char[BUFFER_LENGTH]; int count; while ((count = reader.read(buffer)) != -1) { writer.write(buffer, 0, count); } reader.close(); writer.close(); } }
小结:
- 使用Java IO流执行完IO后,不能忘记关闭流,关闭流才可以保证物理资源会被回收(关闭输出流还可以保证将缓冲区中的数据flush到物理节点中,因为在执行close()方法之前,会自动执行输出流的flush()方法).
- Java 1.7改写了所有的IO资源类,他们都实现了AutoCloseable接口,因此都可通过自动关闭的try语句来自动关闭这些流.
- 通常来说,字节流的功能比字符流的功能强大,因为计算机里所有的数据都是二进制的,因此字节流可以处理所有所有的二进制文件.
- 如果使用字节流来处理文本文件,则需要将这些字节转换成字符,增加了编程的复杂度.所以有一个规则:如果进行IO是文本内容,则应该考虑使用字符流;如果进行IO的是二进制内容, 则应该考虑使用字节流.
节点流/处理流
节点流的的构造参数是物理IO节点,而处理流的构造参数是已经存在的流.
常用节点流:
\ |
InputStream |
OutputStream |
Reader |
Writer |
文件 |
FileInputStream |
FileOutputStrean |
FileReader |
FileWriter |
数组 |
ByteArrayInputStream |
ByteArrayOutputStream |
CharArrayReader |
CharArrayWriter |
字符串 |
* |
* |
StringReader |
StringWriter |
管道 |
PipedInputStream |
PipedOutputStream |
PipedReader |
PipedWriter |
常用处理流:
\ |
InputStream |
OutputStream |
Reader |
Writer |
缓冲流 |
BufferedInputStrean |
BufferedOutputStream |
BufferedReader |
BufferedWriter |
转换流 |
InputStreamReader |
OutputStreamWriter |
* |
* |
数据流 |
DataInputStream |
DataOutputStream |
* |
* |
对象流 |
ObjectInputStream |
ObjectOutStream |
* |
* |
合并流 |
SequenceInputStream |
* |
* |
* |
回退流 |
PushbackInputStream |
* |
PushbackReader |
* |
打印流 |
PrintStream |
* |
PrintWriter |
* |
- 处理流与节点流相比可以隐藏底层节点流的差异,对外提供更加统一的IO方法,让开发人员只需关心高级流的操作(使用更加简单, 执行效率更高).
- 使用处理流的思路:使用处理流来包装节点流,程序通过处理流执行IO,让节点流与底层IO设备/文件交互.
在使用处理流包装了节点流之后, 关闭输入/输出流资源时, 只要关闭最上层的处理流即可.关闭最上层的处理流时, 系统会自动关闭该处理流包装的节点流.
数组作为IO节点
上表中列出了一种以数组为物理节点的节点流,字节流以字节数组为节点ByteArrayInputStream/ByteArrayOutputStream,字符流以字符数组为节点CharArrayReader/CharArrayWriter, 这种数组流除了在创建节点流需要传入数组外,用法上与其他节点流完全类似.数组流类似还可以使用字符串作为物理节点,用于实现从字符串中读取字符串(String)java.io.StringReader, 或将内容写入字符串(StringBuffer)java.io.StringWriter.
/** * @author jifang * @since 16/1/7上午9:48. */ public class StringReaderWriter { @Test public void testStringReaderWriter() throws IOException { String string = "锦瑟无端五十弦,一弦一柱思华年。" + "庄生晓梦迷蝴蝶,望帝春心托杜鹃。" + "沧海月明珠有泪,蓝田日暖玉生烟。" + "此情可待成追忆?只是当时已惘然。"; try (StringReader reader = new StringReader(string)) { char[] buffer = new char[32]; int count; while ((count = reader.read(buffer)) != -1) { System.out.println(new String(buffer, 0, count)); } } System.out.println("************"); try (StringWriter writer = new StringWriter()) { writer.write("昨夜星辰昨夜风,"); writer.append("画楼西畔桂堂东。"); writer.append("身无彩凤双飞翼,"); writer.append("心有灵犀一点通。"); System.out.println(writer.toString()); } } }
缓冲流
BufferedInputStream BufferedOutputStream BufferedReader
BufferedWriter 四个缓冲流增加了缓冲功能, 可以提高IO效率, 但是需要使用flush()才可以将缓冲区的内容写入实际的物理节点.
/** * @author jifang * @since 16/1/7上午9:48. */ public class BufferWriterTest { @Test public void testBufferedWriter() throws IOException { try (BufferedWriter writer = new BufferedWriter(new FileWriter("save.txt"))) { writer.write("昨夜星辰昨夜风,"); writer.write("画楼西畔桂堂东。"); writer.write("身无彩凤双飞翼,"); writer.write("心有灵犀一点通。"); writer.write("********"); writer.flush(); } } }
转换流
Java I/O流体系提供了两个转换流, 用于实现将字节流转换成字符流:
- java.io.InputStreamReader将字节输入流转换成字符输入流
- java.io.OutputStreamWriter将字节输出流转换成字符输出流
回退流
在IO流体系中, 有两个特殊的流与众不同: PushbackInputStream PushbackReader 他们都提供了如下三个方法:
方法 |
释义 |
void unread(byte[]/char[] buf) |
Pushes back an array of bytes/characters by copying it to the front of the pushback buffer. |
void unread(byte[]/char[] buf, int off, int len) |
Pushes back a portion of an array of bytes/characters by copying it to the front of the pushback buffer. |
void unread(int b/c) |
Pushes back a single byte/character by copying it to the front of the pushback buffer. |
这两个推回输入流都带有一个推回缓冲区, 当程序调用者两个的unread()方法时,系统将会把指定的数据推回到该缓冲区中, 而这两个流在每次调用read()方法时,都会先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后, 才会到原输入流中获取.
这样, 当程序创建该流时, 需要指定推回缓冲区大小(默认为1),如果在程序中推回的数据超出了推回缓冲区的大小, 则会抛出java.io.IOException: Pushback buffer overflow.
RandomAccessFile
java.io.RandomAccessFile与普通的Java I/O流不同的是, 他可以支持随机访问文件, 程序可以直接跳转到文件的任意地方读写数据(因此如果只是访问文件的部分内容,或向已存在的文件追加数据, RandomAccessFile是更好的选择).但RandomAccessFile也有一个局限就是只能读写文件, 不能读写其他IO节点.
RandomAccessFile对象包含了一个记录指针, 用以标识当前读写位置, 当程序新建一个RandomAccessFile对象时, 记录指针位于文件头, 当读/写n个字节后, 指针将会向后移动n个字节.除此之外RandomAccessFile还可以(前/后)自由移动该记录指针,RandomAccessFile提供了如下方法来操作文件记录指针:
方法 |
释义 |
long getFilePointer() |
Returns the current offset in this file. |
void seek(long pos) |
Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs. |
RandomAccessFile既可以读文件, 也可以写文件, 所以他提供了完全类似于InputStream的read()方法, 也提供了完全类似于OutputStream的write()方法, 此外他还包含了一系列的readXxx() writeXxx()来完成IO(其实这几个操作是DataInput DataOutput接口来提供的, DataInputStream/DataOutputStreamObjectInputStream/ObjectOutputStream也实现了这两个接口).
在构造一个RandomAccessFile时, 都需要提供一个String mode参数, 用于指定RandomAccessFile的访问模式, 该参数有如下取值:
mode |
模式 |
“r” |
只读 |
“rw” |
读写, 如果文件不存在, 则创建 |
“rws” |
读写,相对于”rw”,还要求对文件内容或文件元数据的更新都同步写入底层存储数据 |
“rwd” |
读写,相对于”rw”,只要求对文件内容的更新同步写入底层存储数据 |
注意: RandomAccessFile不能直接向文件的指定位置插入数据,不然新插入的数据会覆盖文件的原内容.如果需要向指定的位置插入数据,程序需要先把指定插入点后的内容读入缓冲区, 等把需要插入的数据写入完成后, 再将缓冲区的内容追加到文件后面.
public void writePositionContent(RandomAccessFile file, long position, byte[] content) { try { // 首先将position到文件末尾的内容写入数组 file.seek(position); ByteArrayOutputStream tmpStream = new ByteArrayOutputStream(); byte[] tmpArray = new byte[1024]; int readCount; while ((readCount = file.read(tmpArray)) != -1) { tmpStream.write(tmpArray, 0, readCount); } // 然后再回到position, 向其中写入内容 file.seek(position); file.write(content); // 最后将暂存的内容写入文件 file.write(tmpStream.toByteArray()); tmpStream.close(); } catch (IOException e) { throw new RuntimeException(e); } }
对象序列化
序列化机制使得对象可以脱离程序的运行环境而独立存在: 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流, 从而可以把这种二进制流持久的保存在磁盘上, 或通过网络将这种二进制流传输到另一个网络节点. 其他程序一旦获取到了这个二进制流, 都可以将他恢复成原先的Java对象.
如果需要让某个对象支持序列化机制, 则必须让他的类是可序列化的(serializable). 该类必须实现如下接口之一:
- java.io.Serializable
- java.io.Externalizable
注意: 对象的类名, 实例变量(基本类型/数组/引用对象)都会被序列化; 而方法/类变量(static)/transient实例变量都不会序列化.
Serializable
使用Serialiable实现对象序列化只需实现这个接口即可(而这个接口只是一个Tag接口).
/** * @author jifang * @since 16/1/13下午7:50. */ public class Bean implements Serializable { private Boolean isUsed; private Double rate; private String name; public Bean(Boolean isUsed, Double rate, String name) { this.isUsed = isUsed; this.rate = rate; this.name = name; } public Boolean getIsUsed() { return isUsed; } public void setIsUsed(Boolean isUsed) { this.isUsed = isUsed; } public Double getRate() { return rate; } public void setRate(Double rate) { this.rate = rate; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Bean{" + "isUsed=" + isUsed + ", rate=" + rate + ", name='" + name + '\'' + '}'; } }
/** * @author jifang * @since 16/1/13下午7:48. */ public class Serialization { @Test public void writeObject() throws IOException { try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("save.txt"))) { Bean bean = new Bean(true, 3.14, "fq"); output.writeObject(bean); } } @Test public void readObject() throws IOException, ClassNotFoundException { try (ObjectInputStream input = new ObjectInputStream(new FileInputStream("save.txt"))) { Bean bean = (Bean) input.readObject(); System.out.println(bean); } } }
注意:
- 反序列化读取的仅仅是Java对象的数据, 而不是Java类, 因此采用反序列化恢复Java对象时,必须提供对象所属的类的class文件, 否则将会引发ClassNotFoundException.
- 从上例看到: Bean类并没有提供无参构造器, 因此可以证明反序列化机制无须通过构造器来初始化对象.
- 如果使用序列化机制向文件中写入了多个Java对象, 反序列化时必须按实际写入的顺序读取.
根据经验: 像Date BigInteger这样的值类应该实现Serializable接口,大多数的集合也应该如此. 但代表活动实体的类, 如线程池(Thread Pool), 一般不应该实现Serializable.
对象引用序列化
- 如果某个类的成员变量不是基本类型或String类型,而是另一个引用类型, 那么这个引用类必须是可序列化的, 否则拥有该类型成员变量的类也是不可序列化的.
- Java序列化算法
- 所有保存到磁盘(或传输到网络中)的对象都有一个序列化编号.
- 当程序试图序列化一个对象时, 程序将先检查该对象是否已经被序列化过, 只有该对象(在本次虚拟机的上下文Context中)从未被序列化过, 系统才会将该对象转换成字节序列并输出.
- 如果某个对象已经序列化过(即使该对象的实例变量后来发生了改变), 程序将不再重新序列化该对象.
/** * @author jifang * @since 16/1/13下午7:48. */ public class Serialization { @Test public void testSerialization() throws IOException, ClassNotFoundException { try (ObjectInputStream input = new ObjectInputStream(new FileInputStream("save.txt")); ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("save.txt"))) { Bean bean = new Bean(true, 3.14, "fq"); output.writeObject(bean); bean.setName("feiqing"); output.writeObject(bean); // 可以看到两个对象是完全一样的 Bean readBean1 = (Bean) input.readObject(); Bean readBean2 = (Bean) input.readObject(); System.out.println(readBean1 == readBean2); System.out.println(readBean1); System.out.println(readBean2); } } @Test public void writeObject() throws IOException { try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("save.txt"))) { Bean bean = new Bean(true, 3.14, "bean"); ComplexBean complexBean = new ComplexBean(); complexBean.setName("complex_bean"); complexBean.setRefBean(bean); output.writeObject(bean); // 在这里对complexBean中的refBean成员做了修改 complexBean.getRefBean().setName("simple_bean"); output.writeObject(complexBean); } } @Test public void readObject() throws IOException, ClassNotFoundException { try (ObjectInputStream input = new ObjectInputStream(new FileInputStream("save.txt"))) { // 可以发现complex_bean内的refBean属性并未改变 Bean bean = (Bean) input.readObject(); ComplexBean complexBean = (ComplexBean) input.readObject(); System.out.println("bean : " + bean + "\n" + "complexBean: " + complexBean); System.out.println(bean == complexBean.getRefBean()); } } }
/** * @author jifang * @since 15/12/31下午4:04. */ public class ComplexBean implements Serializable { private String name; private Bean refBean; public ComplexBean() { } public String getName() { return name; } public void setName(String name) { this.name = name; } public Bean getRefBean() { return refBean; } public void setRefBean(Bean refBean) { this.refBean = refBean; } @Override public String toString() { return "ComplexBean{" + "name='" + name + '\'' + ", refBean=" + refBean + '}'; } }
序列化版本
Java序列化机制允许为序列化的类提供一个private static final long serialVersionUID = xxxL;值, 该值用于标识该Java类的序列化版本; 一个类升级之后, 只要他的serialVersionUID值不变, 序列化机制也会把它们当成同一个序列化版本(由于提供了serialVersionUID之后JVM不需要再次计算该值,因此还有个小小的性能好处).
可以通过JDK提供的serialver工具来提取类的serialVersionUID值.
serialver com.fq.domain.Bean #生成serialVersionUID值
serialver -show #启动serialVersionUID的图形生成界面
如果不显式定义serialVersionUID的值,可能会造成以下问题:
- 该值将由JVM根据类的相关信息计算,而修改后的类的计算结果与修改前的类的计算结果往往不同,从而造成对象的反序列化因为类的版本不兼容而失败.
- 不利于程序在不同JVM之间移植, 因为不同的编译器对该变量的计算策略可能不同, 而从造成类虽然没有改变, 但因为JVM的不同, 也会出现序列化版本不兼容而导致无法正确反序列化的现象.
自定义序列化
在一些的场景下, 如果一个类里面包含的某些变量不希望对其序列化(或某个实例变量是不可序列化的,因此不希望对该实例变量进行递归序列化,以避免引发java.io.NotSerializableException异常); 或者将来这个类的实现可能改动(最大程度的保持版本兼容), 或者我们需要自定义序列化规则, 在这种情景下我们可选择实用自定义序列化.
transient
通过在实例变量前面加transient关键字以指定Java序列化时无需理会该实例变量(注意: transient关键字只能用于修饰实例变量, 不可修饰Java其他成分).
被transient修饰的实例变量被称为瞬时变量.
/** * 将ComplexBean的refBean属性设置不需要序列化 * * @author jifang * @since 15/12/31下午4:04. */ public class ComplexBean implements Serializable { private static final long serialVersionUID = 7046068335702080988L; private String name; private transient Bean refBean; public ComplexBean() { } public ComplexBean(String name, Bean refBean) { this.name = name; this.refBean = refBean; } //... }
/** * @author jifang * @since 16/1/13下午7:48. */ public class Serialization { @Test public void writeObject() throws IOException { try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("save.txt"))) { ComplexBean complexBean = new ComplexBean("complex_bean", new Bean(true, 3.14, "bean")); output.writeObject(complexBean); } } @Test public void readObject() throws IOException, ClassNotFoundException { try (ObjectInputStream input = new ObjectInputStream(new FileInputStream("save.txt"))) { // 可以发现complex_bean内的refBean属性为null ComplexBean complexBean = (ComplexBean) input.readObject(); System.out.println("complexBean: " + complexBean); } } }
readObject/writeObject
使用transient关键字修饰实例变量虽然简单, 但被transient修饰的实例变量将完全被隔离在序列化机制之外, 这样导致反序列化恢复Java对象时无法取得该实例变量的值. 因此, Java还提供了另一种自定义序列化机制,通过提供writeObject() readObject() readObjectNoData()等方法可以让程序控制如何序列化各实例变量, 甚至完全不序列化某些实例变量.
- private void writeObject(java.io.ObjectOutputStream output) throws IOException;
负责序列化类实例,以便readObject()可以恢复它;通过重写该方法, 可以实现完全控制该类的序列化机制,自主决定哪些实例变量需要实例化,需要怎样序列化. 在默认情况下, 该方法会调用output.defaultWriteObject()来保存Java对象的各实例变量, 从而可以实现序列化对象的目的. - private void readObject(java.io.ObjectInputStream input) throws IOException, ClassNotFoundException;
负责反序列化类实例,通过重写该方法,可以完全控制该类的反序列化机制,自主决定需要反序列化哪些实例变量,以及如何进行反序列化.在默认情况下, 该方法会调用input.defaultReadObject()来恢复Java对象的非瞬时变量.
一般readObject()应与writeObject()方法对应,如果writeObject()对Java对象的实例变量进行了一些处理, 则应该在readObject()方法中对其实例变量进行相应的反处理,以便正确恢复该对象.
/** * 自定义readObject, writeObject * * @author jifang * @since 16/1/13下午7:50. */ public class Bean implements Serializable { private static final long serialVersionUID = 2975296536292876992L; private boolean isUsed; private Double rate; private String name; public Bean() { } public Bean(boolean isUsed, Double rate, String name) { this.isUsed = isUsed; this.rate = rate; this.name = name; } private void writeObject(ObjectOutputStream output) throws IOException { // 将name实例变量值转为大写反转之后写入二进制流 output.writeObject(new StringBuilder(this.name.toUpperCase()).reverse()); // output.writeBoolean(isUsed); output.writeDouble(rate); } private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException { this.name = ((StringBuilder) input.readObject()).reverse().toString().toLowerCase(); //this.isUsed = input.readBoolean(); this.rate = input.readDouble(); } // ... }
由于我们可以自己定制序列化规则, 因此, 在网络传输中, 可以对对象实例进行加密, 在读出时自动解密, 这样即使在传输过程被截获, 获取到的也是加密后的值.但writeObject()方法的加密规则必须与readObject()的解密规则一致.
建议readObject() writeObject()的方法内首先调用defaultReadObject() defaultWriteObject();
- private void readObjectNoData() throws ObjectStreamException;
当序列化流不完整时, readObjectNoData()方法可以用来正确地反序列化对象.例如, 接收方使用的反序列化类的版本不同于发送方,或接收方版本扩展的类不是发送方版本扩展的类时,系统都会调用readObjectNoData()方法来初始化反序列化的对象. - private Object writeReplace() throws ObjectStreamException;
Java序列化机制保证在序列化某个对象之前, 先调用对象的writeReplace()方法, 如果该方法返回另一个Java对象, 则系统转化为序列化另一个对象.
/** * 实际序列化的是ArrayList * * @author jifang * @since 16/1/13下午7:50. */ public class Bean implements Serializable { private static final long serialVersionUID = 2975296536292876992L; private boolean isUsed; private Double rate; private String name; private Object writeReplace() throws ObjectStreamException { List<String> list = new ArrayList<>(); list.add("bean1"); list.add("bean2"); return list; } // ... }
/** * @author jifang * @since 16/1/13下午7:48. */ public class Serialization { @Before public void writeObject() throws IOException { try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("save.txt"))) { output.writeObject(new Bean()); } } @Test @SuppressWarnings(value = "unchecked") public void readObject() throws IOException, ClassNotFoundException { try (ObjectInputStream input = new ObjectInputStream(new FileInputStream("save.txt"))) { List<String> list = (List<String>) input.readObject(); for (String bean : list){ System.out.println(bean); } } } }
与writeReplace()对应, 序列化还有一个readResolve()方法
- private Object readResolve() throws ObjectStreamException;
可以实现保护性复制整个对象, 这个方法会紧接着readObject()之后调用, 该方法的返回值将会替换原来反序列化的对象, 而原来readObject()反序列化的对象将会被立即丢弃.
readResolve()方法在序列化单例类, 枚举类时尤其有用(细节请参考static, enum, 内部类与单例模式), 因此所有的单例类, 枚举类在实现序列化时都应该提供readResolve()方法.
readResolve()与writeReplace()还可以使用其他的访问修饰符, 但建议使用private修饰.
Externalizable
实现Externalizable接口以实现对象序列化, 这种序列化方式完全由程序员决定存储和恢复对象数据的机制.该接口提供了如下两个方法:
- public void writeExternal(ObjectOutput out) throws IOException;
序列化 - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
反序列化
/** * @author jifang * @since 16/1/13下午7:50. */ public class Bean implements Externalizable { private static final long serialVersionUID = 2975296536292876992L; private boolean isUsed; private Double rate; private String name; public Bean() { } public Bean(boolean isUsed, Double rate, String name) { this.isUsed = isUsed; this.rate = rate; this.name = name; } @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeBoolean(isUsed); out.writeDouble(rate); out.writeObject(name); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.isUsed = in.readBoolean(); this.rate = in.readDouble(); this.name = (String) in.readObject(); } // ... }
实现Externalizable接口序列化可以带来一定的性能提升,而且还可以完全自己的自定义序列化规则, 因此只有当默认的序列化形式(Serializable)能够合理地描述对象的逻辑状态时,才能实用默认的序列化形式(详见Effective Java第11/12章).
Java虚拟机读写其他进程数据
使用Runtime对象的exec()方法可以运行操作系统平台上的其他程序, 该方法返回一个Process对象, 代表由该Java程序启动的子进程.Process提供如下方法, 用于主进程和子进程进行通信:
方法 |
释义 |
InputStream getErrorStream() |
Returns the input stream connected to the error output of the subprocess. |
InputStream getInputStream() |
Returns the input stream connected to the normal output of the subprocess. |
OutputStream getOutputStream() |
Returns the output stream connected to the normal input of the subprocess. |
注意: Input/Output是站在主进程角度来看的.
/** * @author jifang * @since 16/1/10下午8:45. */ public class ProcessCommunication { public static void main(String[] args) throws IOException { InputStream input = Runtime.getRuntime().exec("ls").getInputStream(); System.out.println(CharStreams.toString(new InputStreamReader(input))); } }
注意: 上面程序使用到了Guava的CharStreams, 其详细用法请参考我的下一篇博客Java I/O 扩展.暂时可在pom中添加如下依赖:
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>19.0</version> </dependency>
相关文章
- 暂无相关文章
用户点评