条件随机场java代码 Java怎么随机生成数字的代码

代码反平庸吗?

代码反混淆(deobfuscation)和代码混淆(obfuscation)对应,是其逆过程。维基百科将代码混淆定义为故意生成人类难以理解的源代码或机器码的过程("In software development, obfuscation is the deliberate act of creating source or machine code that is difficult for humans to understand.")。代码反混淆可以理解为将原本人类难以理解的代码转化为简单的、可理解的、直观的代码的过程。

创新互联专注于凤台网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供凤台营销型网站建设,凤台网站制作、凤台网页设计、凤台网站官网定制、微信小程序开发服务,打造凤台网络公司原创品牌,更为您提供凤台网站排名全网营销落地服务。

这篇文章主要介绍一下

"Big Code" 在代码反混淆领域的应用。更具体一点就是介绍一下提出 "JSNice" 和 "Deguard"

的两篇文章,这两篇文章虽然已经发表快五年了,但至今没有文章Follow这两份工作,因为文章已经将使用 "Big Code"

做代码命名反混淆做到了极致。后来的人无法在这个问题上推陈出新,脱颖而出。

"Big Code": 代码托管网站如GitHub上的大量免费可用的高质量代码被称为 "Big Code" ,这些数据结合统计推理或深度学习为新兴的开发工具的出现提供了契机。

概率图模型:概率图模型是用图来表示变量概率依赖关系的理论,结合概率论与图论的知识,利用图来表示与模型有关的变量的联合概率分布。

问题

为了项目的安全,开发者在打包发布项目时会对代码进行混淆加密,包括但不限于用无意义的短变量去重命名类、变量、方法,以免代码被轻易破解泄露。另外由于JS脚本主要用于Web开发,对其进行混淆还能压缩脚本的大小,使得浏览器下载、加载更加快速,提升用户的浏览体验。

这一类通过对类、变量、方法重命名的混淆方案确实能加大其他开发者对代码的理解难度。其他开发者不干了,为了能方便理解他人混淆后的代码,学习(抄袭)他人的经验,针对这一类混淆方法的反混淆方法也应运而生。

下面先展示一下安卓APP的代码混淆技术:

图1. Java程序的代码混淆过程

经过混淆的代码在功能上是没有变化的,但是去掉了部分名称中的语义信息。因为种种限制,这类混淆也不可能对所有的名称都进行替换。上图中的SQLiteHelper、SQLiteDatabase和Cursor就是一个证明,这些名称都是安卓API,如果将这些类名混淆会影响代码的功能。

理论上一个有经验的安卓开发者可以在这些有限的提示下为所有的名称找到富含语义的表示,所以反混淆只需要一个有经验的开发者(有经验的开发者:???我做错了什么)。退一步,找不到有经验的开发者怎么办,没关系,GitHub有高质量的各种项目,现训练一个有经验的开发者也行。不过为了人道主义,消灭996剥削,程序员表示可以用程序代替人,正好可以用GitHub数据训练一个程序做这个反混淆嘛!理论存在,实践开始。

JSNice[1]

用程序代替人其实并不简单,针对上图中的反混淆问题,程序需要具有“联想”和“推理”能力,比如从a

extends

SQLiteHelper这一句中,a应该很可能也是Helper类,结合类中有SQLiteDatabase实例推理出比较符合a的语义的类名是DBHelper。

针对以上两点,程序需要先有关系的概念,能从一个词“联想”到另一个词,然后还要有推理的能力,能通过约束从几个候选词中找到最符合的那个词。怎么做这个事呢?虽然有很多的未混淆的JS脚本供程序学习,但在反混淆JS脚本时,程序无法理解复杂的JS脚本,所以需要将JS脚本表示成一种可以利用已知属性推理未知属性的结构,JSNice采用了概率图模型。

概率图模型相比其他学习算法的优势在于可以利用图结构将已知信息带入知识网络,在使用概率图模型之前,往往要求图结构是已知的。现实中我们没有这些先验知识,但是有大量的样本(未混淆代码)。通过样本学习出未混淆JS脚本的概率图就是JSNice的核心。

具体到JSNice,这个工具想要做的是为JS脚本推测名称(name)和类型(type)。先通过一张图看看JSNice的推理过程。

图2. 一段推理出名称和类型的JavaScript程序及推理名称的推理过程

由于JSNice推理名称和推理类型的过程类似,本文就只阐述推理名称的过程。

1. 确定已知和未知属性

JS脚本中包含了各类代码元素(elements)比如变量,常量,类,方法名,域等。对推理名称问题,元素的属性即带有语义的名称,一个需要推理名称的元素,称其属性未知。不需要推理名称的元素其属性已知。首先需要确定JS脚本中的元素属性是否已知。很容易看出图2(a)代码中属性已知的元素包括:常量0和[]、feild名称length和方法名称push,其他的局部变量如e,t,n,r和i的属性都是未知的。

将问题泛化,如何判断任意的JS脚本中的任意元素的属性是否已知是需要解决的第一个问题。JSNice采用程序分析和人工指定的方式确定元素属性是否已知,简单来说,JSNice认为JS脚本中的常量(constants)、对象属性名(objects

properties)、方法名(methods)和全局变量名(global

variables)都是属性已知的元素,而所有的局部变量的属性都是未知的。值得注意的是JSNice将对象属性名称和API名称直接看做是常量。这个划分是否合理暂不去讨论,但是其确实适用与大部分的JS脚本。有兴趣的读者可以自行研究。

2. 建立依赖网络

第一步获取了JS脚本中的所有元素(属性已知或未知),接下来需要建立元素之间的关系,好方便后续的推理;图2(c)中简单的给出了一些关系,比如"var r = e.length"中可以得到(r, length, L=_.R)的关系。

JSNice实际考虑的元素关系十分复杂,主要有三类,这里简单的进行描述:

句法关系,这一类关系主要通过AST得到,例子如下:

图3. (a) 语句i + j amp;amp;amp;amp;amp;lt; k的AST. (b) 根据AST建立用于名称预测的依赖网络. (c) 用于类型预测的网络.

这类关系的形式化定义如下

别名关系,这类关系通过standard alias analysis得到,将方法调用时传入的arguments和方法声明时的parameters进行关联,形成ARG_TO_PM关系。

函数名称关系,由于JS的语言特性,很多的局部变量本身是方法定义,MAY_CALL和MAY_ACCESS表示一个方法的可能调用方法关系和可能访问对象域关系,这类关系可以通过program analysis得到。

类型推理采用的关系有所不同,但这里也不再详述,有兴趣的读者直接移步原文。

3. 训练和推理

现在整理一下对某个JS脚本x进行上述两步分析能得到什么?得到了一个依赖网络  ,其中  为节点集,分别表示未知(unknow)属性元素和已知属性元素。  为边集,表示两个程序元素之间的关系以及关系类型。

现在不去考虑训练的过程,直接看一下推理过程,JSNice采用贪婪算法,对未知属性元素遍历其所有的属性可能,寻找到使score最大的属性作为结果。

图4. 建立网络寻找未知属性元素y的最佳属性的模式

具体的算法如下:

JSNice在具体实现时对算法有所优化,但是其基本思想和主要框架都没变。其实对比前文提到人进行反混淆时需要的“联想”和“推理”两种能力,candidates函数担负了“联想”的重任,利用scoreEdges函数对不同的候选属性计算score并选择最大score对应属性的过程就是”推理“。

将图2的推理部分摘出来看:

r的candidates有len和length,t的candidates有step、j和q,i的candidates只有i。推理得到的(r、i、t)的属性是(len、i、step)而不是(length、i、step);是因为前者的综合score是0.4+0.8+0.5=1.7,而后者的综合score只有0.5+0.6+0.5=1.6。

那么怎么得到scoreEdges和candidates函数呢?

JSNice定义了一个条件随机场:

x为给定某个JS脚本,y为未知属性元素(复数)的任意分配属性,score为指示属性y和脚本x的匹配程度的函数,其返回值为实数,值越大越匹配。

Z是对应JS脚本x的一个惩罚系数,用来保证其Pr和为1

将score定义为k个特征函数的加权平均,得到

最终条件随机场的表示形式为:

写到这里,出现了第一个问题,score为k个特征函数的加权平均,如何确定k呢?

JSNice是在训练阶段的预处理步骤得到k的,实际上不仅仅这一步不仅获得了k,还直接定义了k个 pairwise feature functions  。

往前回一步,本文前面一直说GitHub有未混淆的代码可供概率图模型学习,这里定义训练集  由t个未混淆的JS脚本组成。对  中的任意  元组,JSNice定义其特征的集合为

整个训练集的所有特征集为

JSNice直接定义pairwise feature functions为每个特征三元组

的指示函数:

所以训练集有多少特征三元组,k的值就有多大。

但说了这么多,还是没有提到scoreEdges和candidates。别急,直接定义

如此把前面的公式都串起来了,整个公式组其实只有  是未知,条件随机场的训练过程其实就是计算  的过程。

请点击输入图片描述

至于candidates,假设现在概率图模型中的  已经训练完成,根据前面的定义,  和  其实是一一对应的,  本身是特征三元组的指示函数,也和三元组一一对应,所以可以使用权重  直接限制节点  的可能取值。定义  函数对输入的特征三元组集合基于此权重返回top s的三元组。定义  。定义如下辅助函数:

最后:

candidates函数其实就是先在  中找到和  有  关系的  ,然后利用  和  在训练集中找到和  最相似的词作为候选。比较方便的是辅助函数其实可以在预测之前提前计算并缓存下来。

由于笔者本身没有不研究概率图模型,所以训练模型得到  的内容就省略了,有兴趣的读者可以阅读原文[1],本文只简单的描述:

JSNice采用判别式训练,由于最大似然需要计算  ,JSNice采用max-margin training,使用Structured Support Vector Machine (SSVM)并用scalable subgradient descent algorithm优化。

请点击输入图片描述

Deguard[2]

相对JSNice做的对JS脚本进行反混淆,Deguard对安卓APK做反混淆的难度要大了很多,放在眼前的一个问题就是项目规模,JSNice的应用scope其实是Web上的JS脚本,考虑网站的加载等限制,单个JS脚本必不会太大,而安卓APK不同,由于安卓本身事件驱动的编程方式,一个简单的安卓APK的复杂度可能就能比得上十分复杂的JS脚本。并且安卓APK的大小一般也没有限制。

还有一些安卓或者说Java需要的约束是建模时需要考虑的,比如一个Java类中的feilds名称必须不一样,一个package中的classes名称必须不一样。不满足这些约束,对APK进行反混淆的结果就失去了其实用性。

考虑到安卓application的复杂性,选取合适的粒度建模是首要的问题,关系过于复杂不利于概率图模型的学习,关系过于简略可能导致无法预测准确的属性,必须有一个权衡。

1. 确定程序元素(图的节点  )

要为安卓APK定义一个依赖图,首先确定图上的节点,Deguard考虑了APK中的如下元素:

Types,不管是基本类型(int, long, float),引用类型(Object, ArrayList),还是数组类型(int[], Object[])都能做为节点

Feilds,APK中的任意类中定义的任意Feild都能做为节点

Packages,APK中的任意package能做为节点,像package a.b就可以做为两个节点,a和a.b。

Methods,APK中的任意方法都能做为节点,但是如果有overrides关系,那么子类和父类的method公用一个节点表示。

Constant values and null

Access Modifiers,比如static,synchronized

Operations

这里没有考虑Java语言中的泛型机制是因为在编译过程中会消除泛型,APK本身就是编译过后的文件。另外和JSNice不一样的是,Deguard没有考虑局部变量名和参数名,但考虑了局部变量的类型和参数的类型,一方面减少规模,另一方面就是变量名和参数名本身不在APK中。

2. 确定元素属性是否已知

这里将元素属性定义为节点是否被混淆,属性已知说明节点名称未被混淆,不需要预测名称,属性未知说明节点名称已被混淆,需要预测名称。

已知属性元素包括

节点如果代表pakages,classes,methods和feilds且在安卓API中,那么这些节点是已知的,因为基于名称的混淆工具并不会混淆这些名称

构造函数名称都是已知的

节点代表对安卓API方法重写的方法也是已知的,因为前文已经提过,重写关系其实是用一个节点表示。

剩下的元素都是未知的,需要预测名称

3. 构建依赖网络

Deguard用一张图详细描述了其用于构建依赖网络的关系

图5. 安卓application采用的关联程序元素的关系,第二列定义了边的类型,如果满足第三列提出的条件,一个关系就成立

相对JSNice中大部分的关系来自AST,Deguard选择的关系明显融合进了人的经验,更加的抽象。实际上本文的精华也是这一张图,某种程度上这图中展现出来了人类对具体问题具体分析的思考,而不是仅仅简单的复用已有工作提出的各种关系。

剩下的内容比如模型的训练和推理其实和JSNice差不多,这里不会重复一遍,后续会讲不一样的地方,也就是Deguard如何处理Java程序带来的关于各种名称的约束。

Java中的名称约束还是比较复杂的,这里拿一个例子讲一下:

图6. 展示方法命名显示的例子

A.a(A)没有任何约束,因为A作为参数的方法只有它一个

A.b(Object)不能被重命名为equals,因为这样会override java.lang#

B.g()和B.h()方法重命名名称必须不一样,因为这两方法的特征一样

B.g()和A.c()方法重命名名称必须不一样,如果一样就是隐式的继承

B.h()和A.c()方法重命名名称必须不一样,也是由于method overriding的原因

C.x()和B.h()却没有冲突,因为B.h()是private的

相等约束(继承重写机制)可通过共用节点表示,不等约束也需要明确表示,所以Deguard提出了一个检测方法名称不等约束的算法

其他元素,比如类名,Feilds名称的不等约束比较简单,直接处理就行。

所有不等约束以集合  表示,  ,  中任意两个节点的名称必须不一样。

注意这个约束只用与预测阶段,因为训练数据(未混淆)本身满足这些约束。很容易可以把这些约束结合到JSNice的算法1中。

Deguard的概率图优化算法和JSNice也不一样,采用的是pseudo likelihood estimation。具体阐述推荐阅读文章[3]。

值得注意的是,为什么JSNice就没有Deguard中提到的相等约束和不等约束,笔者个人认为还是由问题和语言特性共同决定,JSNice的名称预测其实只预测了局部变量,而JS的语言特性导致其本身不需要检测局部变量的名称冲突,只有执行结果报错才会说明程序出错。也就是说其实JS本身语言特性就没有这类约束,自然不需要建模。

Java代码如何优化

1. 尽量在合适的场合使用单例

使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:

第一,控制资源的使用,通过线程同步来控制资源的并发访问;

第二,控制实例的产生,以达到节约资源的目的;

第三,控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。

2. 尽量避免随意使用静态变量

要知道,当某个对象被定义为stataic变量所引用,那么gc通常是不会回收这个对象所占有的内存

3. 尽量避免过多过常的创建Java对象

尽量避免在经常调用的方法,循环中new对象,由于系统不仅要花费时间来创建对象,而且还要花时间对这些对象进行垃圾回收和处理,在我们可以控制的范围内,最大限度的重用对象,最好能用基本的数据类型或数组来替代对象。

4. 尽量使用final修饰符

带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String.为String类指定final防止了使用者覆盖length()方法。另外,如果一个类是final的,则该类所有方法都是final的。Java编译器会寻找机会内联(inline)所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%.

5. 尽量使用局部变量

调用方法时传递的参数以及在调用中创建的临时变量都保存在栈(Stack)中,速度较快。其他变量,如静态变量、实例变量等,都在堆(Heap)中创建,速度较慢。

6. 尽量处理好包装类型和基本类型两者的使用场所

虽然包装类型和基本类型在使用过程中是可以相互转换,但它们两者所产生的内存区域是完全不同的,基本类型数据产生和处理都在栈中处理,包装类型是对象,是在堆中产生实例。

在集合类对象,有对象方面需要的处理适用包装类型,其他的处理提倡使用基本类型。

7. 慎用synchronized,尽量减小synchronize的方法

都知道,实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。synchronize方法被调用时,直接会把当前对象锁 了,在方法执行完之前其他线程无法调用当前对象的其他方法。所以synchronize的方法尽量小,并且应尽量使用方法同步代替代码块同步。

8. 尽量使用StringBuilder和StringBuffer进行字符串连接

这个就不多讲了。

9. 尽量不要使用finalize方法

实际上,将资源清理放在finalize方法中完成是非常不好的选择,由于GC的工作量很大,尤其是回收Young代内存时,大都会引起应用程序暂停,所以再选择使用finalize方法进行资源清理,会导致GC负担更大,程序运行效率更差。

10. 尽量使用基本数据类型代替对象

String str = "hello";

上面这种方式会创建一个"hello"字符串,而且JVM的字符缓存池还会缓存这个字符串;

String str = new String("hello");

此时程序除创建字符串外,str所引用的String对象底层还包含一个char[]数组,这个char[]数组依次存放了h,e,l,l,o

11. 单线程应尽量使用HashMap、ArrayList

HashTable、Vector等使用了同步机制,降低了性能。

12. 尽量合理的创建HashMap

当你要创建一个比较大的hashMap时,充分利用另一个构造函数

public HashMap(int initialCapacity, float loadFactor)

避免HashMap多次进行了hash重构,扩容是一件很耗费性能的事,在默认中initialCapacity只有16,而loadFactor是 0.75,需要多大的容量,你最好能准确的估计你所需要的最佳大小,同样的Hashtable,Vectors也是一样的道理。

13. 尽量减少对变量的重复计算

并且在循环中应该避免使用复杂的表达式,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。

14. 尽量避免不必要的创建

15. 尽量在finally块中释放资源

程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。

16. 尽量使用移位来代替'a/b'的操作

"/"是一个代价很高的操作,使用移位的操作将会更快和更有效

17.尽量使用移位来代替'a*b'的操作

同样的,对于'*'操作,使用移位的操作将会更快和更有效

18. 尽量确定StringBuffer的容量

StringBuffer 的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再 丢弃旧的数组。在大多数情况下,你可以在创建 StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。

19. 尽量早释放无用对象的引用

大部分时,方法局部引用变量所引用的对象 会随着方法结束而变成垃圾,因此,大部分时候程序无需将局部,引用变量显式设为null.

20. 尽量避免使用二维数组

二维数据占用的内存空间比一维数组多得多,大概10倍以上。

21. 尽量避免使用split

除非是必须的,否则应该避免使用split,split由于支持正则表达式,所以效率比较低,如果是频繁的几十,几百万的调用将会耗费大量资源,如果确实需 要频繁的调用split,可以考虑使用apache的StringUtils.split(string,char),频繁split的可以缓存结果。

22. ArrayList LinkedList

一 个是线性表,一个是链表,一句话,随机查询尽量使用ArrayList,ArrayList优于LinkedList,LinkedList还要移动指 针,添加删除的操作LinkedList优于ArrayList,ArrayList还要移动数据,不过这是理论性分析,事实未必如此,重要的是理解好2 者得数据结构,对症下药。

23. 尽量使用System.arraycopy ()代替通过来循环复制数组

System.arraycopy() 要比通过循环来复制数组快的多

24. 尽量缓存经常使用的对象

尽可能将经常使用的对象进行缓存,可以使用数组,或HashMap的容器来进行缓存,但这种方式可能导致系统占用过多的缓存,性能下降,推荐可以使用一些第三方的开源工具,如EhCache,Oscache进行缓存,他们基本都实现了FIFO/FLU等缓存算法。

25. 尽量避免非常大的内存分配

有时候问题不是由当时的堆状态造成的,而是因为分配失败造成的。分配的内存块都必须是连续的,而随着堆越来越满,找到较大的连续块越来越困难。

26. 慎用异常

当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。当需要创建一个 Exception 时,JVM 不得不说:先别动,我想就您现在的样子存一份快照,所以暂时停止入栈和出栈操作。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。

如 果您创建一个 Exception ,就得付出代价。好在捕获异常开销不大,因此可以使用 try-catch 将核心内容包起来。从技术上讲,您甚至可以随意地抛出异常,而不用花费很大的代价。招致性能损失的并不是 throw 操作--尽管在没有预先创建异常的情况下就抛出异常是有点不寻常。真正要花代价的是创建异常。幸运的是,好的编程习惯已教会我们,不应该不管三七二十一就 抛出异常。异常是为异常的情况而设计的,使用时也应该牢记这一原则。

(1)。 用Boolean.valueOf(boolean b)代替new Boolean()

包装类的内存占用是很恐怖的,它是基本类型内存占用的N倍(N2),同时new一个对象也是性能的消耗。

(2)。 用Integer.valueOf(int i)代替new Integer()

和Boolean类似,java开发中使用Integer封装int的场合也非常多,并且通常用int表示的数值都非常小。SUN SDK中对Integer的实例化进行了优化,Integer类缓存了-128到127这256个状态的Integer,如果使用 Integer.valueOf(int i),传入的int范围正好在此内,就返回静态实例。这样如果我们使用Integer.valueOf代替new Integer的话也将大大降低内存的占用。

(3)。 用StringBuffer的append方法代替"+"进行字符串相加。

这个已经被N多人说过N次了,这个就不多说了。

(4)。 避免过深的类层次结构和过深的方法调用。

因为这两者都是非常占用内存的(特别是方法调用更是堆栈空间的消耗大户)。

(5)。 变量只有在用到它的时候才定义和实例化。

这是初学者最容易犯的错,合理的使用变量,并且只有在用到它的时候才定义和实例化,能有效的避免内存空间和执行性能上的浪费,从而提高了代码的效率。

(6)。 避免在循环体中声明创建对象,即使该对象占用内存空间不大。

这种情况在我们的实际应用中经常遇到,而且我们很容易犯类似的错误

采用上面的第二种编写方式,仅在内存中保存一份对该对象的引用,而不像上面的第一种编写方式中代码会在内存中产生大量的对象引用,浪费大量的内存空间,而且增大了垃圾回收的负荷。因此在循环体中声明创建对象的编写方式应该尽量避免。

(7)。 如果if判断中多个条件用'||'或者''连接,请将出现频率最高的条件放在表达式最前面。

这个小技巧往往能有效的提高程序的性能,尤其是当if判断放在循环体里面时,效果更明显。

1.JVM管理两种类型的内存:堆内存(heap),栈内存(stack),堆内在主要用来存储程序在运行时创建或实例化的对象与变量。而栈内存则是用来存储程序代码中声明为静态(static)(或非静态)的方法。

2.JVM中对象的生命周期,创建阶段,应用阶段,不可视阶段,不可到达阶段,可收集阶段,终结阶段,释放阶段

3.避免在循环体中创建对象,即使该对象点用内存空间不大。

4.软引用的主要特点是具有较强的引用功能。只有当内存不够的时候,才回收这类内存,因此在内存足够的时候,它们通常不被回收。它可以用于实现一些常用资源的缓存,实现Cache的功能

5.弱引用对象与Soft引用对象最大不同就在于:GC在进行回收时,需要通过算法检查是否回收Soft引用对象,而对于Weak引用对象,GC总是进行回收。

6.共享静态变量存储空间

7.有时候我们为了提高系统性能,避免重复耗时的操作,希望能够重用一些创建完成的对象,利用对象池实现。类似JDBC连接池。

8.瞬间值,序列化对象大变量时,如果此大变量又没有用途,则使用transient声明,不序列化此变量。同时网络传输中也不传输。

9.不要提前创建对象

10 .(1)最基本的建议就是尽早释放无用对象的引用

A a = new A();

a = null; //当使用对象a之后主动将其设置为空

(2)尽量少用finalize函数。

(3) 如果需要使用经常用到的图片展,可以使用软引用。

(4) 注意集合数据类型,包括数组,树等数据,这些数据结构对GC来说,回收更为复杂,

(5) 尽量避免在类的默认构造器中创建,初始化大量的对象,防止在调用其自类的构造器时造成不必要的内存资源浪费。

(6) 尽量避免强制系统做垃圾内存回收。

(7) 尽量避免显式申请数组空间。

(8) 尽量在合适的场景下使用对象池技术以提高系统性能,缩减系统内存开销。

11.当做数组拷贝操作时,采用System.arraycopy()方法完成拷贝操作要比采用循环的办法完成数组拷贝操作效率高

12. 尽量避免在循环体中调用方法,因为方法调用是比较昂贵的。

13. 尽量避免在循环体中使用try-catch 块,最好在循环体外使用try--catch块以提高系统性能。

14. 在多重循环中,如果有可能,尽量将最长的循环放在最内层,最短的循环放在最外层,以减少循环层间的变换次数。

15. 在需要线程安全的情况下,使用List list = Collections.synchronizedList(new ArrayList());

16. 如果预知长度,就设置ArrayList的长度。

17. ArrayList 与 LinkedList 选择,熟悉底层的实现原理,选择适当的容器。

18. 字符串累加采用StringBuffer.

19. 系统I/O优化,采用缓冲和压缩技术。优化性能。

20. 避免在类在构造器的初始化其他类

21 尽量避免在构造中对静态变量做赋值操作

22. 不要在类的构造器中创建类的实例

23. 组合优化继承

24. 最好通过Class.forname() 动态的装载类

25. JSP优化,采用out 对象中的print方法代替println()方法

26 .采用ServletOutputStream 对象代替JSPWriter对象

27. 采用适当的值初始化out 对象缓冲区的大小

28. 尽量采用forward()方法重定向新的JSP

29. 利用线程池技术处理客户请求

30.Servlet优化

(1) 通过init()方法来缓存一些静态数据以提高应用性能。

(2) 用print() 方法取代println()方法。

(3) 用ServletOutputStream 取代 PrintWriter.

(4) 尽量缩小同步代码数量

31. 改善Servlet应用性能的方法

(1)不要使用SingleThreadModel

(2)使用线程池ThreadPool

32. EJB优化

实体EJB:

(1)实体EJB中常用数据缓存与释放

(2)采用延迟加载的方式装载关联数据

(3)尽可能地应用CMP类型实体EJB

(4)直接采用JDBC技术处理大型数据

33. 优化JDBC连接

(1)设置合适的预取行值

(2)采用连接池技术

(3)全合理应用事务

(4)选择合适的事务隔离层与及时关闭连接对象

34. PreparedStatemetn只编译解析一次,而Statement每次都编译解析。

35. 尽可能地做批处理更新

36. 通过采用合适的getXXX方法提高系统性能

37. 采用设计模式。

条件随机场的条件随机域(场)

条件随机域(场)(conditional random fields,简称 CRF,或CRFs),是一种判别式概率模型,是随机场的一种,常用于标注或分析序列资料,如自然语言文字或是生物序列。

如同马尔可夫随机场,条件随机场为具有无向的图模型,图中的顶点代表随机变量,顶点间的连线代表随机变量间的相依关系,在条件随机场中,随机变量 Y 的分布为条件机率,给定的观察值则为随机变量 X。原则上,条件随机场的图模型布局是可以任意给定的,一般常用的布局是链结式的架构,链结式架构不论在训练(training)、推论(inference)、或是解码(decoding)上,都存在效率较高的算法可供演算。

“条件随机场”被用于中文分词和词性标注等词法分析工作,一般序列分类模型常常采用隐马尔可夫模型(HMM),像基于类的中文分词。但隐马尔可夫模型中存在两个假设:输出独立性假设和马尔可夫性假设。其中,输出独立性假设要求序列数据严格相互独立才能保证推导的正确性,而事实上大多数序列数据不能被表示成一系列独立事件。而条件随机场则使用一种概率图模型,具有表达长距离依赖性和交叠性特征的能力,能够较好地解决标注(分类)偏置等问题的优点,而且所有特征可以进行全局归一化,能够求得全局的最优解。

经典概率模型和条件随机场

由上图中可知, 1):贝叶斯模型(NB)和隐马尔科夫模型(HMM)都属于求取联合概率的模型,而最大熵模型(ME)和条件随机场模型(CRF)则是求取条件概率模型。2):贝叶斯模型和最大熵模型是针对单个标签输出的模型,而隐马尔科夫模型和CRF则是序列模型。

我们建模的目的是根据输入的特征x,获得最有可能的输出标签y。

其中x代表输入特征。

每个输出标签y的概率值可简单统计训练数据的频率即可获得。接下来最终我们需要计算的子项是P(y|x)

当我们需要根据模型计算序列化标签时,可简单改造贝叶斯模型,即

每个输入x对应一个输出y,并且 序列输出标签y之间保持独立 ,这是一个较强的假设,现实应用中很难保证该假设。而假设时序标签y之间有时序上的依赖关系,这是一个很合理的假设,因此有

由该公式,可导出HMM的公式为

由信息论中条件熵定义

最大熵模型的基本思想是寻找最大条件熵的同时,保持和训练数据信息一致。

其中p(x)由经验分布可近似为:

训练数据由特征进行表征,特征f_i的期望值由经验分布P(x,y)计算可得,经验分布概率可由变量不同值统计频率计算而得。我们建模的希望能达到的是经验分布的期望值等于实际模型分布的期望值,即有

由约束条件

,根据经典的解优化方法,拉格朗日函数可得

求解拉格朗日等式可得

最大熵马尔科夫模型是序列化的最大熵模型,最大熵模型(ME)以P(y|x)建模,单次输入对应单个输出标签y。在序列标签预测任务时,基于最大熵模型,并考虑标签的位置信息,即得最大熵马尔科夫模型(MEMM)。

由上式可以看出来, 模型采用局部归一化,但是局部归一化容易陷入局部最优,而得不到全局最优解 。

概率无向图模型,又称为马尔科夫随机场。

由图可知,最大团为(v1,v2,v4)和(v2,v3,v4)。

概率无向图的联合概率分布P(Y)可由所有最大团C上的势函数的乘积表示

势函数(potential functions)可以是任意函数,因此势函数不必是概率函数,最终为了得到合适的概率度量,需要对最大团乘积进行归一化。

最大熵模型条件概率为:

其势函数为

加权特征的指数形式被广泛采用,因为它满足了势函数严格为正的要求。

条件随机场根据条件概率建模

由无向图的定义,联合概率分布P(Y)可由最大团C上的势函数的乘积计算可得,因此

由概率无向图的联合概率定义可得,其势函数为

最终

模型训练,由最大似然函数计算,有

CRF模型推理,1):前向-后向算法;2):维特比算法(viterbi)。

《Classical Probabilistic Models and Conditional Random Fields》

条件随机场在自然语言处理中怎么用

条件随机场模型是Lafferty于2001年,在最大熵模型和隐马尔科夫模型的基础上,提出的一种判别式概率无向图学习模型,是一种用于标注和切分有序数据的条件概率模型;

条件随机场模型作为一个整句联合标定的判别式概率模型,同时具有很强的特征融入能力,是目前解决自然语言序列标注问题最好的统计模型之一。条件随机场的缺点是训练的时间比较长。

条件随机场定义

设G=(V,E)是一个无向图,Y=(Yv),,Y表示图中顶点的结合。如果在观察变量X的条件下,在图G中随机变量Yv服从马尔科夫属性,即:表示在图G中,v,w是邻居,那么(X,Y)就表示一个条件随机场。

即它是在给定需要标记的观察序列 X的条件下计算整个标记序列 Y的联合概率分布,而不是在给定当前状态条件下定义下一个状态的分布。公式如下所示:

HMM.vs MEMM .vs CRF

隐马尔可夫模型中存在两个假设:输出独立性假设和马尔可夫性假设。其中,输出独立性假设要求序列数据严格相互独立才能保证推导的正确性,而事实上大多数序列数据不能被表示成一系列独立事件。而条件随机场则使用一种概率图模型,条件随机场没有隐马尔可夫模型那样严格的独立性假设条件,因而可以容纳任意的上下文信息,可以灵活地设计特征。同时,条件随机场具有表达长距离依赖性和交叠性特征的能力,而且所有特征可以进行全局归一化,能够求得全局的最优解,还克服了最大熵马尔可夫模型标记偏置的缺点。

下图是用图的形式描述HMM(左),MEMM(中),链式的CRF(右)表示序列时的区别。注(空心圆表示此变量是不用于描述模型的)。其中HMM中,Yi只与Yi-1有关,而Xi输出只与Yi有关。在MEMM中,Yi 是由Xi和Yi-1确定的,在CRF中,确定某个Yi,它会考虑整个Y及Xi的。

希望我的回答可以帮到您哦


网站名称:条件随机场java代码 Java怎么随机生成数字的代码
本文地址:http://azwzsj.com/article/hisisi.html