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
/DataOutputStream
ObjectInputStream
/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>