最长公共子序列

假设您正在开发一个将对象自动保存到数据库中的框架。 您需要检测两次保存之间所做的更改,以便仅保存修改的字段。 如何检测脏场。 最简单的方法是遍历原始数据和当前数据,并分别比较每个字段。 代码如下:

public static void getDirtyFields(Object obj, Object obj2, Class cls, Map<String, DiffFields> diff)throws Exception {Field[] flds = cls.getDeclaredFields();for (int i = 0; i < flds.length; i++) {flds[i].setAccessible(true);Object fobj = flds[i].get(obj);Object fobj2 = flds[i].get(obj2);if (fobj.equals(fobj2)) continue;if (checkPrimitive(flds[i].getType())) {<!-- add to dirty fields -->continue;}Map<String, DiffFields> fdiffs = new HashMap<String, DiffFields>();getDirtyFields(fobj, fobj2, fobj.getClass(), fdiffs);<!-- add to dirty fields -->}if (cls.getSuperclass() != null)getDirtyFields(obj, obj2, cls.getSuperclass(), diff);}

上面的代码不能处理很多条件,例如null值,字段是集合,映射或数组等。但是,这给出了可以做什么的想法。 如果对象很小并且其中不包含太多层次结构,则效果很好。 当在巨大的层次结构对象中的变化很小时,我们必须一直遍历最后一个对象才能知道差异。 而且,使用equals可能不是检测脏字段的正确方法。 可能尚未实现等于,或者仅可以仅比较几个字段,所以没有进行真正的脏字段检测。 您必须遍历每个字段,而不论是否相等,直到您击中图元来检测脏字段为止。

在这里,我想谈谈检测脏场的另一种方法。 除了使用反射,我们还可以使用序列化来检测脏字段。 我们可以轻松地替换上面代码中的“等于”来序列化对象,并且仅当字节不同时才继续操作。 但这不是最佳选择,因为我们将多次序列化同一对象。 我们需要如下逻辑:

  • 序列化要比较的两个对象
  • 比较两个字节流时,检测要比较的字段
  • 如果字节值不同,则将该字段存储为不同
  • 收集所有不同的字段并返回

因此,一次遍历两个字节流可以生成不同的字段列表。 我们如何实现这种逻辑? 我们可以遍历序列化流并能够识别其中的字段吗? 我们要编写如下代码:

public static void main(String[] args) throws Exception {ComplexTestObject obj = new ComplexTestObject();ComplexTestObject obj2 = new ComplexTestObject();obj2._simple._string = "changed";//serialize the first object and get the bytesByteArrayOutputStream ostr = new ByteArrayOutputStream();CustomOutputStream str = new CustomOutputStream(ostr);str.writeObject(obj);str.close();byte[] bytes = ostr.toByteArray();//serialize the second object and get the bytesostr = new ByteArrayOutputStream();str = new CustomOutputStream(ostr);str.writeObject(obj2);str.close();byte[] bytes1 = ostr.toByteArray();       //read and compare the bytes and get back a list of differing fieldsReadSerializedStream check = new ReadSerializedStream(bytes, bytes1);Map diff = check.compare();System.out.println("Got difference: " + diff);}

地图应包含_simple._string,以便我们可以直接转到_string并对其进行处理。

解释序列化格式

有些文章解释了标准序列化字节流的外观。 但是,我们将使用自定义格式。 尽管我们可以读取标准的序列化格式,但是当类的结构已经被我们的类定义时,它就没有必要了。 我们将简化它,并更改序列化的格式以仅写入字段的类型。 字段的类型是必需的,因为类声明可以引用接口,超类等,而所包含的值可以是派生类型。

为了自定义序列化,我们创建了自己的ObjectOutputStream并覆盖了writeClassDescriptor函数。 现在,我们的ObjectOutputStream如下所示:

public class CustomOutputStream extends ObjectOutputStream {public CustomOutputStream(OutputStream str)throws IOException  {super(str);}@Overrideprotected void writeClassDescriptor(ObjectStreamClass desc)throws IOException  {<b>String name = desc.forClass().getName();writeObject(name);</b>String ldr = "system";ClassLoader l = desc.forClass().getClassLoader();if (l != null)  ldr = l.toString();if (ldr == null)  ldr = "system";writeObject(ldr);}
}

让我们编写一个简单的对象进行序列化,并查看字节流的外观:

public class SimpleTestObject implements java.io.Serializable {int _integer;String _string;public SimpleTestObject(int b)  {_integer = 10;_string = "TestData" + b;}public static void main(String[] args) throws Exception  {SimpleTestObject obj = new SimpleTestObject(0);FileOutputStream ostr = new FileOutputStream("simple.txt");CustomOutputStream str = new CustomOutputStream(ostr);str.writeObject(obj);str.close(); ostr.close();}
}

运行此类后,调用“ hexdump -C simple.txt”显示以下输出:

00000000  ac ed 00 05 73 72 74 00  10 53 69 6d 70 6c 65 54  |....srt..SimpleT|
00000010  65 73 74 4f 62 6a 65 63   74 74 00 27 73 75 6e 2e  |estObjectt.'sun.|
00000020  6d 69 73 63 2e 4c 61 75  6e 63 68 65 72 24 41 70  |misc.Launcher$Ap|
00000030  70 43 6c 61 73 73 4c 6f   61 64 65 72 40 33 35 63  |pClassLoader@35c|
00000040  65 33 36 78 70 00 00 00  0a 74 00 09 54 65 73 74  |e36xp....t..Test|
00000050  44 61 74 61 30                                                          |Data0|
00000055

按照本文中的格式,我们可以将字节跟踪为:

  • AC ED:STREAM_MAGIC。 指定这是一个序列化协议。
  • 00 05:STREAM_VERSION。 序列化版本。
  • 0×73:TC_OBJECT。 指定这是一个新对象。

现在我们需要阅读类描述符。

  • 0×72:TC_CLASSDESC。 指定这是一个新类。

类描述符是我们编写的,因此我们知道格式。 它已读取两个字符串。

  • 0×74:TC_STRING。 指定对象的类型。
  • 0×00 0×10:字符串的长度,后跟对象类型的16个字符,即SimpleTestObject
  • 0×74:TC_STRING。 指定类加载器
  • 0×00 0×27:字符串的长度,后跟类加载器名称
  • 0×78:TC_ENDBLOCKDATA,对象的可选块数据的结尾。
  • 0×70:TC_NULL,在结束块之后,表示没有超类的事实

此后,将写入类中不同字段的值。 我们的类_integer和_string中有两个字段。 因此我们有4个字节的_integer值,即0×00、0×00、0×00、0x0A,后跟一个格式为字符串的字符串

  • 0×74:TC_STRING
  • 0×00 0×09:字符串的长度
  • 9个字节的字符串数据

比较流并检测脏区

现在我们了解并简化了序列化格式,我们可以开始为流编写解析器并对其进行比较。 首先,我们为原始字段编写标准的读取函数。 例如,如下所示编写getInt以读取整数(示例代码中存在其他整数):

static int getInt(byte[] b, int off) {return ((b[off + 3] & 0xFF) << 0) +  ((b[off + 2] & 0xFF) << 8) +((b[off + 1] & 0xFF) << 16) + ((b[off + 0]) << 24);}

可以使用以下代码读取类描述符。

byte desc = _reading[_readIndex++]; //read TC_CLASSDESCbyte cdesc = _compareTo[_compareIndex++];switch (desc) {case TC_CLASSDESC: {byte what = _reading[_readIndex++];  byte cwhat = _compareTo[_compareIndex++]; //read the type written TC_STRINGif (what == TC_STRING) {String[] clsname = readString(); //read the field Type if (_reading[_readIndex] == TC_STRING) {what = _reading[_readIndex++];  cwhat = _compareTo[_compareIndex++];String[] ldrname = readString(); //read the classloader name}ret.add(clsname[0]);cret.add(clsname[1]);}byte end = _reading[_readIndex++]; byte cend = _compareTo[_compareIndex++]; //read 0x78 TC_ENDBLOCKDATA//we read again so that if there are super classes, their descriptors are also read//if we hit a TC_NULL, then the descriptor is readreadOneClassDesc(); }break;case TC_NULL://ignore all subsequent nulls while (_reading[_readIndex] == TC_NULL) desc = _reading[_readIndex++];while (_compareTo[_compareIndex] == TC_NULL) cdesc = _compareTo[_compareIndex++];break;}

在这里,我们读取第一个字节,如果它是TC_CLASSDESC,则读取两个字符串。 然后,我们继续阅读,直到达到TC_NULL。 还有其他条件要处理,例如TC_REFERENCE,它是对先前声明的值的引用。 可以在示例代码中找到。

注意:函数同时读取两个字节流(_reading和_compareTo)。 因此,他们两个总是指向下一个必须开始比较的点。 字节作为一个块读取,这确保即使存在值差异,我们也始终会从正确的位置开始。 例如,字符串块的长度指示直到读取的位置,类描述符的末尾指示直到读取的位置,依此类推。

我们尚未编写字段序列。 我们如何知道要阅读哪些字段? 为此,我们可以执行以下操作:

Class cls = Class.forName(clsname, false, this.getClass().getClassLoader());ObjectStreamClass ostr = ObjectStreamClass.lookup(cls);ObjectStreamField[] flds = ostr.getFields();

这为我们提供了序列化顺序的字段。 如果我们遍历flds,则将按照写入数据的顺序进行。 因此,我们可以如下进行迭代:

Map diffs = new HashMap();
for (int i = 0; i < flds.length; i++) {DiffFields dfld = new DiffFields(flds[i].getName());if (flds[i].isPrimitive()) { //read primitivesObject[] read = readPrimitive(flds[i]);if (!read[0].equals(read[1])) diffs.put(flds[i].getName(), dfld); //Value is not the same so add as different}else if (flds[i].getType().equals(String.class)) { //read stringsbyte nxtread = _reading[_readIndex++]; byte nxtcompare = _compareTo[_compareIndex++];String[] rstr = readString();if (!rstr[0].equals(rstr[1])) diffs.put(flds[i].getName(), dfld); //String not same so add as difference}
}

在这里,我仅说明了如何检查类中的原始字段是否存在差异。 但是,可以通过递归调用对象字段类型的相同函数,将逻辑扩展到子类。

可以在此处找到该博客要尝试的示例代码,该代码具有比较子类和超类的逻辑。 在这里可以找到更整洁的实现。

请注意。 此方法存在一些缺点:

  • 此方法只能使用可序列化的对象和字段。 暂态和静态字段之间没有差异。
  • 如果writeObject覆盖默认的序列化,则ObjectStreamClass不能正确反映序列化的字段。 为此,我们将不得不对这些类的读取进行硬编码。 例如,在示例代码中,存在对ArrayList的读取或使用并解析标准序列化格式。
参考: 在我们的JCG合作伙伴Raji Sankar的Reflections博客上, 使用序列化查找对象中的脏区。

翻译自: https://www.javacodegeeks.com/2013/11/using-serialization-to-find-dirty-fields-in-an-object.html

最长公共子序列

最长公共子序列_使用序列化查找对象中的脏字段相关推荐

  1. java对象序列化去掉字段_使用序列化查找对象中的脏字段

    java对象序列化去掉字段 假设您正在开发一个将对象自动保存到数据库中的框架. 您需要检测两次保存之间所做的更改,以便仅保存已修改的字段. 如何检测脏场. 最简单的方法是遍历原始数据和当前数据,并分别 ...

  2. 使用序列化查找对象中的脏字段

    假设您正在开发一个将对象自动保存到数据库中的框架. 您需要检测两次保存之间所做的更改,以便仅保存修改过的字段. 如何检测脏场. 最简单的方法是遍历原始数据和当前数据,并分别比较每个字段. 代码如下: ...

  3. java最长公共子序列_技术分享 | 最长公共子序列在比对工具的应用

    即使如何1 在实际工作中,我们常常要对输出的文本和数据进行比对:以取证大师为例,取证大师导出的取证结果数据量很容易达到上万条.这类数据特点除了数量级大外,其实数据结构很相近.即使我们以无以伦比的细致和 ...

  4. 最长公共子序列_Java恶意序列化背后的历史和动机

    最长公共子序列 与Java的序列化机制有关的问题已广为人知. Effective Java 1st Edition (第10章)和Effective Java 2nd Edition (第11章)的整 ...

  5. c语言最长公共子序列_序列比对(二十四)——最长公共子序列

    原创: hxj7 本文介绍如何求解两个字符串的最长公共子序列. 最长公共子序列问题 前文<序列比对(二十三)--最长公共子字符串>介绍了如何求解两个字符串的最长公共子字符串,本文将介绍如何 ...

  6. 数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串 (转)...

    作者:寒小阳 时间:2013年9月. 出处:http://blog.csdn.net/han_xiaoyang/article/details/11969497. 声明:版权所有,转载请注明出处,谢谢 ...

  7. python实现求解最长公共子序列LCS问题

    在实现论文<Automatically Generating Models for Botnet Detection>论文的算法中,用到了一个The longest commom subs ...

  8. 文本比较算法Ⅶ——线性空间求最长公共子序列的Nakatsu算法

    在参阅<A Longest Common Subsequence Algorithm Suitable for Similar Text Strings>(Narao Nakatsu,Ya ...

  9. 动态规划算法解最长公共子序列LCS问题

    动态规划算法解LCS问题 作者 July 二零一零年十二月三十一日 本文参考:微软面试100题系列V0.1版第19.56题.算法导论.维基百科. 第一部分.什么是动态规划算法 ok,咱们先来了解下什么 ...

最新文章

  1. bzoj 3598 [ Scoi 2014 ] 方伯伯的商场之旅 ——数位DP
  2. Mac 安装Android Studio
  3. redis之adlist.c
  4. java基本类型的面试,15、基本类型常见面试题
  5. 40%美国人付不起400美元意外开销,大家怎么看?
  6. HttpService远程校验
  7. Redis五种数据类型及应用场景
  8. 思源EMLOG文章页网址跳转插件V1.1
  9. hadoop生态系统学习之路(十)MR将结果输出到hbase
  10. 计算机技能大赛初赛主持稿,职业技能大赛开幕式主持词
  11. asp.net在发送邮件时出现服务器响应为: You are not authorized to send mail, authentication is required 解决方案...
  12. 上班一族“黑话”辞典大曝光
  13. ftp等远程登录工具的星号密码查看方法
  14. 教你怎么卸载Office最干净?
  15. 数据库存储大文本类型
  16. 矢量绘图工具 Ipe
  17. CTS测试中testCameraOrientationAlignedWithDevice项
  18. 腾讯Bugly工具介绍节选
  19. Git版本回退的两种方式及回退方式推荐
  20. java索引图片,索引从图片中提取像素数据的代码的错误

热门文章

  1. PADS原理图与PCB转到AD或者高版本PADS转出文件给低版本使用
  2. 小牛采购管理系统 bt
  3. [ISUX转译]iOS 8人机界面指南(一):UI设计基础
  4. 小白也能看懂的XML简介
  5. 如何在Mac上安装的Skype
  6. make问题:make[1] entering directory
  7. 建站之星网站迁移攻略
  8. matlab 怎么解欠定方程 有Warning:Rank deficient,rank=2 tol=4.6151e-015 (转百度知道)
  9. php sesstion,操作Session的PHP类
  10. 【和81】基于复杂价值链的合作价值界定(温微观察13-11)