聚焦Java领域优质技术数据,欢迎关注

代码优化

一个非常重要的课题。 有些人可能认为这没什么用。 一些小地方可以修改什么? 对代码的运行效率会有什么影响? 我这样想这个问题,就像海里的鲸鱼,吃一只小虾有用吗? 没什么用,但是吃多了虾,鲸鱼就饱了。

代码优化也是如此。 如果项目的重点是尽快无bug上线,那么此时就可以先大后小,对代码的细节进行微调; 但是如果你有足够的时间去开发和维护代码,你一定要考虑每一个可以优化的细节,一个个小的优化点的积累,一定会提高代码的运行效率。

代码优化的目标是代码优化细节

1、尽量指定类和方法的final修饰符

带有final修饰符的类是不可派生的。 在Java核心API中,有很多应用final的例子,例如java.lang。 整个班级都是最终的。 为类指定final修饰符可以防止该类被继承,为方法指定final修饰符可以防止该方法被重写。 如果一个类被指定为final,则该类的所有方法都是final的。 Java 编译器将寻找机会内联所有最终方法。 内联对于提高Java运行效率有着重要的作用。 有关详细信息,请参阅 Java 运行时优化。 这可以平均提高 50% 的性能。

2. 尽可能重复使用对象

特别是,当发生字符串串联时,应该使用对象而不是 / 。 由于Java虚拟机不仅需要时间来生成对象,而且将来可能还需要花费时间对这些对象进行垃圾收集和处理,因此,生成过多的对象会对程序的性能产生很大的影响。

3.尽可能使用局部变量

调用方法时传递的参数以及调用过程中创建的临时变量存储在堆栈中的速度较快,而其他变量,例如静态变量和实例变量则创建在堆中,速度较慢。 另外,栈中创建的变量会随着方法结束而消失,不需要额外的垃圾回收。

4.及时关闭流

在Java编程中,进行数据库连接和I/O流操作时要小心。 使用完毕后及时关闭,释放资源。 因为这些大对象的操作会造成很大的系统开销,稍有不慎就会导致严重的后果。

5. 尽量减少变量的重复计算

澄清一个概念,调用一个方法,即使方法里只有一句话,也是消耗的,包括创建栈帧、调用方法时保护场景、恢复方法调用时场景。 例如以下操作:

for(int i =0; i < list.size(); i++){...}

建议将其替换为:

for(int i =0, int length = list.size(); i < length; i++){...}

这样当list.size()很大的时候,就减少了很多消耗

6、尽量采用懒加载策略,即只在需要的时候创建

例如:

Stringstr="aaa";if(i ==1){list.add(str);}

建议将其替换为:

if(i ==1){Stringstr="aaa";list.add(str);}

7. 谨慎使用异常

异常不利于性能。 抛出异常首先创建一个新对象。 接口的构造函数调用一个名为()的本地同步方法,()方法检查堆栈并收集调用跟踪信息。 每当抛出异常时,Java 虚拟机就必须调整调用堆栈,因为在处理过程中会创建一个新对象。 异常应该只用于错误处理,而不应该用于控制程序流程。

8.循环中不要使用try…catch…,应该放在最外层

除非你必须这样做。 如果你无缘无故地写出这样的东西,只要你的领导比较有经验,有强迫症,他很可能会骂你写出这样的垃圾代码。

9. 如果要添加的内容的长度可以估计,则为以数组形式实现的底层集合和工具类指定初始长度

如、、、、、等,举个例子:

(1)StringBuilder() // 默认分配16个字符的空间
(2)StringBuilder(int size) // 默认分配size个字符的空间
(3)StringBuilder(String str) // 默认分配16个字符+str.length()个字符空间

它的初始化能力可以通过类来设置(这里指的不仅仅是上面),这样可以显着提高性能。 例如,表示当前可以保留的字符数。 因为当达到最大容量时,它会将自己的容量增加到当前容量的2倍加2,每当达到最大容量时,它就得创建一个新的字符数组,然后将旧的字符数组内容复制到一个新字符数组 – 这是一个非常消耗性能的操作。 试想一下,如果在不指定长度的情况下,可以估计字符数组中会存储大约5000个字符,最接近的2到5000的幂是4096,每次扩展加2也没关系,那么:

(1)在4096的基础上,申请8194个字符数组,相当于一次性申请12290个字符数组。 如果一开始就可以指定5000个字符数组,将节省一倍以上的空间;

(2) 将原来的4096个字符复制到新的字符数组中。

这样就浪费了内存空间,降低了代码运行效率。 因此,为数组实现的底层集合和工具类设置合理的初始化容量是没有错的,会带来立竿见影的效果。 不过要注意,像这种数组+链表实现的集合,不要把初始大小设置成和你预估的大小一样,因为一张表上只连接一个对象的可能性几乎为零。 建议将初始大小设置为2的N次方。如果可以估计有2000个元素,可以设置为new(128)或new(256)。

10、复制大量数据时,使用.()命令

11.乘法和除法使用移位运算

例如:

for(val=0;val<100000;val+=5){a =val*8;b =val/2;}

使用移位操作可以大大提高性能,因为在计算机底层,位的操作是最方便、最快的,所以建议修改为:

for(val=0;val<100000;val+=5){a =val<>1;}

移位操作虽然很快,但是可能会导致代码难以理解,所以最好添加相应的注释。

12.不要在循环中不断创建对象引用

例如:

for(inti =1; i <= count; i++)
{Objectobj =newObject();}

这种方法将导致内存中存在 count 个对象引用。 如果计数太大,就会消耗内存。 建议改为:

Objectobj =null;for(inti =0; i <= count; i++) { obj =newObject(); }

这种情况下,内存中只有一份对象引用,每次new()时,该对象引用都指向不同的一个,但内存中却只有一份,这样就大大节省了内存空间。

13、基于效率和类型检查的考虑,尽可能使用数组,只有在无法确定数组大小的情况下才使用数组。

14.尽量使用,,除非线程安全需要,否则不建议使用,,,后三者由于使用同步机制造成性能开销

15.不要将数组声明为final

因为这是没有意义的,它只是将引用定义为final,而数组的内容仍然可以随意改变。 将数组声明为安全漏洞更像是一个安全漏洞,这意味着该数组可以被外部类更改。

16.在适当的场合尝试使用单例

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

(1)控制资源的使用,通过线程同步控制资源的并发访问

(2)控制实例的生成,达到节省资源的目的

(3) 控制数据的共享,使多个不相关的进程或线程之间能够进行通信,而无需建立直接关系

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

要知道,当一个对象被定义为 的变量引用时,那么 gc 通常不会回收这个对象占用的堆内存,如:

publicclassA{privatestaticB b =newB();}

此时,静态变量b的生命周期与A类相同,如果A类没有被卸载,则引用B指向的B对象将一直保留在内存中,直到程序终止。

18.及时清理不再需要的会话

为了清除不再活动的会话,许多应用程序服务器都有默认的会话超时,通常为 30 分钟。 当应用服务器需要保存更多的会话时,如果内存不足,操作系统会将一些数据传输到磁盘上,应用服务器也可能会根据MRU(最近使用)算法将一些不活动的会话转储到磁盘上。 它甚至可能抛出内存不足的异常。 如果要将会话转储到磁盘,则必须首先对其进行序列化,而在大型集群中序列化对象的成本可能会很高。 因此,当不再需要时,应该及时调用()方法来清除。

19、实现接口的集合比如应该使用最常见的for循环而不是循环来遍历

这是JDK向用户推荐的。 JDK API对该接口的解释是:该接口的实现用于表明其支持快速随机访问。 该接口的主要目的是允许通用算法改变其行为,以便在应用于随机或连续访问列表时能够提供良好的性能。 实践经验表明,如果随机访问实现接口的类实例,使用普通for循环的效率会比使用循环更高; 反之,如果按顺序访问,使用效率会更高。 可以使用类似下面的代码来进行判断:

if(listinstanceofRandomAccess){for(int i =0; i iterator =list.iterable();while(iterator.hasNext()){iterator.next()}}

循环的底层实现原理是迭代器,参见Java语法糖1:变长参数和循环原理。 所以后半句“相反,如果是顺序访问的话,使用效率会更高”,意思就是那些顺序访问的类实例,使用循环来遍历。

20.使用同步代码块而不是同步方法

这一点在多线程模块中的lock方法块一文中已经说得很清楚了。 除非可以确定整个方法需要同步,否则尽量使用同步代码块,避免禁用不需要同步的代码。 进行了同步,影响了代码执行的效率。

21.将常量声明为final并以大写命名

这样就可以在编译时将这些内容放入常量池中,避免运行时计算生成常量值。另外,用大写命名常量也可以方便区分常量和变量

22.不要创建一些未使用的对象,不要导入一些未使用的类

这是没有意义的,如果代码中出现“The value of the local i is ”、“The java.util is ”,那么请删除这些无用的内容

23、程序运行时避免使用反射

有关的信息,请参阅反射。 反射是Java为用户提供的一个非常强大的功能,而强大的功能往往意味着低效率。 不建议在程序运行过程中特别频繁地使用反射机制。 如果确实有必要,建议的做法是在项目启动时通过反射实例化需要加载的类。 一个对象并放入内存——用户只关心与同伴交互时获得最快的响应速度,而不关心同伴的项目需要多长时间才能启动。

24、使用数据库连接池和线程池

两个池都是用来复用对象的,前者可以避免频繁打开和关闭连接,后者可以避免频繁创建和销毁线程

25.使用缓冲输入和输出流进行IO操作

缓冲输入输出流,即,,,,可以大大提高IO效率

26、顺序插入和随机访问较多的场景使用,元素删除和中间插入较多的场景使用

到此,你就知道了原理的理解和

27.不要让方法有太多形参

方法是外部提供的方法。 如果给这些方法太多的形式参数,则有两个主要缺点:

1、违背了面向对象编程的思想。 Java强调一切皆对象。 太多的形式参数不符合面向对象编程的思想。

2、参数过多必然导致方法调用出错概率增加

至于“太多”指的是多少个,可能是3个,也可能是4个。比如我们用JDBC写一个方法,有10个学生信息字段要插入到表中,这10个参数可以封装在一个实体类作为方法的形式参数。

28、写字符串变量和字符串常量时,把字符串常量写在前面

这是一个比较常见的trick,如果有下面的代码:

String str ="123";
if(str.equals("123")) {...}

建议修改为:

String str ="123";
if("123".equals(str)){
...
}

这主要是为了避免空指针异常

29、请注意,java中if(i==1)和if(1==i)没有区别,但是从阅读习惯上来说,建议使用前者

人们通常会问,“if (i == 1)”和“if (1== i)”有什么区别吗?它以C/C++开头。

在C/C++中,“if(i==1)”判断条件成立,基于0和非零,0表示假,非零表示真,如果有这样一段代码:

int i =2;
if(i ==1){
...
}else{
...
}

C/C++判断“i==1”不为真,所以用0表示,为假。 但如果:

int i =2;if(i =1) {...}else{...}

万一程序员不小心把“if(i==1)”写成了“if(i=1)”,就会出现问题。 在if内给ia赋值1,if判断里面的内容不为0,返回值为true,但显然i是2,比较的值为1,应该返回false。 这种情况在C/C++开发中很有可能发生,并且会导致一些难以理解的错误。 因此,为了避免开发者在if语句中进行错误的赋值操作,建议将if语句写成:

int i =2;if(1== i) {...}else{...}

这样,即使开发者不小心写成了“1=i”,C/C++编译器也能第一时间检查出来,因为我们可以将i赋给1给一个变量,但我们不能将1赋给i给一个变量。持续的。

然而在Java中,C/C++的“if(i=1)”语法是不可能的,因为一旦写出这个语法,Java就会编译并报错“Type: from int to”。 不过,虽然Java的“if(i==1)”和“if(1==i)”在语义上没有区别,但从阅读习惯来看,还是推荐使用前者更好。

30.不要在数组上使用()方法

看看在数组上使用 () 会打印出什么:

public static void main(String[] args){
int[]is=newint[]{1,2,3};
System.out.println(is.toString());}

结果发现:

[I@18a992f

目的是打印出数组的内容,但可能会导致空指针异常,因为数组引用为空。 不过,虽然对于array()来说没有意义,但是对于()来说却可以打印出集合的内容,因为集合的父类重写了()方法。

31. 不要向下转型为超出范围的原始数据类型

这永远不会得到期望的结果:

public static void main(String[] args){
long l =12345678901234L;
int i = (int) l;
System.out.println(i);}

我们可能期望得到其中一些位,但结果是:

1942892530

解释。 Java中的Long是8字节64位,所以234在计算机中的表示应该是:

0 0 0 0 0 0010

一个int类型数据是4个字节32位,上面这串二进制数据取低位的前32位为:

0 1 1111 0010

这串二进制表示为十进制,所以就是我们上面控制台输出的内容。 从这个例子可以得出两个结论:

1、默认的数据类型是int,long l = 234L,这个数字超出了int的范围,所以末尾有一个L,表示是long类型的数字。 顺便说一句,浮点数的默认类型是,所以定义float时应该写成“”float f = 3.5f”

2. 接下来,写下句子“int ii = l + i;” 并且会报错,因为long + int是long,不能赋值给int

32、公共集合类中未使用的数据必须及时丢弃

如果一个集合类是公共的(即不是方法中的属性),那么这个集合中的元素将不会自动释放,因为总是有对它们的引用。 因此,如果公共集合中的某些数据没有被使用并且没有被移除,就会导致公共集合不断增长,导致系统存在内存泄漏的隐患。

33、将基本数据类型转换为字符串,基本数据类型.()是最快的方式,.(data)次之,data+""最慢

可通过三种方法将基本数据类型转换为通用数据类型。 我有一个类型数据 i,我可以通过三种方式使用 i.()、.(i)、i+"" 。 这三种方式的效率如何? 我们来看一个测试:

public static void main(String[] args){
int loopTime =50000;
Integer i =0;
long startTime = System.currentTimeMillis();
for(intj =0; j < loopTime; j++)
{String str = String.valueOf(i);}
System.out.println("String.valueOf():"+ (System.currentTimeMillis() - startTime) +"ms");
startTime = System.currentTimeMillis();
for(intj =0; j < loopTime; j++){
String str = i.toString();}
System.out.println("Integer.toString():"+ (System.currentTimeMillis() - startTime) +"ms");
startTime = System.currentTimeMillis();
for(intj =0; j < loopTime; j++){String str = i +"";}
System.out.println("i + "":"+ (System.currentTimeMillis() - startTime) +"ms");}

运行结果为:

String.valueOf():11ms Integer.toString():5ms i +"":25ms

所以以后遇到基本数据类型转换为 时,优先使用()方法。 至于为什么,很简单:

1、.()方法底层调用了.()方法,但是调用前会做一个简短的判断

2..()方法就不提了,直接调用即可

3、i+""底层使用实现,先使用方法进行拼接,然后使用()方法获取字符串

三者比较,明显2最快,1第二,3最慢

34.用最有效的方式遍历地图

遍历地图的方法有很多种。 一般来说,我们需要的是遍历Map中的Key和Value。 推荐且最有效的方法是:

public static void main(String[] args){
HashMap hm =newHashMap();
hm.put("111","222");
Set<Map.Entry> entrySet = hm.entrySet();
 
Iterator<Map.Entry> iter = entrySet.iterator();
 while (iter.hasNext()) {
 Map.Entry entry = iter.next();
 System.out.println(entry.getKey() + "t" + entry.getValue());
 }
}

如果只是想遍历这个Map的键值,那么使用“Set = hm.();”会更合适

35、建议单独操作资源的close()

意思是,例如我有这样一段代码:

try{XXX.close();YYY.close();}catch (Exception e){...}

建议修改为:

try{ XXX.close(); }catch (Exception e) {...}try{ YYY.close(); }catch (Exception e) {...}

虽然麻烦一点,但是可以避免资源泄漏。 我想,如果没有修改代码,如果XXX.close()抛出异常,那么就会进入cath块,YYY.close()不会被执行,YYY资源也不会被回收。 占用,如果此类代码过多,可能会导致资源句柄泄漏。 改成上面的写法后,保证XXX和YYY无论如何都会被关闭。

好了,今天的主题就讲到这里吧,不管如何,能帮到你我就很开心了,如果您觉得这篇文章写得不错,欢迎点赞和分享给身边的朋友。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注