★文件流
文件操作是最简单最直接也是最容易想到的一种方式,我们说的文件操作不仅仅是通过FileInputStream/FileOutputStream这么“裸”的方式直接把数据写入到本地文件(像我以前写的一个扫雷的小游戏JavaMine就是这样保存一局的状态的),这样就比较“底层”了。
主要类与方法 | 描述 |
---|---|
FileInputStream.read() | 从本地文件读取二进制格式的数据 |
FileReader.read() | 从本地文件读取字符(文本)数据 |
FileOutputStream.write() | 保存二进制数据到本地文件 |
FileWriter.write() | 保存字符数据到本地文件 |
★XML
和上面的单纯的I/O方式相比,XML就显得“高档”得多,以至于成为一种数据交换的标准。以DOM方式为例,它关心的是首先在内存中构造文档树,数据保存在某个结点上(可以是叶子结点,也可以是标签结点的属性),构造好了以后一次性的写入到外部文件,但我们只需要知道文件的位置,并不知道I/O是怎么操作的,XML操作方式可能多数人也实践过,所以这里也只列出相关的方法,供初学者预先了解一下。主要的包是javax.xml.parsers,org.w3c.dom,javax.xml.transform。
主要类与方法 | 描述 |
---|---|
DocumentBuilderFactory.newDocumentBuilder().parse() | 解析一个外部的XML文件,得到一个Document对象的DOM树 |
DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() | 初始化一棵DOM树 |
Document.getDocumentElement(). appendChild() | 为一个标签结点添加一个子结点 |
Document.createTextNode() | 生成一个字符串结点 |
Node.getChildNodes() | 取得某个结点的所有下一层子结点 |
Node.removeChild() | 删除某个结点的子结点 |
Document. getElementsByTagName() | 查找所有指定名称的标签结点 |
Document.getElementById() | 查找指定名称的一个标签结点,如果有多个符合,则返回某一个,通常是第一个 |
Element.getAttribute() | 取得一个标签的某个属性的的值 |
Element.setAttribute() | 设置一个标签的某个属性的的值 |
Element.removeAttribute() | 删除一个标签的某个属性 |
TransformerFactory.newInstance().newTransformer().transform() | 将一棵DOM树写入到外部XML文件 |
★序列化
使用基本的文件读写方式存取数据,如果我们仅仅保存相同类型的数据,则可以用同一种格式保存,譬如在我的JavaMine中保存一个盘局时,需要保存每一个方格的坐标、是否有地雷,是否被翻开等,这些信息组合成一个“复合类型”;相反,如果有多种不同类型的数据,那我们要么把它分解成若干部分,以相同类型(譬如String)保存,要么我们需要在程序中添加解析不同类型数据格式的逻辑,这就很不方便。于是我们期望用一种比较“高”的层次上处理数据,程序员应该花尽可能少的时间和代码对数据进行解析,事实上,序列化操作为我们提供了这样一条途径。
序列化(Serialization)大家可能都有所接触,它可以把对象以某种特定的编码格式写入或从外部字节流(即ObjectInputStream/ObjectOutputStream)中读取。序列化一个对象非常之简单,仅仅实现一下Serializable接口即可,甚至都不用为它专门添加任何方法:
public class MySerial implements java.io.Serializable { ... } |
但有一个条件:即你要序列化的类当中,它的每个属性都必须是是“可序列化”的。这句话说起来有点拗口,其实所有基本类型(就是int,char,boolean之类的)都是“可序列化”的,而你可以看看JDK文档,会发现很多类其实已经实现了Serializable(即已经是“可序列化”的了),于是这些类的对象以及基本数据类型都可以直接作为你需要序列化的那个类的内部属性。如果碰到了不是“可序列化”的属性怎么办?对不起,那这个属性的类还需要事先实现Serializable接口,如此递归,直到所有属性都是“可序列化”的。
主要类与方法 | 描述 |
---|---|
ObjectOutputStream.writeObject() | 将一个对象序列化到外部字节流 |
ObjectInputStream.readObject() | 从外部字节流读取并重新构造对象 |
从实际应用上看来,“Serializable”这个接口并没有定义任何方法,仿佛它只是一个标记(或者说像是Java的关键字)而已,一旦虚拟机看到这个“标记”,就会尝试调用自身预定义的序列化机制,除非你在实现Serializable接口的同时还定义了私有的readObject()或writeObject()方法。这一点很奇怪。不过你要是不愿意让系统使用缺省的方式进行序列化,那就必须定义上面提到的两个方法:
public class MySerial implements java.io.Serializable { private void writeObject(java.io.ObjectOutputStream out) throws IOException { ... } private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { ... } ... } |
譬如你可以在上面的writeObject()里调用默认的序列化方法ObjectOutputStream.defaultWriteObject();譬如你不愿意将某些敏感的属性和信息序列化,你也可以调用ObjectOutputStream.writeObject()方法明确指定需要序列化那些属性。关于用户可定制的序列化方法,我们将在后面提到。
★Bean
上面的序列化只是一种基本应用,你把一个对象序列化到外部文件以后,用notepad打开那个文件,只能从为数不多的一些可读字符中猜到这是有关这个类的信息文件,这需要你熟悉序列化文件的字节编码方式,那将是比较痛苦的(在《Core Java 2》第一卷里提到了相关编码方式,有兴趣的话可以查看参考资料),某些情况下我们可能需要被序列化的文件具有更好的可读性。另一方面,作为Java组件的核心概念“JavaBeans”,从JDK 1.4开始,其规范里也要求支持文本方式的“长期的持久化”(long-term persistence)。
打开JDK文档,java.beans包里的有一个名为“Encoder”的类,这就是一个可以序列化bean的实用类。和它相关的两个主要类有XMLEcoder和XMLDecoder,显然,这是以XML文件的格式保存和读取bean的工具。他们的用法也很简单,和上面ObjectOutputStream/ObjectInputStream比较类似。
主要类与方法 | 描述 |
---|---|
XMLEncoder.writeObject() | 将一个对象序列化到外部字节流 |
XMLDecoder.readObject() | 从外部字节流读取并重新构造对象 |
如果一个bean是如下格式:
public class MyBean { int i; char[] c; String s; ...(get和set操作省略)... } |
那么通过XMLEcoder序列化出来的XML文件具有这样的形式:
<?xml version="1.0" encoding="UTF-8"?> <java version="1.4.0" class="java.beans.XMLDecoder"> <object class="MyBean"> <void property="i"> <int>1</int> </void> <void property="c"> <array class="char" length="3"> <void index="0"> <int>a</int> </void> <void index="1"> <int>b</int> </void> <void index="2"> <int>c</int> </void> </array> </void> <void property="s"> <string>fox jump!</string> </void> </object> </java> |
像AWT和Swing中很多可视化组件都是bean,当然也是可以用这种方式序列化的,下面就是从JDK文档中摘录的一个JFrame序列化以后的XML文件:
<?xml version="1.0" encoding="UTF-8"?> <java version="1.0" class="java.beans.XMLDecoder"> <object class="javax.swing.JFrame"> <void property="name"> <string>frame1</string> </void> <void property="bounds"> <object class="java.awt.Rectangle"> <int>0</int> <int>0</int> <int>200</int> <int>200</int> </object> </void> <void property="contentPane"> <void method="add"> <object class="javax.swing.JButton"> <void property="label"> <string>Hello</string> </void> </object> </void> </void> <void property="visible"> <boolean>true</boolean> </void> </object> </java> |
因此但你想要保存的数据是一些不是太复杂的类型的话,把它做成bean再序列化也不失为一种方便的选择。