6.3 档案入库:File与对象序列化


6.3 档案入库:File 与对象序列化

本节摘要:File 是档案柜管理员——只管路径与元信息(存在否、大小、建删改名、列目录),不碰内容;内容读写要回到流。对象序列化把实现了 Serializable 资格证的对象按字节封套写入,反序列化原样拆封还原;serialVersionUID 是封套上的版本号,不一致会拆封失败。transient 字段不封存。本节过一遍 File 的元信息操作,完成一次对象的入柜与取回。

File 管什么、不管什么

新手最常见的误解是把 File 当"文件内容"——其实它只是路径的抽象:一个名字加一串元信息操作。它像档案柜管理员:告诉你某格有没有档案、档案多厚、柜子叫什么、里面有哪些格子,可以把档案改名、销毁、新建柜子,唯独不读不写档案内容——那是流的活:

import java.io.File; import java.io.IOException; public class FileDemo { public static void main(String[] args) throws IOException { File dir = new File("registry"); // 一个柜子(目录) System.out.println("柜子存在吗:" + dir.exists()); // 首次运行 输出:false System.out.println("新建目录:" + dir.mkdirs()); // 输出:true(含父级一并创建) System.out.println("是目录吗:" + dir.isDirectory()); // 输出:true File doc = new File(dir, "a1.txt"); // 柜子里的一个档案 System.out.println("档案存在吗:" + doc.exists()); // 输出:false System.out.println("新建空文件:" + doc.createNewFile()); // 输出:true System.out.println("档案大小:" + doc.length() + " 字节"); // 输出:0(空文件) // 列出柜子里的全部条目 new File(dir, "a2.txt").createNewFile(); String[] items = dir.list(); for (String s : items) System.out.println("柜内条目:" + s); // 输出两行:a1.txt 与 a2.txt File renamed = new File(dir, "a1-renamed.txt"); System.out.println("改名:" + doc.renameTo(renamed)); // 输出:true System.out.println("删除:" + renamed.delete()); // 输出:true } }

这套操作覆盖了"运行前检查环境"的常见需求:启动时确认数据目录存在、不存在就建;批量处理前列出目录下所有文件;写临时文件前判断重名。注意 File 不抛"找不到文件"的异常,用布尔返回值报告成败——这与流的行为不同(流找不到文件会抛受检异常,第 7 章的主角)。另外 delete 对非空目录返回失败:目录里有东西就删不掉,要递归先清空再删,这是个小坑。

序列化:把对象封进标准封套

集合和对象活在内存里,进程一退就没了。序列化把一个对象的状态(字段值)转成字节序列写出去;反序列化把字节序列还原成对象。用途三类:落盘持久化(今日快照明日还能读)、网络传输(对象变成字节才能上线)、深拷贝(先序列化再反序列化得到独立副本)。

入场的资格是 Serializable 接口——第 3 章发证科发的那张"可入库资格证":没有任何方法的标记接口,实现了才允许序列化,否则运行时抛不可序列化异常。读写用对象流这对处理流:

import java.io.Serializable; class ResidentCard implements Serializable { // 持证:可入库 private static final long serialVersionUID = 1L; // 封套版本号 手动声明 String name; int room; transient String tempNote; // transient:临时便签 不封存 ResidentCard(String name, int room, String note) { this.name = name; this.room = room; this.tempNote = note; } @Override public String toString() { return name + " 住 " + room + " 室(便签:" + tempNote + ")"; } }
import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class SerialDemo { public static void main(String[] args) throws IOException, ClassNotFoundException { File store = new File("card.bin"); ResidentCard out = new ResidentCard("张三", 301, "周一交材料"); try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(store))) { oos.writeObject(out); // 封套入库 整个对象状态写成字节 } try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(store))) { ResidentCard in = (ResidentCard) ois.readObject(); // 拆封还原 需要强转 System.out.println(in); // 输出:张三 住 301 室(便签:null)——transient 字段没有封存 拆封后是默认值 } System.out.println("是同一个对象吗:" + (out == new ResidentCard("张三", 301, ""))); // 输出:false 序列化产出的是内容相同的新对象 } }

图 6-3 序列化入柜与拆封还原

图 6-3 序列化入柜与拆封还原

版本号:拆封时的对暗号

serialVersionUID 是封套上的版本暗号。不手动声明时,编译器按类结构自动算一个——类一改(加个字段、加个方法),自动值就变,旧封套拆封立刻抛"无效类"异常:版本号不匹配。手动声明成固定值后,小的类变更(加字段)仍能拆旧封套,新字段取默认值。两种策略各有立场:需要兼容旧数据(长期落盘、跨版本传输)就手动声明并谨慎对待每次变更;确认数据只在同一版本内流转(缓存场景),自动版本号反而能在类变更后让旧缓存全部失效,避免读到陈旧结构。关键是知道这个机制存在、做有意识的选择,而不是被默认行为悄悄安排。

完整案例背景:登记系统每日收市后把当日住户列表落盘,次日启动恢复继续办。操作:列表整体一次写入、一次读回——集合只要装的是可序列化元素,整表可封:

import java.io.*; import java.util.ArrayList; import java.util.List; public class SnapshotCase { public static void main(String[] args) throws IOException, ClassNotFoundException { List<ResidentCard> today = new ArrayList<>(); today.add(new ResidentCard("张三", 301, "待补照片")); today.add(new ResidentCard("李四", 302, "已交材料")); try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("snapshot.bin"))) { oos.writeObject(today); // 整表入柜 引用闭包自动带上每个元素 } List<ResidentCard> restored; try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("snapshot.bin"))) { restored = (List<ResidentCard>) ois.readObject(); // 拆封 恢复为列表 } System.out.println("恢复条数:" + restored.size()); // 输出:恢复条数:2 for (ResidentCard c : restored) System.out.println(c); // 输出两行 姓名房号完好 便签均为 null } }

结果:次日启动,列表从快照完整恢复(transient 便签除外)。解读:序列化的引用闭包特性在这里是福利——写一个列表等于写整张对象网;但反过来,若对象互相引用成环或引用了不可序列化的资源(比如打开的流、连接),序列化就会失败,这也是"资源句柄类字段标 transient"的原因。变式:若快照需要人类可读、可人工校对,改用文本格式(每行一条、按行读写,6.2 节的骨架)更合适;需要跨语言交换时用行业通用的结构化格式更稳——序列化的字节格式是 Java 私有的,别的语言读不懂,且不同版本运行环境间也可能有兼容差异,这是选型时必须权衡的一点。

本节要点回顾

  • File 管元信息不管内容:存在、大小、目录、建删改名、列条目;内容读写靠流;操作用布尔返回值不抛"找不到"
  • 序列化资格证 Serializable:标记接口无方法,不持证运行时抛不可序列化;字段引用的类型也要持证
  • transient 与 static 都不封存:拆封后取默认值——敏感字段、缓存字段、资源句柄字段标 transient
  • 版本号对暗号:serialVersionUID 手动声明可兼容小变更,自动生成则类一改旧封套全废;按数据寿命做有意识的选择
  • 引用闭包整网入柜:序列化集合会连带全部元素;引用环与不可序列化资源是失败源

数据搬进搬出都齐了,最后一件事是给档案盖时间戳。下一节看日期时间:老 API 的月份从零数起、格式化类线程不安全这些历史包袱,以及新时间包怎么用"专人专管"把问题清干净。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U