【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;
       }
    }

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


相关文章
|
7月前
|
监控 Java API
现代 Java IO 高性能实践从原理到落地的高效实现路径与实战指南
本文深入解析现代Java高性能IO实践,涵盖异步非阻塞IO、操作系统优化、大文件处理、响应式网络编程与数据库访问,结合Netty、Reactor等技术落地高并发应用,助力构建高效可扩展的IO系统。
216 0
|
4月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
251 1
|
4月前
|
Java Unix Go
【Java】(8)Stream流、文件File相关操作,IO的含义与运用
Java 为 I/O 提供了强大的而灵活的支持,使其更广泛地应用到文件传输和网络编程中。!但本节讲述最基本的和流与 I/O 相关的功能。我们将通过一个个例子来学习这些功能。
223 1
|
4月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
267 1
|
6月前
|
Java 测试技术 API
Java IO流(二):文件操作与NIO入门
本文详解Java NIO与传统IO的区别与优势,涵盖Path、Files类、Channel、Buffer、Selector等核心概念,深入讲解文件操作、目录遍历、NIO实战及性能优化技巧,适合处理大文件与高并发场景,助力高效IO编程与面试准备。
|
7月前
|
存储 Java Linux
操作系统层面视角下 Java IO 的演进路径及核心技术变革解析
本文从操作系统层面深入解析Java IO的演进历程,涵盖BIO、NIO、多路复用器及Netty等核心技术。分析各阶段IO模型的原理、优缺点及系统调用机制,探讨Java如何通过底层优化提升并发性能与数据处理效率,全面呈现IO技术的变革路径与发展趋势。
161 2
|
6月前
|
SQL Java 数据库连接
Java IO流(一):字节流与字符流基础
本文全面解析Java IO流,涵盖字节流、字符流及其使用场景,帮助开发者理解IO流分类与用途,掌握文件读写、编码转换、异常处理等核心技术,通过实战案例提升IO编程能力。
|
7月前
|
监控 Java API
Java语言按文件创建日期排序及获取最新文件的技术
这段代码实现了文件创建时间的读取、文件列表的获取与排序以及获取最新文件的需求。它具备良好的效率和可读性,对于绝大多数处理文件属性相关的需求来说足够健壮。在实际应用中,根据具体情况,可能还需要进一步处理如访问权限不足、文件系统不支持某些属性等边界情况。
358 14
|
8月前
|
XML JSON Go
Go语言中的文件与IO:JSON、CSV、XML处理
本文介绍了 Go 语言中对 JSON、CSV 和 XML 三种常见数据格式的处理方法。通过标准库 `encoding/json`、`encoding/csv` 和 `encoding/xml`,可以实现结构体与数据格式之间的序列化与反序列化。JSON 适合 Web API 和前后端通信,因其清晰易读;CSV 适用于表格数据和轻量级交换;XML 则支持复杂嵌套结构,常用于配置文件和 SOAP 协议。文中提供代码示例,涵盖基本使用、嵌套结构处理及实战建议,帮助开发者高效操作这些格式。
|
8月前
|
JSON Java 数据库连接