序列化和反序列化 |
(1)概念
序列化:把内存中的java对象转换成二进制字节流,这样就可以把java对象存储在磁盘中,或者在网络中传输了。
反序列化:程序从网络或者磁盘中获得二进制流后,将其恢复成原来的java对象。这就是反序列化。
下面演示序列化对象的步骤:
//Person类实现了Serializable接口,则此类的对象就是可序列化的,可在网络中进行传输
public class Person implements java.io.Serializable{
private String name;
private int age;
public Person(String name,int age){
this.name=name;
this.age=age;
}
//省略get/set方法
}
public class WriteObject{
public static void main(String[] args){
try(
//创建一个ObjectOutputStream输出流
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("object.txt"));
){
Person p=new Person("孙悟空",500);
//将p对象写入输出流
oos.writeObject(p);
}catch(IOException ex){
ex.printStackTrace();
}
}
}
以上是序列化的步骤,即将java对象转换为二进制字节流。最后生成一个object.txt文件,该文件的内容就是Person对象。如果是反序列化,大致修改的地方是将ObjectOutputStream改为ObjectInputStream,writeObject改为readObject。
值得一提的是,以上Person类的name、age属性为基本类型,所以可以进行以上处理。如果类的属性是引用类型呢?那么需要引用类也是可序列化的,否则此Person类便不可序列化。
(2)过滤
过滤功能就是设置一个过滤器,在反序列化之前对数据进行检查,查看其是否符合反序列化的要求。
//为对象输入流设置过滤器
setObjectInputFilter();
//当程序通过ObjectInputStream反序列化对象时,
//过滤器的checkInput()方法会被自动激发,来检查序列化数据是否有效
checkInput();
//此方法有3个返回值,分别为拒绝,允许,未决定。
Status.rejected;
Status.allowed;
Status.undecided;
(3)自定义
当我们不希望系统对某个实例变量值进行序列化或某个实例变量本身就是不可序列化的,我们可以使用transient。只要在实例变量前面加上transient关键字,程序序列化时就会忽略此实例变量。
还以开头的Person类为例。
private transient int age;
缺点:将此实例变量完全隔离在序列化机制之外,如此在反序列化恢复java对象时,无法取得该实例变量的值。
下面提供另一种自定义序列化的方法。
private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
//将name实例变量值反转后写入二进制流
out.writeObject(new StringBuffer(name).reverse());
out.writeInt(age);
}
private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException
{
//将读取的字符串反转给name实例变量
this.name=((StringBuffer)in.readObject()).reverse().toString();
this.age=in.readInt();
}
NIO |
NIO就是New IO,指在JDK1.4之后改进的IO新功能。
传统IO流是阻塞式的,处理单位是字节,所以效率不高。新IO采用内存映射文件的方式来处理IO,将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了。如果说传统IO是面向流的处理,则新IO就是面向块的处理。
Channel和Buffer是新IO中的两个核心对象。下面对其进行详细说明。
(1)Channel(通道)
Channel是对传统的输入/输出系统的模拟,在新IO系统中,所有的数据都需要通过通道传输。其与传统的InputStream、OutputStream最大的区别是提供了一个可以将“一块数据”映射到内存中的map()方法。
//对a.txt文件进行复制
File f=new File("a.txt");
try(
//创建一个RandomAccessFile对象
RandomAccessFile raf=new RandomAccessFile(f,"rw");
//获取RandomAccessFile对应的Channel
FileChannel randomChannel=raf.getChannel()
){
//将Channel中的所有数据映射成ByteBuffer
ByteBuffer buffer=randomChannel.map(FileChannel.MapMode.READ_ONLY,0,f.length());
//把Channel的记录指针移动到最后
randomChannel.position(f.length());
//将buffer中的所有数据输出
randomChannel.write(buffer);
}
(2) Buffer(缓冲)
Buffer的本质是一个数组, 发送到Channel中的所有对象都必须首先放到Buffer中,从Channel中读取的数据也必须先放到Buffer中。其使用较多的子类是ByteBuffer、CharBuffer。
//创建Buffer
CharBuffer buff=CharBuffer.allocate(8);
//放入元素
buff.put('a');
//为从Buffer中取出数据做好准备
buff.flip();
//为再次向Buffer中装入数据做好准备
buff.clear();
//返回Buffer的容量大小。获取界限和位置的方法类似
buff.capacity();
(3)NIO.2
NIO.2是对NIO进行了改进,使我们对文件进行某些操作时更方便了。
- Path接口
代表一个平台无关的平台路径。
- Paths工具类
包含了两个返回Path的静态工厂方法。其中可使用WatchService来监控文件变化。
public class FilesTest{
public static void main(main args) throws Exception{
//复制文件
Files.copy(Paths.get("FilesTest.java"),new FileOutputStream("a.txt"));
//一次性读取FilesTest.java文件的所有行
List<String> lines=Files.readAllLines(Paths.get("FilesTest.java"),Charset.forName("gbk"));
System.out.println(lines);
//判断指定文件的大小
Files.size(Paths.get("FilesTest.java"));
//直接将多个字符串内容写入指定文件中
List<String> b=new ArrayList<>();
b.add("锄禾日当午");
b.add("汗滴禾下土");
Files.write(Paths.get("b.txt"),poem,Charset.forName("gbk"));
}
}
- Files工具类
包含了大量静态的工具方法来操作文件。其中可使用FileVisitor来遍历文件和目录。
- Attribute包
访问文件属性
总结 |
从传统IO到NIO,再到NIO.2,可以很明显地发现方法的封装性更强,开发人员使用起来更便捷了。这次对IO的总结很好地诠释了“挂一漏万”的思想(⊙︿⊙),庆幸的是也收获了挺多,加油ヾ(◍°∇°◍)ノ゙。