安全编程技术,其本质是要编写安全的程序。但是,由于安全是一个广泛的概念,除了在功能上需要能够不出现隐患外,在性能上也需要能够阻止隐患出现的可能。特别是在某些情况下,性能优化显得格外重要。如果程序性能不好,也可能导致某些方面的安全问题。因此,性能优化是保证程序安全的一个重要方面。
1.数据优化
一般来说,由于局部变量用完之后释放,因此有些作用范围较大的变量操作,可改为局部变量来实现,有助于节省宝贵的系统资源。代码如下:
上述代码是求1~1000的和,变量sum作为类成员变量,在循环中对其进行反复读取,由于对局部变量进行读取,消耗资源较少,因此,可以将这个读取过程交给局部变量去做,变更代码如下:
(2)优化字符串
由于字符串的特殊性和灵活性,字符串的优化应用较广。首先,由于字符串的池机制,字符串的初始化(分配内存过程)可以优化。代码如下:
String str=new String("Apple");
上述代码中,系统实例化一个新的对象str,为其分配内存空间。但是由于字符串使用了池机制,可以将上面的代码优化如下:
String str="Apple";
此代码中,系统首先检查池中有无“Apple”,如果有,系统将直接使用池中的字符串,而不用重分配内存空间。
值得一提的是,在对多个字符串进行操作或对一个字符串进行修改时,用StringBuffer比用String要好。代码如下:
str3将保存strl和str2连接在一起的结果,系统将为str3额外分配内存。为了避免这个额外的资源消耗,代码可以优化如下:
这样,就不需要为两个字符串连接的结果额外分配内存。
(3)选择合适的数据结构
在实际的开发过程中,选择一种合适的数据结构很重要。例如,有一堆随机存放的数据,如果经常在其中进行插入和删除操作,使用链表较好;如果要经常进行读取,并且数据个数固定,则使用数组较好。这里需要注意的是,在高级语言中,大部分语言虽然提供了同样功能的API,但是底层实现机制不同,操作性能大不相同,这不是从表面就可以看出来的。如Java语言中:
●ArrayList和LinkedList,提供了功能类似的API,如对元素的增添删除、修改、查询。但是前者采用数组方式存储数据,后者采用链表方式存储数据,在进行数据大量添加或删除时效果不一样。
●ArrayList和Vector,后者实现了线程同步,在没有线程要求时适合用前者,因为速度较快;多个线程访问同一个Vector时适合用后者,因为可以保证数据安全。
又如,在C语言中,数组与指针语句具有十分密切的关系,一般来说,指针的好处是比较灵活、简洁,而数组则比较直观,容易理解。与数组索引相比,指针一般能使代码速度更快,占用空间更少。另外,对于大部分的编译器,使用指针比使用数组生成的代码更短,执行效率更高。这种情况下,使用多维数组时差异更明显。
下面的代码作用是相同的,但是效率不同。
2.算法优化
(1)优化基本运算
很多代码都可以进行优化,其中最常见的是对乘法和除法的优化。观察下面的代码:
此处如果使用移位来代替乘法运算,可以使性能提高。重写的代码如下:
此外,整数除法是整数运算中最慢的,所以应该尽可能避免。对于连除,有时可以由乘法代替。以下是不推荐使用的代码:
推荐使用的代码如下:
(2)优化流程
流程主要包括以下两类:选择和循环。
1)选择结构的优化。
在选择结构的语句中,可以利用一些手段提高运行性能,如充分将可能性大的分支写在前面、充分利用短路判断运算符等。如下代码是根据学生的分数判断其等级:
该程序中,根据学校以往的统计经验,如果学生不能通过的概率较大,那么elseif分支就可以调到前面去。
另外,短路运算符有时也可以提高性能。如if(条件1&&条件2),可以将不成立概率较大的条件放在前面;又如if(条件1||条件2),可以将成立概率较大的条件放在前面。
在用if判断某些值是否相等时,尽量将变量作为比较的对象。如if(a==3)改成if(3==a)更好,这是为了消除程序员将“==”写成“=”造成的安全隐患。
在选择流程的嵌套上,代码会按照顺序进行比较,匹配时就跳转到满足条件的语句上执行。可以对嵌套可能的值依照发生的可能性进行排序,把最有可能的放在第一位,这样可以提高效率。
综上所述,当if-else语句中的分支很多时,为了减少比较的次数,明智的做法是把多分支if-else语句转为嵌套if-else语句。把发生频率高的情况放在一个if语句中,并且是嵌套if-else语句的最外层,发生频率相对低的情况放在另一个if语句中。
2)循环结构的优化。
循环的特点是可能反复执行一些代码,因此,在循环中,有很多可以优化的地方。优化得好,可以大大提高系统性能。代码如下:
该代码中,循环内i<v.size()语句;会反复执行,系统会重复计算v的大小。因此,这段代码可以优化。优化方法是可以让系统只调用v.size()一次。代码如下:
3.应用优化
(1)优化异常处理
异常处理给开发程序带来较大的方便,但是因为一个异常抛出首先需要创建一个新的对象,所以异常处理需要消耗底层资源。因此,Exception会降低系统性能。在异常操作的过程中,有时候可以对其进行优化,代码如下:
该代码相当于抛出NullPointerException时处理非正常操作。但是,该代码也可写成如下形式:
比较上述两段代码,从可读性和安全性上来讲,第一段代码比较好;但是从性能上,却是第二段代码比较好。因此,在实际开发的过程中,需要仔细权衡。一般来说,异常在需要抛出的地方抛出,try-catch能整合就整合。注意,不到万不得已,不要在循环中使用异常捕捉块。代码如下:
应该改为:
(2)线程同步中的优化
由于线程的同步可能造成系统性能的降低,因此,关于线程同步的操作,要注意以下几个方面:
●能够不用同步的地方就不要用同步,在程序中避免使用过多的同步。如果将不必要同步的代码块同步,同步的安全性优势不但没有体现出来,反而会造成程序性能的下降。因此,如果程序是单线程或者在多线程中不需要同步代码段,就一定不要使用同步代码块。
●同步的范围尽量小一些。很明显,如果同步代码范围大,则在较大的范围内只能被一个线程独占,性能降低的程度较大,因此,同步的范围应该尽量小一些。一般情况下,如果可以对某个方法或函数进行同步,就尽量不要对整个代码段进行同步。
4.数据库的优化
(1)设计上的优化
在数据库设计上,应适当采用相应措施,可以大大提高访问效率。
1)尽量给表设置主键与外键。
很多数据库允许数据表不设置主键,即使表中实体有主键和外键关系,也允许不设置外键。但是,从查询性能优化上讲,一个实体不能既无主键又无外键,因为很多与索引有关的操作都要基于主键与外键来进行。实际上,主键是实体的高度抽象,外键表达了实体之间的某种对应关系。主键与外键的配对表示了实体之间的连接。
2)适当降低范式标准,以空间换取时间。
一般来说,表及其字段之间的关系,应尽可能满足第三范式。但是,为了提高数据库的运行效率,有时可以降低范式标准,适当增加一些冗余,提高查询性能,以空间换时间。
3)将多对多关系分解成一对多关系。
在数据库设计的过程中,一对多情况下的设计比较容易,多对多情况下的设计相对复杂一些。若实体之间存在多对多的关系,就可以将其转化为若干个一对多关系,简化设计。
以最简单的两个实体之间的多对多关系为例,可以在两者之间增加第三个实体:关系实体。原来的两个实体都和这个关系实体发生联系。换句话说,原来多对多的关系转变为两个一对多的关系。(www.xing528.com)
4)科学地进行主键取值。
在数据库中,主键唯一确定一条记录,这也是表间连接和索引建立的依据。主键可以由如下方法赋值:
●某个唯一确定记录的列,如学生表中的学号。
●好几个列的组合,如选课表中的课程编号和学生学号的组合。
●一个无物理意义的数字串,当增加一个行时,程序自动给一个新串。
一般来说,建议采用第3种做法。很明显,第3种做法花费的空间较少,在生成索引时,占用空间小,查询速度快。不过,如果一定要用字段组合或者使用单独字段作为主键,字段个数最好不要太多,或者选用宽度较小的字段作为主键。
5)适当利用视图来保证数据安全性。
视图是一种虚表,本身并不存储数据,依赖于实际的表而存在,是实际表的一种映像。可以通过以下手段来设计视图:
●不将数据表中的保密数据显示在视图中,而将非保密数据在视图中公布,源表不对用户开放,只开放视图。
●在有必要的情况下,可以设置多层视图。特别是在一些权限系统中,如果用户可以查询同一个表中的列,权限越大的用户可以查询的列数越多,这种情况下,就可以首先基于源表创建第一层视图,公布的列数较多,面向权限最大的用户;然后在第一层视图的基础上建立第二层视图,公布的列数少一些,面向权限相对较小的用户。
6)适当使用“列变行”技术,减少不必要的数据冗余,提高性能。
实际上,一个表的列个数越少越好。将列数变少,是减少不必要的数据冗余的重要手段。
(2)SQL语句优化
从编程的角度讲,对数据库的访问主要是对数据库数据的操作,如添加、删除、修改和查询等。由于添加、删除和修改操作,主要还是要基于查询,因此,数据库访问上的优化主要指查询上的优化。一般来说,开发人员的查询工作多用SQL语句来实现。这里基于Oracle数据库,来介绍一些和SQL语句优化有关系的方案。
1)SELECT子句中尽量不使用“*”,而用列名替代。
在Oracle数据库中,解析SQL语句时,会将“*”转换成表中所有的列名,该工作意味着另一次查询,具有一定的时间损耗。
2)充分利用内部函数来提高SQL语句的效率。
很多SQL语句的功能可以用内部函数来实现,内部函数往往实现了优化,因此,如果能够使用的话,尽量使用内部函数。
3)在查询过程中,尽量使用表别名。
在SQL语句多表查询中,可以不给源表指定别名,但是推荐使用表的别名,并在每个列前加上别名,减少解析时间。当然,这种方法也能避免由于列名相同引起的歧义。
4)合理使用过滤操作子句。
在数据库中,过滤操作子句一般有如下几个。
●ON:用于连接过程中的过滤。
●WHERE:用于对检索出来的结果进行过滤。
●HAVING:用于在有聚合函数的情况下对检索结果进行过滤。
一般情况下,ON最先执行,WHERE次之,HAVING最后。这里给出的建议是,尽量一步步缩小过滤的范围,ON、WHERE和HAVING要有条理地分布在SQL语句中。不过,在某些情况下,如HAVING中有聚合函数进行计算时,WHERE子句的运行速度快于HAVING。因此,这种情况下,如果能够通过一些手段使用WHERE子句,就不要用HAVING子句。
另外,要尽量减小GROUP BY的范围,以提高GROUP BY语句的效率。如果在GROUP BY中需要过滤掉一些数据,尽量在GROUP BY之前用WHERE子句过滤,在GROUP BY之后用HAVING子句过滤。
从员工表中统计工资的代码如下:
SELECT姓名+AVG(工资)
FROM员工表
GROUP BY岗位
HAVING岗位='管理部门'OR岗位='行政部门'
上述语句将数据的过滤放在GROUP BY之后来做,可以改为:
SELECT姓名,AVG(工资)
FROM员工表
WHERE岗位='管理部门'OR岗位='行政部门'
GROUPBY岗位
5)SQL语句尽量大写。
在很多数据库中,系统遇到了小写的SQL语句,也会转换为大写,耗费资源。
6)适当利用关联子查询。
在关联子查询过程中,外层查询和内层查询一起进行,记录量减少较快,因此,在能够使用关联子查询的地方,应尽量使用。代码如下:
SELECT学号FROM学籍表WHERE年龄>20
AND班级号IN(SELECT班级号FROM班级表WHERE班主任='唐云')
因为要进行两次查询,即分别对学籍表和班级表进行全表查询,效率较低,所以可以改为:
SELECT学号FROM学籍表WHERE年龄>20ANDEXISTS
(SELECT*FROM班级号WHERE班级表.班级号=学籍表.班级号AND班主任='唐云')
7)利用索引提高效率。
索引可以用来提高数据的检索效率。通过索引查询数据比全表扫描要快。注意关于索引的使用,在有些数据库中,如果对索引进行了一些计算,索引将被使用,转而进行全表扫描,因此,要避免在索引上使用计算。
8)WHERE子句后条件顺序的考虑。
WHERE子句后可能有多个条件进行限制,此时可以先弄清楚条件执行的顺序,然后将可能筛选掉较多数据的条件先执行。
SELECT学号FROM学籍表
WHERE学费状态='未交'
AND性别='女'
如果未交学费的学生比例很小,那么尽量让“学费状态='未交'”这个条件先执行,如果数据库对两个WHERE条件的执行是从右到左的话,那么就可以改为:
SELECT学号FROM学籍表
AND性别='女'
WHERE学费状态='未交'
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。