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


    前 言
    🍉 作者简介:半旧518,长跑型选手,立志坚持写10年博客,专注于java后端
    ☕专栏简介:深入、全面、系统的介绍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();
        		}
        	}
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

        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");
        	}
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    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;
        	}
        
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
        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();
        		}
        	}
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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();
        		}
        	}
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.3 对象引用的序列化

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

    Student student = new Student("wz");
        Teacher t1 = new Teacher(student,"Chinese");
        Teacher t2 = new Teacher(student,"Math");
    
    • 1
    • 2
    • 3

    对象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();
        	}
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    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;
        	}
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    上面的代码重写了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();
        		}
        
        	}
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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);
        	}
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

        public class School implements Serializable {
        	public static School highSchool = new School();
        
        	private School() {
        	}
        
        	 public Object readResolve() {
        	 return highSchool;
        	 }
        
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11


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

    “工欲善其事,必先利其器”。要想成为工作上的高手,面试时的题霸,独步江湖,就必须拿到一份"武林秘籍"。
    在这里插入图片描述
    我个人强推牛客网:找工作神器|大厂java面经汇总|超全笔试题库

    推荐理由:
    1.刷题题库,题目特别全面,刷爆笔试再也不担心
    在这里插入图片描述
    链接: 找工作神器|大厂java面经汇总|超全笔试题库
    2.超全面试题、成体系、高质量,还有AI模拟面试黑科技
    在这里插入图片描述
    链接: 工作神器|大厂java面经汇总|超全笔试题库
    3.超多面经,大厂面经很多
    在这里插入图片描述
    4.内推机会,大厂招聘特别多
    在这里插入图片描述
    链接: 找工作神器|大厂java面经汇总|超全笔试题库
    5.大厂真题,直接拿到大厂真实题库,而且和许多大厂都有直接合作,题目通过率高有机会获得大厂内推资格。
    在这里插入图片描述
    链接: 找工作神器|大厂java面经汇总|超全笔试题库

  • 相关阅读:
    从零搭建嵌入式开发环境
    成本高、落地难、见效慢,开源安全怎么办?
    戴尔笔记本重装系统按f几进入
    SpringMvc日志打印被忽略输出问题分析(源码分析)
    第5章:组件的数据挂载方式
    【C++】了解模板--泛型编程基础
    Java NIO非阻塞I/O与传统阻塞式I/O的区别
    内存模型和线程规范
    线程安全(六)AQS 的工作原理
    QEMU 结构体对齐产生的问题
  • 原文地址:https://blog.csdn.net/qq_41708993/article/details/126003754