RandomAccessFile
RandomAccessFile类是java.io包中一个特别的类,简称随机访问文件流,这里的“随机”不是随机读取或者写入的意思,而是说可以使用该类实现在文件的任意位置进行读写操作;因此,该类既可以实现输入流(InputStream)相关功能,也能实现输出流(OutputStream)相关功能,另外RandomAccessFile类从DataInput和DataOutput接口实现,具备直接操作元数据的能力,RandomAccesFile操作文件包含两个构造器:
-
RandomAccessFile(File file,String mode)
-
RandomAccessFile(Stirng fileName,String mode)
以上构造器中mode参数为设置流的读写模式,可选值为以下四个:
-
"r" 以只读方式打开流
-
“rw” 以读写方式打开流
-
"rws" 基于“rw”还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备
-
"rwd" 基于“rw”还要求对文件内容的每个更新都同步写入到底层存储设备
常用方法包含以下:
-
read() 从流中读取一个字节
-
read(byte[] b) 将流中读取的字节存储到字节数组
-
readInt() 从流中读取一个int数据
-
readDouble() 从流中读取一个double数据
-
readBoolean() 从流中读取一个boolean数据
-
readChar() 从流中读取一个char数据
-
...
-
write(int b) 写入字节到文件中
-
write(byte[] b) 写入字节数组到文件中
-
writeInt(int i) 以元数据的方式写入一个int值到文件中
-
writeDouble(double d) 以元数据的方式写入一个double值到文件中
-
writeBoolean(boolean b) 以元数据的方式写入一个boolean值到文件中
-
writeChar(int c) 以元数据的方式写入一个char值到文件中
-
...
-
getFilePointer() 获取当前流中的文件指针
-
seek(long l) 跳过指定长度字节数之后发生下一个读取或者写入
实例1:
public class RandomStreamDemo {
public static void main(String[] args) throws IOException {
//创建一个随机访问文件流
RandomAccessFile raf = new RandomAccessFile("C:/Users/mrchai/Desktop/javacode/Dog.java", "r");
// raf.read();
// raf.read();
// raf.read();
// raf.read();
// //获取当前文件指针的位置
// long pos = raf.getFilePointer();
// System.out.println(pos);
// raf.writeUTF(String)
// byte[] b = new byte[1024];
// int len = 0;
// while((len = raf.read(b)) != -1){
// System.out.println(new String(b,0,len));
// }
}
}
实例2:
public class RandomStreamDemo2 {
public static void main(String[] args) throws IOException {
RandomAccessFile raf = new RandomAccessFile("C:/Users/mrchai/Desktop/a.txt", "rw");
// raf.writeLong(10000L);//占用8字节
// raf.writeInt(10);//占用4字节
// raf.writeFloat(3.14f);//占用4字节
// raf.writeDouble(3560.98);//占用8字节
// raf.writeBoolean(true);//占用1bits
//跳过指定个字节
raf.seek(24);
// float f = raf.readFloat();
boolean f = raf.readBoolean();
System.out.println(f);
}
}
对象序列化
背景
在玩游戏时,比如单机类的角色扮演游戏,在玩到一定的级别和状态后如果需要关闭电脑(退出游戏),如何能实现保存当前游戏的进度以及角色的实时状态?又比如:在电商系统中需要对当前被检索的商品缓存到本地文件存储起来,同时需要能够在适当时候从本地文件中重新查询出数据;又比如:如何实现将一个java对象通过网络进行传输,并且最大限度的保证对象数据的完整性?
概述
通过对以上问题分析得出,将一个对象的信息完整的存储到磁盘,网络或者其他存储介质中,并且能够在适当的时候完整的还原这个对象的信息,这些需求很常见的操作;因此在java-io中提供了一种对象序列化(Serializable)的技术,这种技术可以通过特殊的手段将一个java对象中包含的数据按照一定的组织格式存储到对应的存储介质(文件,网络),并且可以通过反序列化技术将对象完整还原回来。
Java-IO中对象序列化的实现方式有两种:
-
实现Serializable接口
-
实现Externalizable接口
Serializable接口
Serializable接口是一个标记型接口,该接口内部不包含任何未实现方法,接口的作用仅限于标记该类所对应的对象是可序列化的,具体使用如下:
-
需要被序列化对象对应的类实现Serializable接口
/** * 玩家类 * @author mrchai */ public class Player implements Serializable{ /** * 生成一个随机的序列号,用于反序列化时的对象校验 */ private static final long serialVersionUID = 1281206283763342806L; private int id;//id private String name;//角色名 private String niceName;//称号 private int level;//等级 private double money;//游戏币 private int hp;//血量 private int mp;//魔法量 public Player() { // TODO Auto-generated constructor stub } public Player(int id, String name, String niceName, int level, double money, int hp, int mp) { super(); this.id = id; this.name = name; this.niceName = niceName; this.level = level; this.money = money; this.hp = hp; this.mp = mp; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getNiceName() { return niceName; } public void setNiceName(String niceName) { this.niceName = niceName; } public int getLevel() { return level; } public void setLevel(int level) { this.level = level; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } public int getHp() { return hp; } public void setHp(int hp) { this.hp = hp; } public int getMp() { return mp; } public void setMp(int mp) { this.mp = mp; } }
-
使用ObjectOutputStream和ObjectInputStream实现对象序列化和反序列化
public class TestPlayer { public static void main(String[] args) throws IOException, ClassNotFoundException { Player p = new Player(1, "渣渣辉", "嗜血狂魔", 88, 35000, 15000, 3500); p.setMsg("等级提升!"); //序列化 // FileOutputStream fos = new FileOutputStream("src/savepoint.txt"); // //基于节点流获取一个对象输出流(完成对象序列化) // ObjectOutputStream oos = new ObjectOutputStream(fos); // oos.writeObject(p); // oos.flush(); // oos.close(); //反序列化 FileInputStream fis = new FileInputStream("src/savepoint.txt"); //基于节点流获取一个对象的输入流(完成反序列化) ObjectInputStream bis = new ObjectInputStream(fis); Object obj = bis.readObject(); p = (Player)obj; System.out.println(p.getName()); } }
-
Externalizable接口
另一种对象序列化的方式是使用需要被序列化对象对应的类实现Externalizable,Externalizable是从Serializable接口继承而来,在其基础上新增了两个未实现方法:readExternal(ObjectInput in)和writeExternal(ObjectOutput out);通过对这两个方法的实现,可以自定义对那些必要的属性进行序列化和反序列化操作,具体使用如下:
-
需要被序列化对象对应的类实现Externalizable接口,同时实现两个方法readExternal,writeExternal
public class User implements Externalizable{ private int id; private String name; private String pwd; public User() { } public User(int id, String name, String pwd) { super(); this.id = id; this.name = name; this.pwd = pwd; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; } @Override public void readExternal(ObjectInput input) throws IOException, ClassNotFoundException { //指定需要被反序列化的属性并返回给成员变量 id = input.readInt(); name = input.readUTF(); } @Override public void writeExternal(ObjectOutput output) throws IOException { //指定需要被序列化的属性 output.writeInt(id); output.writeUTF(name); } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", pwd=" + pwd + "]"; } }
transient关键字
在对对象进行序列化操作时,可能由于某些属性为临时变量,并不需要被执行序列化操作,此时可以对相关属性使用transient关键字进行修饰即可,这样改属性就成为瞬时属性,在进行对象序列化操作时,该属性值不会被序列化,例如:
public class Player implements Serializable{ /** * 生成一个随机的序列号 */ private static final long serialVersionUID = 1281206283763342806L; private int id;//id private String name;//角色名 private String niceName;//称号 private int level;//等级 private double money;//游戏币 private int hp;//血量 private int mp;//魔法量 private transient String msg; //临时信息,不需要被序列化 //类变量也不允许被序列化 static int n = 100; //构造器定义(略) //setter/getter方法定义(略) }
注意事项:
被static修饰的属性不再是实例变量(对象属性),直接属于类,因此被static修饰的属性也不能被序列化。