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

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方法,即方式二执行过程。



























  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java中,ComparableComparator都是用于进行对象比较的接口。它们的用法如下: 1. Comparable接口 Comparable接口是Java内置的接口,它包含一个方法compareTo(),用于比较对象的大小。实现该接口的类可以直接进行排序。 例如,我们定义一个Person类实现Comparable接口: ``` public class Person implements Comparable<Person> { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public int compareTo(Person person) { // 按照年龄进行排序 return this.age - person.age; } } ``` 在这个例子中,我们通过实现Comparable接口,并重写compareTo()方法,按照年龄进行排序。 使用Comparable接口进行排序的例子: ``` List<Person> list = new ArrayList<Person>(); list.add(new Person("Tom", 20)); list.add(new Person("Jerry", 18)); list.add(new Person("Jack", 25)); Collections.sort(list); for(Person p : list) { System.out.println(p.getName() + " " + p.getAge()); } ``` 输出结果: ``` Jerry 18 Tom 20 Jack 25 ``` 2. Comparator接口 Comparator接口也是Java内置的接口,它包含一个方法compare(),用于比较两个对象的大小。实现该接口的类可以定制不同的比较规则。 例如,我们定义一个PersonComparator类实现Comparator接口: ``` public class PersonComparator implements Comparator<Person> { public int compare(Person p1, Person p2) { // 按照姓名进行排序 return p1.getName().compareTo(p2.getName()); } } ``` 在这个例子中,我们通过实现Comparator接口,并重写compare()方法,按照姓名进行排序。 使用Comparator接口进行排序的例子: ``` List<Person> list = new ArrayList<Person>(); list.add(new Person("Tom", 20)); list.add(new Person("Jerry", 18)); list.add(new Person("Jack", 25)); Collections.sort(list, new PersonComparator()); for(Person p : list) { System.out.println(p.getName() + " " + p.getAge()); } ``` 输出结果: ``` Jack 25 Jerry 18 Tom 20 ``` 总之,ComparableComparator都是用于对象比较的接口。使用Comparable接口可以方便地对实现该接口的对象进行排序,而使用Comparator接口可以定制不同的比较规则。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值