您的位置  > 互联网

Java代码优化的运行效率有什么影响?

代码优化也是如此。 如果项目的重点是尽快无bug上线,那么此时就可以先大后小,代码的细节不需要细化; 但如果有足够的时间来开发和维护代码,这个时候就必须考虑方方面面。 还有一些细节可以优化。 小的优化点一点一滴的积累,一定会提高代码的运行效率。

代码优化的目标是

代码优化细节

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

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

调用方法时传递的参数以及调用中创建的临时变量都保存在堆栈中,速度更快。 其他变量,如静态变量、实例变量等,都是在堆中创建的,速度较慢。 此外,在堆栈中创建的变量的内容随着方法结束而消失,并且不需要额外的垃圾收集。

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

澄清一个概念,调用一个方法,即使方法中只有一条语句,其代价也是高昂的,包括创建栈帧、调用方法时保护场景、调用方法时恢复场景等。所以对于例如以下操作:

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

建议将其替换为:

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

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

例如:

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

建议将其替换为:

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

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

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

例如、、、、、等等,例如:

(1)()//默认分配16个字符的空间

(2) (int size) // 默认分配size个字符的空间

(3) (str) // 默认分配16个字符+str.()字符空间

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

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

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

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

例如:

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

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

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

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

例如:

for (int i = 1; i <= count; i++)
{
Object obj = new Object();
}

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

Object obj = null;for (int i = 0; i <= count; i++) 
{
obj = new Object(); 
}

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

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

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

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

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

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

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

public class A
{ 
private static B b = new B();
}

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

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

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

if (list instanceof RandomAccess)
{ for (int i = 0; i < list.size(); i++){}
}else{
Iterator<?> iterator = list.iterable(); while (iterator.hasNext()){iterator.next()}
}

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

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

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

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

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

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

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

一旦你了解了 和 的原理就知道了

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

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

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

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

这是一个比较常见的技巧。 如果您有以下代码:

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

建议修改为:

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

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

人们常常会问,“if(i==1)”和“if(1==i)”有什么区别吗? 这要从 C/C++ 开始。

在C/C++中,“if(i==1)”判断条件是根据0和非0成立的。 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 中将 i 赋值为 1。 如果判断里面的内容不为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)”在语义上没有区别,但从阅读角度来看,还是使用前者更好。

看一下在数组上使用 () 打印的内容:

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

结果发现:

[I@18a992f

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

这永远不会给出期望的结果:

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

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

解释。 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。

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

基本数据类型的转换通常有三种方法。 我有一个类型数据 i。 我可以使用 i.()、.(i) 和 i+””。 这三种方法的效率如何? 我们来做个测试:

public static void main(String[] args)
{ 
int loopTime = 50000;
Integer i = 0; long startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++)
{
String str = String.valueOf(i);
}
System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms");
startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++)
{
String str = i.toString();
}
System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms");
startTime = System.currentTimeMillis(); for (int j = 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最慢。

遍历地图的方法有很多种。 一般场景下,我们需要的是遍历Map中的Keys和。 推荐且最有效的方法是:

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

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

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

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无论如何都会被关闭。

目前几乎所有的项目都采用了线程池技术,这是非常好的。 线程数量可以动态配置,线程可以复用。

但是,如果您在项目中使用它,请务必记住在使用之前或之后进行。 这是因为上面提到的线程池技术是线程复用,也就是说代码运行时,某个线程不会被销毁而是会等待下一次使用。 我们来看看参考持有情况。 在课堂里:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

线程没有被销毁意味着前一个线程的集合中的数据仍然存在。 那么当下一个线程复用这个的时候,很有可能拿到的是上一个线程设置的数据而不是你想要的内容。

这个问题非常晦涩难懂。 一旦因为这个原因出现错误,如果没有相关经验或者没有扎实的基础,是很难发现这个问题的。 所以写代码的时候要注意这一点,这样以后你的工作量会减少很多。

—————————————————————————————————————————————————— ——————————————————————————

如果您在学习过程中有任何疑问,请来吉乐网()提问,或者扫描下方二维码,关注吉乐官方微信,并在平台下方留言哦~