JAVA IO前言Comparable & Comparator & 序列化Serializable & 反序列化Deserializable

2023-11-02

Comparable可认为是内比较器(可比较的),是接口类,类参数为泛型对象T,通常对比的类本身需实现继承Comparable接口类的唯一方法compareTo(T o),对比指标为类的一个或多个属性,对比类与Comparable接口类耦合性强,Comparable接口类源代码如下:

public interface Comparable<T> {
 	public int compareTo(T o);
}
  类对象通常在实现继承Comparable接口类后,配合java.util.Collections.sort(T[] arr)或java.util.Arrays.sort(List<T> list)来实现排序。
  JDK常见使用:如基本数据类型String,Integer implements java.io.Serializable, Comparable<T>以及File implements java.io.Serializable, Comparable<File>

 Comparator可认为是是外比专用比较器,是接口类,类参数为泛型对象T,通过编写独立的排序算法类继承Comparator接口类,而对比类本身不做任何继承,保证对比类与Comparator接口类无耦合接口类部分源代码如下:

public interface Comparator<T> {
    int compare(T o1, T o2);
    boolean equals(Object obj);
    .......
}

  类对象通常在独立的排序算法类实现继承Comparator接口类后,配合java.util.Collections.sort(T[] arr, Comparator<? super T> c)java.util.Arrays.sort(List<T> list, Comparator<? super T> c)来实现排序。常见使用习惯(代码片段):

StudentComparator implements Comparator<Student> {
   @Override
   public int compare(Student o1, Studento2) {
	  if (o1.getScore() > o2.getScore()) return -1;
	  else if (o1.getScore() < o2.getScore()) return 1;
	  else {
               if (o1.getAge() > o2.getAge()) return 1;
	       else if (o1.getAge() < o2.getAge()) return -1;
	       else return 0;
	   }
   }	
}
java.util.Arrays.sort(Student[] stuArrs,new StudentComparator());
java.util.Collections.sort(List<Student> sList,new StudentComparator());

序列化Serializable 和反序列化Deserializable

概念:把对象(内存中)转换为字节序列的过程称为对象的序列化;把字节(码)序列恢复为对象的过程称为对象的反序列化。

主要用途:1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中 2)在网络上传送对象的字节序列

    **磁盘数据存储格式或网络间数据存储传输格式都基于 字节 **

序列化 ID

  序列化 ID 是否一致确定了类反序列化是否正确默认long serialVersionUID = 1L),序列化保存的是对象的状态,不能保存类的状态,序列化静态变量不保存,直接从内存中取数据验证片断代码:

public class TestSerializable implements Serializable {
	private static final long serialVersionUID = 1L;
	public  static String staticVar = "static";
	public  String name = "cj";
        public static void main(String[] args) {
        TestSerializable m = new TestSerializable();
		 m.setName("cm");
		 ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));
		 out.writeObject(m);
		 out.close(); 
		  TestSerializable mupdage = new TestSerializable();
		  mupdage.staticVar = "staticUpdate";
		  mupdage.setName("cmUpdate");
		  ObjectInputStream oin = new ObjectInputStream(new FileInputStream( "result.obj")); 
		  TestSerializable  t = (TestSerializable) oin.readObject();
		  oin.close(); 
		  System.out.println(t.staticVar +" "+ t.getName());
        }
   }
   输出:staticUpdate cm

  原因:类普通属性值通过反序列化转化后的中对象获取值,而静态变量属性值从内存中获取

Transient[临时的] 关键字:特殊定义变量,阻止该变量被序列化,在被反序列化后,transient 变量的值被设为初始值

对敏感字段加密 

  情境:服务器端给客户端发送序列化对象数据,对象中敏感数据在序列化时需要进行加密,比如密码字符串等,客户端在拥有解密的密钥,且进行反序列化时,才可以对密码进行读取,这样可一定程度保证序列化对象的数据安全。
  解决:在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。验证片断代码:

 public class User implements Serializable {
     private static final long serialVersionUID = 1L;
     private String password = "pass";//省略GET、SET
     private void writeObject(ObjectOutputStream out) {
     try {
        PutField putFields = out.putFields();
        System.out.println("原密码:" + password);
        password = "encryption";//模拟加密
        putFields.put("password", password);
        System.out.println("加密后的密码" + password);
        out.writeFields();
        } catch (IOException e) { e.printStackTrace();}
     }

     private void readObject(ObjectInputStream in) {
    try {
        GetField readFields = in.readFields();
        Object object = readFields.get("password", "");
        System.out.println("要解密的字符串:" + object.toString());
        password = "pass";//模拟解密,需要获得本地的密钥
  }   catch (IOException e) { e.printStackTrace(); } 
       catch (ClassNotFoundException e) { e.printStackTrace(); }   
      }

 public static void main(String[] args) {
     ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.obj"));
         out.writeObject(new User());  out.close();
        ObjectInputStream oin = new ObjectInputStream(new FileInputStream( "result.obj"));
       User t = (User) oin.readObject();
        System.out.println("解密后的字符串:" + t.getPassword());   oin.close();
      }
}
  使用案例 :RMI技术是完全基于 Java序列化技术的,服务器端接口调用所需要的参数对象来至于客户端,它们通过网络相互传输。这就涉及RMI 的安全传输的问题。一些敏感的字段,如用户名密码(用户登录时需要对密码进行传输),我们希望对其进行加密,这时,就可以采用本节介绍的方法在客户端对密 码进行加密,服务器端进行解密,确保数据传输的安全性

序列化存储规则

     Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,增加 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间,如下代码片断:

    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result2.obj"));
        TestSerializable testS = new TestSerializable();
	testS.setName("1");
        out.writeObject(testS);
	out.flush();
        System.out.print(new File("result2.obj").length() +" ");
        testS.setName("2");
	out.writeObject(testS);
	System.out.print(new File("result2.obj").length()+" ");
	out.close();
	oin2 = new ObjectInputStream(new FileInputStream("result2.obj"));
	TestSerializable t1 = (TestSerializable) oin2.readObject();
	TestSerializable t2 = (TestSerializable) oin2.readObject();
	System.out.print(t1 == t2);
	System.out.println(" " +t1.getName() +" " + t2.getName());
输出:72 77 true 1 1

原理分析

  序列化方式一调用ObjectOutputStreamwriteObject方法序列化对象,将其写入磁盘,再次调用readObject时,根据wirteObject方法磁盘文件重新恢复对象

  序列化方式二Externalizable接口扩展Serializable,并增添了两个方法:writeExternal()readExternal()。在序列化和重新装配的过程中,会自动调用两个方法

方式一执行的详细如下

1)ObjectOutputStream的构造函数设置enableOverride = false

public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
    bout = new BlockDataOutputStream(out);
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);
    enableOverride = false;
    writeStreamHeader();
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
}

2)外部调用ObjectOutputStream.writeObject(序列化类对象)方法执行writeObject0(obj, false);

public final void writeObject(Object obj) throws IOException {
    if (enableOverride) {
        writeObjectOverride(obj);
        return;
    }
    try {
        writeObject0(obj, false);
    } catch (IOException ex) {
        if (depth == 0) {
            writeFatalException(ex);
        }
        throw ex;
    }
}

writeObject0(obj, false)重要代码片断:

// remaining cases
if (obj instanceof String) {
    writeString((String) obj, unshared);
} else if (cl.isArray()) {
    writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
    writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
    writeOrdinaryObject(obj, desc, unshared);
} else {
    if (extendedDebugInfo) {
        throw new NotSerializableException(
            cl.getName() + "\n" + debugInfoStack.toString());
    } else {
        throw new NotSerializableException(cl.getName());
    }
}

 从上可以看出,如果对象没有实现Serializable接口,在序列化的时候会抛出NotSerializableException异常

跟踪writeOrdinaryObject(obj, desc,unshared)方法代码片断:

desc.checkSerialize(); 
bout.writeByte(TC_OBJECT);
writeClassDesc(desc, false);
handles.assign(unshared ? null : obj);
if (desc.isExternalizable() && !desc.isProxy()) {
    writeExternalData((Externalizable) obj);
} else {
    writeSerialData(obj, desc);
}

  在检查Serialize后,如果对象实现Externalizable接口,执行writeExternalData((Externalizable) obj)方法如果实现的是Serializable接口,那么执行的是writeSerialData(obj, desc);

  首先看writeSerialData方法,主要执行方法:defaultWriteFields(obj, slotDesc);

private void writeSerialData(Object obj, ObjectStreamClass desc)
    throws IOException
{
    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
    for (int i = 0; i < slots.length; i++) {
        ObjectStreamClass slotDesc = slots[i].desc;
        if (slotDesc.hasWriteObjectMethod()) {
            PutFieldImpl oldPut = curPut;
            curPut = null;
            SerialCallbackContext oldContext = curContext;

            if (extendedDebugInfo) {
                debugInfoStack.push(
                    "custom writeObject data (class \"" +
                    slotDesc.getName() + "\")");
            }
            try {
                curContext = new SerialCallbackContext(obj, slotDesc);
                bout.setBlockDataMode(true);
                slotDesc.invokeWriteObject(obj, this);
                bout.setBlockDataMode(false);
                bout.writeByte(TC_ENDBLOCKDATA);
            } finally {
                curContext.setUsed();
                curContext = oldContext;
                if (extendedDebugInfo) {
                    debugInfoStack.pop();
                }
            }

            curPut = oldPut;
        } else {
            defaultWriteFields(obj, slotDesc);
        }
    }
}

  slotDesc.hasWriteObjectMethod()检查序列化类是否存在自定义的writeObject(ObjectOutputStream outputStream),存在则执行 slotDesc.invokeWriteObject(obj,this);通过反射去执行自定义的writeObject(ObjectOutputStream outputStream)方法,否则执行默认的defaultWriteFields(obj, slotDesc),若执行默认的defaultWriteFields(obj, slotDesc),通过writeObject0循环将类属性写入文件中。代码片断:

private void defaultWriteFields(Object obj, ObjectStreamClass desc){
desc.checkDefaultSerialize();
    Object[] objVals = new Object[desc.getNumObjFields()];
    int numPrimFields = fields.length - objVals.length;
    desc.getObjFieldValues(obj, objVals);
for (int i = 0; i < objVals.length; i++) {
writeObject0(objVals[i], fields[numPrimFields + i].isUnshared());
....
}
}

 再次看一下writeExternalData的方法,重要代码如下:

private void writeExternalData(Externalizable obj) throws IOException {
try {
    curContext = null;
    if (protocol == PROTOCOL_VERSION_1) {
        obj.writeExternal(this);
    } else {
        bout.setBlockDataMode(true);
        obj.writeExternal(this);
        bout.setBlockDataMode(false);
        bout.writeByte(TC_ENDBLOCKDATA);
    }}

  obj.writeExternal(this)序列化接口类(interfaceExternalizable对象writeExternal方法,故必须在自定义的序列化类中重载实现writeExternal方法,即方式二执行过程。

反序列化

1) objectInputStream.readObject()方法执行readObject0(false)方法:主要代码:

switch (tc) {
                case TC_NULL:
                    return readNull();
              .....
                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));
                case TC_ARRAY:
                    return checkResolve(readArray(unshared));
                case TC_ENUM:
                    return checkResolve(readEnum(unshared));
                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));
                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);
                case TC_BLOCKDATA:
                .....
                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }

 根据不同的对象类型做相应的处理,这里我们关注的是TC_OBJECT,执行的方法是:checkResolve(readOrdinaryObject(unshared));接着看readOrdinaryObject(unshared)执行了以下代码:

 ObjectStreamClass desc = readClassDesc(false);
 desc.checkDeserialize();
 Object obj;
      try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
       } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
......
       if (desc.isExternalizable()) {
           readExternalData((Externalizable) obj, desc);
       } else { readSerialData(obj, desc); }

  ObjectStreamClass默认构造函数:

private ObjectStreamClass(final Class<?> cl) {
    if (serializable) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    .....
                    suid = getDeclaredSUID(cl);//获取UID
                    try {
                        fields = getSerialFields(cl);//获取SerialFields
                        computeFieldOffsets();
                    } ......
                    if (externalizable) {
                        cons = getExternalizableConstructor(cl);
                    } else {
                        cons = getSerializableConstructor(cl);
//定义系列ObjectMethod
writeObjectMethod = getPrivateMethod(cl, "writeObject",
                            new Class<?>[] { ObjectOutputStream.class },
                            Void.TYPE);
                        readObjectMethod = getPrivateMethod(cl, "readObject",
                            new Class<?>[] { ObjectInputStream.class },
                            Void.TYPE);
                        readObjectNoDataMethod = getPrivateMethod(
                            cl, "readObjectNoData", null, Void.TYPE);
                        hasWriteObjectData = (writeObjectMethod != null);
                    }
                    ......
              });
          } 
}

 序列化类实现Externalizable 接口执行readExternalData((Externalizable)obj, desc);,实现Serializable接口则执行readSerialData(obj,desc);首先先看readSerialData(obj,desc);代码片断:

    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
        for (int i = 0; i < slots.length; i++) {
            ObjectStreamClass slotDesc = slots[i].desc;
            if (slots[i].hasData) {
                if (obj == null || handles.lookupException(passHandle) != null) {
                    defaultReadFields(null, slotDesc); // skip field values
                } else if (slotDesc.hasReadObjectMethod()) {
               		 ......
                        slotDesc.invokeReadObject(obj, this);
                    }   ......
                } else { defaultReadFields(obj, slotDesc); }
            }
  if (obj != null && slotDesc.hasReadObjectNoDataMethod() && handles.lookupException(passHandle) == null)
            {slotDesc.invokeReadObjectNoData(obj);} 
       }

  序列化类中若存在ReadObject方法,则执行类中ReadObject方法,否则obj为空或默认情况下执行defaultReadFields方法。

其次看readExternalData((Externalizable)obj, desc);跟踪如以下主要代码片断:

private void readExternalData(Externalizable obj, ObjectStreamClass desc){...obj.readExternal(this);...}

obj.readExternal(this)序列化接口类(interfaceExternalizable对象readExternal方法,故必须在自定义的序列化类中重载实现readExternal方法,即方式二执行过程。



























本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA IO前言Comparable & Comparator & 序列化Serializable & 反序列化Deserializable 的相关文章

  • ModelSim的入门仿真步骤(图文干货)

    ModelSim仿真分为以下6个主要步骤 1 新建工程 2 新建或导入工程文件 3 文档编译 4 开始仿真 5 添加仿真波形 6 调整仿真时间 1 新建工程 点击左上角 File Project 弹出如下界面 在 Project Name
  • qt提升控件之后,编译报错

    引言 自定义的控件 在ui文件中将控件提升为自定义的控件 提升的时候没有指明提升的头文件的相对路径或者绝对路径 导致编译的时候无法找到相应的头文件 解决方法 1 在被提升的类的头文件前添加本机电脑所在的相对路径 2 在被提升的类的头文件前添

随机推荐

  • leveldb常见问题以及性能优化点

    本篇是leveldb最后一篇 这里主要把技术核心点 性能提升点或者面试可能会被问到进行总结 一 常见问题 1 leveldb key value内存 内存中保存的是所有key value吗 答 不是 搜索顺序 memtable immtab
  • 利用MATLAB中的newrb函数进行函数逼近

    RBF函数在神经网络控制中较为常见 MATLAB中早已集成了一个newrb的函数 在一些场景下使用起来还比较方便 尤其是涉及到进行函数逼近的时候 参考链接 http blog sina com cn s blog 9b8d0abd0101o
  • Android Studio连接自带模拟器失败怎么办?强烈建议使用第三方模拟器(含各类模拟器下载地址)

    学习安卓开发的小伙伴必然会碰到模拟器的启动问题 就算成功启动Android Studio自带的模拟器 使用起来也是十分缓慢 有时候卡起来是真想骂娘 服了他个老六 那么这个时候 如果有一个第三方模拟器 不仅简单好用 无需配置 而且美观快速零卡
  • 华为存储特性

    华为存储特性 1 SmartPartition SmartPartition是一种性能特性 根据不同业务特点分配存储系统的缓存资源来满足不同应用系统的服务质量要求 由于单个存储系统要面对的应用数量急剧增加 且各业务应用之间的I O特征不同
  • Android-第五节Menu菜单详解

    目录 一 Menu背景 二 Menu使用步骤 1 创建menu 2 设计menu 3 重写menu逻辑代码 4 运行效果 一 Menu背景 手机毕竟和电脑不同 它的屏幕空间非常有限 因此充分地利用屏幕空间在手机界面设计中就显得非常重要了 如
  • STM32 使用TIM2_CH1(PA15) 输出10K PWM信号

    PA15 gt TIM2 Remap CH1 1 apb init RCC APB1PeriphClockCmd RCC APB1Periph TIM2 ENABLE RCC APB2PeriphClockCmd RCC APB2Perip
  • JMM java内存模型

    java内存模型 即 java memory model 它定义了主存 工作内存抽象概念 底层对应着 CPU 寄存器 缓存 硬件内存 CPU 指令优化等 JMM 体现在以下几个方面 原子性 保证指令不会受到线程上下文切换的影响 可见性 保证
  • android 安装命令,【转载】android中APK包的安装以及adb命令的使用

    1 首先将 android sdk platform tools添加都path路径下 2 在控制台窗口中进入到你apk包所在的目录中 3 输入 abd unremount 第一次使用该命令的时候需要获取该命令的操作权限 4 打开androi
  • Filter、Interceptor的使用

    Filter Interceptor的使用 一 Filter Interceptor的区别 1 1 过滤器 Filter 1 2 拦截器 Interceptor 二 Filter Interceptor的使用 2 1 Filter 的实现步
  • 微信临时文件wxfile://tmp文件处理,微信小程序最新获取头像和昵称

    欢迎点击领取 前端面试题进阶指南 前端登顶之巅 最全面的前端知识点梳理总结 分享一个使用比较久的 技术栈 taro框架 vue3版本 解决在微信小程序获取微信头像时控制台报错 找不着wxfile tmp 文件路径 失败 原因如下 因为微信提
  • JS合并数组对象中key相同的数据(将数组里某个属性相同的对象合并成一个数组)

    将数组里某个属性相同的对象合并成一个数组 原数组如下 let resData name 住院医疗最高报销 tagName 医疗 insuredAmount 6000 name 身故赔付 tagName 寿险 insuredAmount 36
  • 80后创业故事之:兄弟散伙,创业失败

    在开始今天的故事之前 首先感谢这么多朋友关注我的文章 实在让我受宠若惊 我定倍加珍惜大家对我的支持 同时我也留意到这些天有一些朋友对我的故事也提出了一些质疑 怀疑是否杜撰 编造或者太过夸大其辞 本来我想出说一箩筐的理由来证明我所说的都是我自
  • AIGC参数量节节攀升,对存储带来的挑战如何解决?

    引言 近期 AIGC 相关产品如同雨后春笋一般不断涌现 但在技术层面 大家普遍的关注点更多集中在性能方面 却经常忽略了存储对推理和训练效率的影响 以及 AIGC 内容可能带来的合规风险 我们特邀腾讯云存储的产品负责人 崔剑老师和益企研究院创
  • 性能自动化+locust

    性能自动化 locust 仅作为个人笔记 如有雷同 请联系删除 性能测试基础 1 性能测试相关概念 性能测试 测试软件的性能表现 考量软件运行的如何 一般关注时间 效率 资源占用等情况 响应时间 应用系统从用户发出请求开始 到客户端接收到所
  • 你想知道的大数据知识都在这里

    欢迎大家前往腾讯云 社区 获取更多腾讯海量技术实践干货哦 毋庸置疑 现如今是属于大数据 Big Data 的 革命性的时代 从社交媒体到企业 每时每刻都在产生大量的数据 无所作为 从而把这样的宝藏白白浪费掉是及其愚蠢的 企业已经学会了收集大
  • OV7670配置和调试总结

    废话后面说 先直接上OV7670寄存器的配置部分 const uint8 t OV7670 Reg 2 Frame Rate Adjustment for 24Mhz input clock 30fps PCLK 24MHz 0x11 0x
  • tomcat部署时注意修改目录路径

    原文 https blog csdn net qq 15676547 article details 81208991 每次发布war包 都会把附件给覆盖了 为了解决这个问题 我们需要把目录给修改到别处 方法如下
  • SSH远程访问AWS EC2

    步骤1 启动Amazon EC2实例 视频演示 启动AWS EC2实例 导航到Amazon EC2并开始启动新实例 在本教程中 我将使用Amazon Linux 2 AMI HVM 操作系统 创建一个没有规则的新安全组 例如MediumSG
  • 利用Selenium(爬虫)爬取物流信息,并用邮件提醒自己物流更新

    受疫情影响 快递无法全面复工 商家在过年期间又压了一堆未发货的订单 现在下单一个快递 商家迟迟无法发货 就算发了货 物流也慢的跟蜗牛一样 每天就是打开淘宝看物流信息 物流信息又没更新 关淘宝 为了节省这些时间 不在焦虑中度过 写了这么一个功
  • JAVA IO前言Comparable & Comparator & 序列化Serializable & 反序列化Deserializable

    Comparable可认为是内比较器 可比较的 是接口类 类参数为泛型对象T 通常对比的类本身需实现继承Comparable接口类的唯一方法compareTo T o 对比指标为类的一个或多个属性 对比类与Comparable接口类耦合性强