【java筑基】IO流进阶之文件随机访问、序列化与反序列化

简介: 【java筑基】IO流进阶之文件随机访问、序列化与反序列化

1.文件的随机访问

RandomAccessFile支持对于文件的随机访问(而不是只能从头开始读写),创建RandomAccessFile对象时需要传入mode参数,该参数有4个值:r(read), rw(read,write), rws(read, write and store data and file into device memory),rwd((read, write and store file into device memory).

    public class RandomAcessFileTest {
      public static void main(String[] args) {
        try (RandomAccessFile raf = new RandomAccessFile("newFile.txt", "rw")) {
          System.out.println("The initial local of RandomAccessFile's index:"
              + raf.getFilePointer());
          byte[] bbuf = new byte[1024];
          int hasRead = 0;
          while ((hasRead = raf.read(bbuf)) > 0) {
            System.out.println(new String(bbuf, 0, hasRead));
          }
          // move the index
          raf.seek(20);
          raf.write("hello, apend\r\n".getBytes());
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

前面的程序只能够实现在文件后面追加内容,而不能在文件中间插入内容,否则会覆盖插入位置的文件内容,要实现在文件中插入内容,只需要设置一个缓存的临时文件存储插入位置后面的文件内容即可。

    public class InsertContext {
      public static void insert(String fileName, long pos, String insertContent)
          throws IOException {
        File tmp = File.createTempFile("tmp", null);
        tmp.deleteOnExit();
        try (RandomAccessFile raf = new RandomAccessFile(fileName, "rw");
            FileOutputStream tmpOut = new FileOutputStream(tmp);
            FileInputStream tmpIn = new FileInputStream(tmp))
        {
          raf.seek(pos);
          byte[] buf = new byte[64];
          int hasRead = 0;
          while ((hasRead = raf.read(buf)) > 0) {
            tmpOut.write(buf, 0, hasRead);
          }
          raf.seek(pos);
          raf.write(insertContent.getBytes());
          while ((hasRead = tmpIn.read(buf)) > 0) {
            raf.write(buf, 0, hasRead);
          }
        }
      }
      public static void main(String[] args) throws IOException {
        insert("newFile.txt", 45, "插入的内容\r\n");
      }
    }

2.序列化与反序列化

2.1 对象序列化

将对象进行序列化可以使对象能够持久化到磁盘中或者进行网络传输,从而使对象脱离程序运行而独立存在,这对于分布式应用很有意义。要实现序列化对象需要实现Seriazable接口或者Externalizable接口。通常,对于javaBean类都建议实现Seriazable接口。下面实现了一个简单的对象序列化的demo。

    public class Person implements Serializable {
      private String name;
      private int age;
      public Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
      public String getName() {
        return name;
      }
      public void setName(String name) {
        this.name = name;
      }
      public int getAge() {
        return age;
      }
      public void setAge(int age) {
        this.age = age;
      }
    }
    public class WriteObject {
      public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("test.txt"));) {
          Person person = new Person("wkx", 3);
          oos.writeObject(person);
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

2.2 对象的反序列化

如果希望从二进制流中恢复对象,则可以进行反序列化。

    public class ReadObject {
      public static void main(String[] args) {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
            "test.txt"))) {
          Person p = (Person) ois.readObject();
          System.out
              .println("p.name=" + p.getName() + ",p.age=" + p.getAge());
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }

2.3 对象引用的序列化

如果一个类的成员变量是引用类型,该成员变量所在类要实现序列化,则该成员变量必须也是可以序列化的。假定以下场景:

​  Student student = new Student("wz");
    Teacher t1 = new Teacher(student,"Chinese");
    Teacher t2 = new Teacher(student,"Math");

对象t1中有一个引用成员变量student,那么在序列化对象t1时,系统也会序列化student对象,那么在序列化对象t2,系统还需要序列化一个Student吗?当然不会,否则进行反序列化回来的时候,t1和t2所指向的student对象就不是同一个对象了。java在序列化一个对象时,会先检查该对象是否已经被序列化了,如果没有,则进行序列化并输出,如果已经序列化过了,则返回一个该对象在磁盘中的序列化编号即可。

java的序列化机制也存在着隐患,如果在第一次序列化一个对象之后,该对象的内容发生了改变,第二次序列化该对象只是输出一个序列化编号,不会重新将对象的内容转换为字节流输出。


2.4 隐私信息的加密与解密

在一些特殊场景里,如果有某些实例变量是敏感信息,比如银行账户信息,我们可能不希望它被序列化。又或者某个成员变量本身是不可以被序列化的,为了避免出现Java.io.NotSeriazableException,我们希望在序列化它所在的对象时该成员变量不会被递归序列化。可以使用trasient关键字对这些成员变量进行修饰。


使用trasient关键字虽然方便,但是也可能带来问题:我们在进行反序列化时无法恢复这些成员变量了。因此Java提供了自定义序列化的机制,允许我们在自定义各个成员变量是否序列化以及让程序控制如何序列化各实例变量。下面程序通过重写writeObject(),readObject()方法实现自定义序列化,对隐私数据name进行了加密,降低安全风险,同时有可以反序列化回来。

    public class Person implements Serializable {
      private String name;
      private int age;
      public Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
      public String getName() {
        return name;
      }
      public void setName(String name) {
        this.name = name;
      }
      public int getAge() {
        return age;
      }
      public void setAge(int age) {
        this.age = age;
      }
      //Override
      private void writeObject(ObjectOutputStream out) throws IOException{
        out.writeObject(new StringBuffer(name).reverse());
        out.writeInt(age);
      }
      private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
        this.name = ((StringBuffer)in.readObject()).reverse().
            toString();
        this.age = in.readInt();
      }
    }

2.5 彻底的自定义序列化机制

有一种彻底的自定义序列化机制,可以在序列化对象时将该对象替换成其它对象。

    public class Person implements Serializable {
      private String name;
      private int age;
      public Person(String name, int age) {
        this.name = name;
        this.age = age;
      }
      public String getName() {
        return name;
      }
      public void setName(String name) {
        this.name = name;
      }
      public int getAge() {
        return age;
      }
      public void setAge(int age) {
        this.age = age;
      }
      // Override
      private Object writeReplace(){
        ArrayList<Object> list = new ArrayList<Object>();
        list.add(name);
        list.add(age);
        return list;
      }
    }

上面的代码重写了writeReplace()方法,Java的序列化机制保证在序列化时先调用该对象的writeReplace方法,如果该方法返回的是另外一个Java对象,则序列化另一个对象。参考下列代码。

    public class ReplaceTest {
      public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(
            new FileOutputStream("test.txt"));
            ObjectInputStream ois = new ObjectInputStream(
                new FileInputStream("test.txt"))) {
          Person per = new Person("w", 18);
          oos.writeObject(per);
          ArrayList list = (ArrayList) ois.readObject();
          System.out.println(list);
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    }

2.6 单例类的序列化

java中有一个与writeReplace()相对的方法readReasolve(),该方法会在readObject之后调用,可以实现保护性的复制整个对象。所有的单例类在实现序列化时,都应该重写readReasolve()方法,这样才能确保在反序列化回来后的对象与单例对象是同一对象(反序列化恢复对象不需要调用构造器)。参考下列代码。

    public class School implements Serializable {
      public static School highSchool = new School();
      private School() {
      }
    }
    public class readResolveTest {
      public static void main(String[] args) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
            "test.txt"));
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
            "test.txt"));
        oos.writeObject(School.highSchool);
        School sch = (School) ois.readObject();
        //false
        System.out.println(sch == School.highSchool);
      }
    }

将school中的readResolve重写后,readResolveTest将输出true。

    public class School implements Serializable {
      public static School highSchool = new School();
      private School() {
      }
       public Object readResolve() {
       return highSchool;
       }
    }

这篇文章就介绍到这里了。


相关文章
|
4天前
|
Java 开发者 UED
【实战攻略】Java异常处理进阶:throw关键字,打造坚不可摧的错误防御体系!
【6月更文挑战第19天】在Java中,`throw`关键字用于主动抛出异常,特别是在检测到错误条件如非法参数时。通过`throw`,开发者能控制何时中断程序并提供清晰的错误信息。例如,在验证订单金额时,如果金额小于等于零,可以抛出`IllegalArgumentException`。此外,`throw`还可用于构建异常链,保留错误上下文,便于问题追溯。掌握`throw`使用,是构建健壮异常处理和提升用户体验的关键。
|
2天前
|
机器学习/深度学习 Java Serverless
Java开发者的神经网络进阶指南:深入探讨交叉熵损失函数
今天来讲一下损失函数——交叉熵函数,什么是损失函数呢?大体就是真实与预测之间的差异,这个交叉熵(Cross Entropy)是Shannon信息论中一个重要概念,主要用于度量两个概率分布间的差异性信息。在信息论中,交叉熵是表示两个概率分布 p,q 的差异,其中 p 表示真实分布,q 表示预测分布,那么 H(p,q)就称为交叉熵:
|
2天前
|
Java 数据挖掘 开发者
Java网络编程进阶:Socket通信的高级特性与应用
【6月更文挑战第21天】Java Socket通信是分布式应用的基础,涉及高级特性如多路复用(Selector)和零拷贝,提升效率与响应速度。结合NIO和AIO,适用于高并发场景如游戏服务器和实时数据分析。示例展示了基于NIO的多路复用服务器实现。随着技术发展,WebSockets、HTTP/2、QUIC等新协议正变革网络通信,掌握Socket高级特性为应对未来挑战准备。
|
6天前
|
网络协议 Java API
【Java】序列化和反序列化
【Java】序列化和反序列化
15 4
|
9天前
|
存储 Java
Java 新手进阶:从变量到常量,一步步走向编程巅峰!
【6月更文挑战第14天】Java新手应掌握变量与常量,它们是编程基础。通过示例展示变量(如矩形的长度和宽度)用于存储可变数据,常量(如重力加速度)用于表示固定值。理解不同类型的变量,如字符串、整型和浮点型,并用`final`关键字定义常量。在银行账户管理程序案例中,变量跟踪账户信息,常量表示年利率。熟悉这些概念将提升编程技能。
|
10天前
|
Java 数据安全/隐私保护 Android开发
Java基础21-读懂Java序列化和反序列化(二)
Java基础21-读懂Java序列化和反序列化(二)
14 1
|
10天前
|
XML 存储 Java
Java基础21-读懂Java序列化和反序列化(一)
Java基础21-读懂Java序列化和反序列化(一)
12 1
|
2天前
|
Java 程序员 数据处理
【技能升级】JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
【6月更文挑战第21天】在Java中,URL是网络资源的位置标识,如`http://www.example.com/resource.txt`,而URLConnection是与这些资源交互的接口。创建URL对象后,通过`openConnection()`获取URLConnection实例以读取或写入资源。读取时,设置请求头,获取输入流并读取数据;写入(POST)时,设置输出流并写入数据。处理网络操作时,别忘了异常处理、使用连接池以优化性能、设置超时以及恰当使用请求头和响应头。这些最佳实践能助你高效、稳定地进行网络编程。
|
2天前
|
Java
Java 从入门到进阶之路(十九)
Java 从入门到进阶之路(十九)
|
2天前
|
安全 Java 网络安全
Java Socket编程技术详解:从基础到进阶的全方位指南
【6月更文挑战第21天】Java Socket编程是网络通信的关键,涉及`Socket`和`ServerSocket`类。基础教程展示了如何创建简单的客户端-服务端交互,而进阶内容涵盖了非阻塞I/O、多路复用(如使用`Selector`)以提升性能,以及通过SSL/TLS确保安全通信。学习Socket编程不仅是技术实践,也是理解网络原理的过程,强调了持续学习和实践的重要性。