Heim  >  Artikel  >  Java  >  Zusammenfassung von 35 Java-Code-Leistungsoptimierungen

Zusammenfassung von 35 Java-Code-Leistungsoptimierungen

伊谢尔伦
伊谢尔伦Original
2016-11-30 11:46:161338Durchsuche

Vorwort

Codeoptimierung ist ein sehr wichtiges Thema. Manche Leute denken vielleicht, dass es nutzlos ist. Welche kleinen Dinge können geändert werden? Welche Auswirkungen hat die Änderung auf die Ausführungseffizienz des Codes? Ich denke über diese Frage nach: Ist es für einen Wal im Meer sinnvoll, eine kleine Garnele zu fressen? Es war nutzlos, aber nachdem er weitere Garnelen gegessen hatte, war der Wal satt. Das Gleiche gilt für die Codeoptimierung. Wenn sich das Projekt darauf konzentriert, so schnell wie möglich ohne Fehler zu starten, können Sie sich zu diesem Zeitpunkt auf das Große konzentrieren und das Kleine loslassen, und die Details des Codes müssen nicht verfeinert werden ; Wenn jedoch genügend Zeit für die Entwicklung und Wartung des Codes vorhanden ist, müssen Sie zu diesem Zeitpunkt jeden Aspekt berücksichtigen. Die Anhäufung kleiner Optimierungspunkte nacheinander wird die Laufeffizienz des Codes definitiv verbessern.

Die Ziele der Codeoptimierung sind:

1. Reduzieren Sie die Größe des Codes

2. Verbessern Sie die Effizienz der Codeausführung

Details zur Codeoptimierung

1. Versuchen Sie, den Endmodifikator von Klassen und Methoden anzugeben.

Klassen mit Endmodifikator können nicht abgeleitet werden. In der Java-Kern-API gibt es viele Beispiele für endgültige Anwendungen, z. B. java.lang.String, bei dem die gesamte Klasse endgültig ist. Durch die Angabe des letzten Modifikators für eine Klasse wird verhindert, dass die Klasse vererbt wird, und durch die Angabe des letzten Modifikators für eine Methode wird verhindert, dass die Methode überschrieben wird. Wenn eine Klasse als final bezeichnet wird, sind alle Methoden der Klasse final. Der Java-Compiler sucht nach Möglichkeiten, alle endgültigen Methoden zu integrieren. Inlining spielt eine wichtige Rolle bei der Verbesserung der Java-Laufzeitoptimierung. Dadurch kann die Leistung um durchschnittlich 50 % verbessert werden.

2. Versuchen Sie, Objekte wiederzuverwenden

Insbesondere für die Verwendung von String-Objekten sollte stattdessen StringBuilder/StringBuffer verwendet werden, wenn String-Verbindungen auftreten. Da die Java Virtual Machine nicht nur Zeit damit verbringt, Objekte zu generieren, muss sie möglicherweise in Zukunft auch Zeit damit verbringen, diese Objekte zu sammeln und zu verarbeiten. Daher hat die Generierung zu vieler Objekte große Auswirkungen auf die Leistung des Programms.

3. Verwenden Sie so viel wie möglich lokale Variablen

Die beim Aufruf der Methode übergebenen Parameter und die während des Aufrufs erstellten temporären Variablen werden schneller auf dem Stapel gespeichert. Andere Variablen, z. B. statische Variablen und Instanzen, Variablen usw. werden alle im Heap erstellt, was langsamer ist. Darüber hinaus gehen die Inhalte der auf dem Stapel erstellten Variablen verloren, wenn die Methode endet, und es ist keine zusätzliche Speicherbereinigung erforderlich.

4. Schließen Sie den Stream umgehend

Seien Sie während der Java-Programmierung vorsichtig, wenn Sie Datenbankverbindungen und E/A-Stream-Vorgänge durchführen. Schließen Sie den Stream nach der Verwendung rechtzeitig, um Ressourcen freizugeben. Da der Betrieb dieser großen Objekte einen hohen Systemaufwand verursacht, kann eine kleine Nachlässigkeit schwerwiegende Folgen haben.

5. Minimieren Sie wiederholte Berechnungen von Variablen

Machen Sie deutlich, dass der Aufruf einer Methode, selbst wenn nur eine Anweisung in der Methode vorhanden ist, kostspielig ist, auch beim Erstellen eines Stapelrahmens und beim Aufrufen einer Methode schützen Sie die Szene, stellen Sie die Szene wieder her, wenn die Methode aufgerufen wird usw. So zum Beispiel die folgende Operation:

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

Es wird empfohlen, sie zu ersetzen durch:

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

Auf diese Weise, wenn list.size() sehr groß ist, viel des Verbrauchs wird reduziert

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

  例如:

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

  建议替换为:

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

  7、慎用异常

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

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

  除非不得已。如果毫无理由地这么写了,只要你的领导资深一点、有强迫症一点,八成就要骂你为什么写出这种垃圾代码来了

 9、如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度

  比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder为例:

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

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

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

  可以通过类(这里指的不仅仅是上面的StringBuilder)的来设定它的初始化容量,这样可以明显地提升性能。比如StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要StringBuilder达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中—-这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么:

  (1)在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间

  (2)把原来的4096个字符拷贝到新的的字符数组中去

  这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。

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

  11、乘法和除法使用移位操作

  例如:

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;
}

  移位操作虽然快,但是可能会使代码不太好理解,因此最好加上相应的注释。

 12、循环内不要不断创建对象引用

  例如:

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

  这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:

Object obj = null;for (int i = 0; i 4caf8890fd2fe33e882cefa83d71f661重写了Object的toString()方法。

 31、不要对超出范围的基本数据类型做向下强制转型

  这绝不会得到想要的结果:

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

  我们可能期望得到其中的某几位,但是结果却是:

  1942892530

  解释一下。Java中long是8个字节64位的,所以12345678901234在计算机中的表示应该是:

  0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010

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

  0111 0011 1100 1110 0010 1111 1111 0010

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

  1、整型默认的数据类型是int,long l = 12345678901234L,这个数字已经超出了int的范围了,所以最后有一个L,表示这是一个long型数。顺便,浮点型的默认类型是double,所以定义float的时候要写成”"float f = 3.5f”

  2、接下来再写一句”int ii = l + i;”会报错,因为long + int是一个long,不能赋值给int

  32、公用的集合类中不使用的数据一定要及时remove掉

  如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去remove掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。

 33、把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据+”"最慢

  把一个基本数据类型转为一般有三种方式,我有一个Integer型数据i,可以使用i.toString()、String.valueOf(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

  所以以后遇到把一个基本数据类型转为String的时候,优先考虑使用toString()方法。至于为什么,很简单:

  1、String.valueOf()方法底层调用了Integer.toString()方法,但是会在调用前做空判断

  2、Integer.toString()方法就不说了,直接调用了

  3、i + “”底层使用了StringBuilder实现,先用append方法拼接,再用toString()方法获取字符串

  三者对比下来,明显是2最快、1次之、3最慢

 34、使用最有效率的方式去遍历Map

  遍历Map的方式有很多,通常场景下我们需要的是遍历Map中的Key和Value,那么推荐使用的、效率最高的方式是:

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的key值,那用”Setf7e83be87db5cd2d9a8a0b8117b38cd4 keySet = hm.keySet();”会比较合适一些

 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都会被close掉。


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn