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

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


相关文章
|
2月前
|
Java
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
java小工具util系列5:java文件相关操作工具,包括读取服务器路径下文件,删除文件及子文件,删除文件夹等方法
92 9
|
14天前
|
人工智能 自然语言处理 Java
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
FastExcel 是一款基于 Java 的高性能 Excel 处理工具,专注于优化大规模数据处理,提供简洁易用的 API 和流式操作能力,支持从 EasyExcel 无缝迁移。
74 9
FastExcel:开源的 JAVA 解析 Excel 工具,集成 AI 通过自然语言处理 Excel 文件,完全兼容 EasyExcel
|
1月前
|
Java
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
java实现从HDFS上下载文件及文件夹的功能,以流形式输出,便于用户自定义保存任何路径下
92 34
|
2月前
|
Java
java 中 IO 流
Java中的IO流是用于处理输入输出操作的机制,主要包括字节流和字符流两大类。字节流以8位字节为单位处理数据,如FileInputStream和FileOutputStream;字符流以16位Unicode字符为单位,如FileReader和FileWriter。这些流提供了读写文件、网络传输等基本功能。
59 9
|
2月前
|
消息中间件 存储 Java
RocketMQ文件刷盘机制深度解析与Java模拟实现
【11月更文挑战第22天】在现代分布式系统中,消息队列(Message Queue, MQ)作为一种重要的中间件,扮演着连接不同服务、实现异步通信和消息解耦的关键角色。Apache RocketMQ作为一款高性能的分布式消息中间件,广泛应用于实时数据流处理、日志流处理等场景。为了保证消息的可靠性,RocketMQ引入了一种称为“刷盘”的机制,将消息从内存写入到磁盘中,确保消息持久化。本文将从底层原理、业务场景、概念、功能点等方面深入解析RocketMQ的文件刷盘机制,并使用Java模拟实现类似的功能。
48 3
|
2月前
|
Java 测试技术 Maven
Maven clean 提示文件 java.io.IOException
在使用Maven进行项目打包时,遇到了`Failed to delete`错误,尝试手动删除目标文件也失败,提示`java.io.IOException`。经过分析,发现问题是由于`sys-info.log`文件被其他进程占用。解决方法是关闭IDEA和相关Java进程,清理隐藏的Java进程后重新尝试Maven clean操作。最终问题得以解决。总结:遇到此类问题时,可以通过任务管理器清理相关进程或重启电脑来解决。
|
5月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
6月前
|
Java 大数据
解析Java中的NIO与传统IO的区别与应用
解析Java中的NIO与传统IO的区别与应用
|
4月前
|
Java 大数据 API
Java 流(Stream)、文件(File)和IO的区别
Java中的流(Stream)、文件(File)和输入/输出(I/O)是处理数据的关键概念。`File`类用于基本文件操作,如创建、删除和检查文件;流则提供了数据读写的抽象机制,适用于文件、内存和网络等多种数据源;I/O涵盖更广泛的输入输出操作,包括文件I/O、网络通信等,并支持异常处理和缓冲等功能。实际开发中,这三者常结合使用,以实现高效的数据处理。例如,`File`用于管理文件路径,`Stream`用于读写数据,I/O则处理复杂的输入输出需求。
270 12
|
5月前
|
Java 数据处理
Java IO 接口(Input)究竟隐藏着怎样的神秘用法?快来一探究竟,解锁高效编程新境界!
【8月更文挑战第22天】Java的输入输出(IO)操作至关重要,它支持从多种来源读取数据,如文件、网络等。常用输入流包括`FileInputStream`,适用于按字节读取文件;结合`BufferedInputStream`可提升读取效率。此外,通过`Socket`和相关输入流,还能实现网络数据读取。合理选用这些流能有效支持程序的数据处理需求。
62 2