java性能时间与空间消耗

Java性能时间与空间消耗

一、减少时间消耗

  1. 标准代码优化

(1) 将循环不变量的计算移出循环


例如:for (int i=0; i<size()*2; i++) { ... }

------>

for (int i=0, stop=size()*2; i<stop; i++) { ... }

(2) 避免重复计算


例如:if (birds.elementAt(i).isGrower()) ...

if (birds.elementAt(i).isPullet()) ...

------>

Bird bird = birds.elementAt(i);

if (bird.isGrower()) ...

if (bird.isPullet()) ...

(3) 减少数据索引访问次数,它比一般变量访问要慢的多,尤其是在大数组和循环次数比较多的情况下


例如:double[] rowsum = new double[n];

for (int i=0; i<n; i++)

for (int j=0; j<m; j++)

rowsum[i] += arr[i][j];

----------->

double[] rowsum = new double[n];

for (int i=0; i<n; i++) {

double[] arri = arr[i];

double sum = 0.0;

for (int j=0; j<m; j++)

sum += arri[j];

rowsum[i] = sum;

}

(4) 将常量声明为final static 或 final ,以便编译器可以将它们内联且在编译时预先计算好它们的值


例如:public final string BASE_NAME = “WH”;

public final static string BASE_NAME = “WH”;

(5) 用switch - case替代if-else-if,如果不能替代,通常用一个final static HashMap替代它


例如:int i = 9;

if(i == 0) { }

else if(i == 1) {…… }

else if(i == 2) {…… }

------------->

int i = 9;

switch(i) {

case 0:

break;

case 1:

break;

case 2:

break;

default:

}

(6) 不要使用另类的写法,来完成一个while循环


例如:int year = 0;

double sum = 200.0;

double[] balance = new double[100];

while ((balance[year++] = sum *= 1.05) < 1000.0);

2.作用域和变量优化

(1) 访问局部变量、方法参数要比实例变量、类变量快的多

(2) 在嵌套语句块内部和循环体内部声明变量,没有运行时的开销,所以尽量将变量越本地化越好,有助于编译器优化程序,同时也提高了代码的可读性

3.字符串优化

(1) 避免通过“+”建立字符串的多次拼接(消耗内存),尽量使用StringBuilder拼接字符串


例如:String s = "";

for (int i=0; i<n; i++) {

s += "#" + i;

}

-------------->

StringBuilder sbuf = new StringBuilder();

for (int i=0; i<n; i++) {

sbuf.append("#").append(i);

}

String s = sbuf.toString();

(2) 下面的情况和StringBuilder 一样,编译器会自动把字符串串起来


例如:String s = "(" + x + ", " + y + ")";

(3) 避免频繁地对字符串对象调用substring和index索引方法

4.常量数组优化

(1) 避免在方法内部声明一个只包含常量的数组,应该把数组提为全局常量数组,这样可以避免每次方法调用都生成数组对象的时间开销


例如:public static int monthdays(int y, int m) {

int[] monthlengths =

{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

return m == 2 && leapyear(y) ? 29 : monthlengths[m-1];

}

------------>

private final static int[] monthlengths =

{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

public static int monthdays(int y, int m) {

return m == 2 && leapyear(y) ? 29 : monthlengths[m-1];

}

(2) 对一些耗时的运算,可以采用预先处理数组的内容来优化


例如:private final static double[] logFac = new double[100];

static {

double logRes = 0.0;

for (int i=1, stop=logFac.length; i<stop; i++)

logFac[i] = logRes += Math.log(i);

}

public static double logBinom(int n, int k) {

return logFac[n] - logFac[n-k] - logFac[k];

}

5.方法优化

(1) 被private final static修饰的方法运行更快(根据实际业务使用)

(2)  如果确定一个类的方法不需要被子类重写,那么将方法用final修饰,这样更快


例如:class Foo {

private int size;

...

public final int getSize() {

return size;

}

}

(3) 使用接口作为方法参数来代替它的实现类,这样更快

6.排序和查找优化

(1) 除非数组或者链表元素很少,否则不要使用选择排序、冒泡排序和插入排序。而要使用堆排序(对于数组)、归并排序(链表)和快速排序(对于数组)。

(2) 更推荐的做法是使用JDK标准API内置的排序方法,时间复杂度为O(nlog(n))

对数组排序用Arrays.sort(它的实现代码使用改良的快速排序算法,不会占用额外内存空间,但是不稳定)
    对链表排序用Collections.sort(稳定算法,但会使用额外内存空间)

(3) 避免对数组和链表进行线性查找,除非你明确知道要查找的次数很少或者数组和链表长度很短

对于数组使用Arrays.binarySearch,但前提是数组已经有序,并且数组如包含多个要查找的元素,不能保证返回哪一个的index
     对于链表使用Collections.binarySearch,前提也是链表已经有序
     使用哈希查找:HashSet<T>、HashMap<K, V>等
     使用二叉查找树:TreeSet<T>和TreeMap<K, V>,一般要提供一个Comparator作为构造函数参数,如果不提供则按照自然顺序排序

7.异常优化

(1) new Exception(...)会构建一个异常堆栈路径,非常耗费时间和空间,尤其是在递归调用的时候。创建异常对象一般比创建普通对象要慢30-100倍。自定义异常类时,层级不要太多。或者使用try-catch块抛出异常更快。

(2) 可以通过重写Exception类的fillInStackTrace方法而避免过长堆栈路径的生成


例如:class MyException extends Exception {

public Throwable fillInStackTrace() {

return this;

}

}

(3) 所以有节制地使用异常,不要将异常用于控制流程、终止循环等。只将异常用于意外和错误场景(文件找不到、非法输入格式等)。尽量复用之前创建的异常对象。即,根据具体的情况需要时,再去自定义使用异常。

8.集合类优化

(1) 如果使用HashSet或者HashMap,确保key对象有一个快速合理的hashCode实现,并且要遵守hashCode和equals实现规约。


例如:class Person {

private String name;

private int id;

Person(String name,int id) {

this.name = name;

this.id = id;

}

public void setName(String name){  ……  }

public String getName(){ …… }

public void setId(int id){ …… }

public int getId(){ …… }

public int hashCode(){

return name.hashCode()+id;

}

public boolean equals(Object obj){

if(obj instanceof Person){

Person p = (Person)obj;

return(name.equals(p.name) && id == p.id);

}

return super.equals(obj);

}

}

(2) 如果使用TreeSet<T>或者TreeMap<K, V>,确保key对象有一个快速合理的compareTo实现;或者在创建TreeSet<T>或者TreeMap<K, V>时显式提供一个Comparator<T>


例如:public class test_treeset {

@SuppressWarnings("unchecked")

public static void main(String[] args) {

Set ts = new TreeSet();

ts.add(new Teacher("zhangsan", 1));

ts.add(new Teacher("lisi", 2));

ts.add(new Teacher("wangmazi", 3));

ts.add(new Teacher("wangwu",4));

ts.add(new Teacher("mazi", 3));

Iterator it = ts.iterator();

while (it.hasNext()) {

System.out.println(it.next());

}

}

}

class Teacher implements Comparable {

int num;

String name;

Teacher(String name, int num) {

this.num = num;

this.name = name;

}

public String toString() {

return "学号:" + num + "\t\t姓名:" + name;

}

//o中存放时的红黑二叉树中的节点,从根节点开始比较

public int compareTo(Object o) {

Teacher ss = (Teacher) o;

int result = num < ss.num ? 1 : (num == ss.num ? 0 : -1);//降序

//int result = num > ss.num ? 1 : (num == ss.num ? 0 : -1);//升序

if (result == 0) {

result = name.compareTo(ss.name);

}

return result;

}

}

(3) 对链表遍历优先使用迭代器遍历或者for(T x: lst),for(T x: lst)隐式地使用了迭代器来遍历链表。而对于数组遍历优先使用索引访问:for(int i = 0; i < array.length; i++)


例如:int size = lst.size();

for (int i=0; i<size; i++)

System.out.println(lst.get(i));

----------->

for (T x : lst)

System.out.println(x);

(4) 避免频繁调用LinkedList<T>或ArrayList<T>的remove(Object o)方法,它们会进行线性查找

(5) 避免频繁调用LinkedList<T>的add(int i, T x)和remove(int i)方法,它们会执行线性查找来确定索引为i的元素

(6) 最好避免遗留的集合类如Vector、Hashtable和Stack,因为它们的所有方法都用synchronized修饰,每个方法调用都必须先获得对象内置锁,增加了运行时开销。如果确实需要一个同步的集合,使用synchronziedCollection以及其他类似方法,或者使用ConcurrentHashMap

(7) 集合类只能存储引用类型数据,例如,Integer、Double、Float……

9.输入输出优化(IO)

(1) 使用缓冲输入和输出(BufferedReader、BufferedWriter、BufferedInputStream和BufferedOutputStream)可以提升IO速度


例如:BufferedReader br = new BufferedReader(new InputStreamReader(in));

(2) 将文件压缩后存到磁盘,这样读取时更快,虽然会耗费额外的CPU来进行解压缩。网络传输时也尽量压缩后传输。Java中压缩有关的类:ZipInputStream、ZipOutputStream、GZIPInputStream和GZIPOutputStream

10.空间和对象创建优化

(1) 如果程序使用很多空间(内存),它一般也将耗费更多的时间:对象分配和垃圾回收需要耗费时间、使用过多内存可能导致不能很好利用CPU缓存甚至可能需要使用虚存(访问磁盘而不是RAM)。而且根据JVM的垃圾回收器的不同,使用太多内存可能导致长时间的回收停顿,这对于交互式系统和实时应用是不能忍受的。

(2) 对象创建需要耗费时间(分配内存、初始化、垃圾回收等),所以避免不必要的对象创建。但是记住不要轻易引入对象池除非确实有必要。大部分情况,使用对象池仅仅会导致代码量增加和维护代价增大,并且对象池可能引入一些微妙的问题。

(3) 不要创建一些不会被使用到的对象

11.数组批量化操作优化

数组批量操作比对数组进行for循环要快得多,部分原因在于数组批量操作只需进行一次边界检查,而对数组进行for循环,每一次循环都必须检查边界。

(1) System.arrayCopy(src, si, dst, di, n) 从源数组src拷贝片段[si...si+n-1]到目标数组dst[di...di+n-1]

(2)  boolean Arrays.equals(arr1, arr2) 返回true,当且仅当arr1和arr2的长度相等并且元素一一对象相等(equals)

(3) void Arrays.fill(arr, x) 将数组arr的所有元素设置为x

(4) void Arrays.fill(arr, i, j x) 将数组arr的[i..j-1]索引处的元素设置为x

(5) int Arrays.hashCode(arr) 基于数组的元素计算数组的hashcode

* 12. 科学计算优化

Colt(http://acs.lbl.gov/software/colt/)是一个科学计算开源库,可以用于线性代数、稀疏和紧凑矩阵、数据分析统计,随机数生成,数组算法,代数函数和复数等。

*13.反射优化

(1) 通过反射创建对象、访问属性、调用方法比一般的创建对象、访问属性和调用方法要慢得多

(2) 访问权限检查(反射调用private方法或者反射访问private属性时会进行访问权限检查,需要通过setAccessible(true)来达到目的)可能会让反射调用方法更慢,可以通过将方法声明为public来比避免一些开销。这样做之后可以提高8倍。

14.编译器和jVM平台优化

(1) Sun公司的HotSpot Client JVM会进行一些代码优化,但一般将快速启动放在主动优化之前进行考虑

(2) Sun公司的HotSpot Server JVM(-server选项,Windows平台无效)会进行一些主动优化,但可能带来更长的启动延迟

(3) IBM的JVM也会进行一些主动优化

(4) J2ME和一些手持设备(如PDA)不包含JIT编译,很可能不会进行任何优化

二、减少空间消耗

1.堆(对象)和栈(方法参数、局部变量等)。堆被所有线程共享,但栈被每个线程独享

2.空间消耗的三个重要方面是:Allocation Rate(分配频率)、Retention(保留率)和Fragmentation(内存碎片)

Allocation Rate是程序创建新对象的频率,频率越高耗费的时间和空间越多。

Retention是存活的堆数据数量。这个值越高需要耗费越多的空间和时间(垃圾回收器执行分配和去分配工作时需要进行更多的管理工作)

Fragmentation:内存碎片是指小块无法使用的内存。如果一直持续创建大对象,可能会引起过多的内存碎片。从而需要更多的时间分配内存(因为要查找一个足够大的连续可用内存块),并且会浪费更多的空间因为内存碎片无法被利用。当然某些GC算法可以避免过多内存碎片的产生,但相应的算法代价也较高。

3.内存泄漏(程序泄漏引起的)

4.垃圾回收器的种类(分代收集、标记清除、引用计数、增量收集、压缩...)对Allocation Rate、Retention和Fragmentation的时间空间消耗影响很大

5.确保所有的对象之间的常量字段是静态的


例如:

public class Car {

ImageIcon symbol = new ImageIcon("porsche.gif");

...

}

--------------->

public class Car {

final static ImageIcon symbol = new ImageIcon("porsche.gif");

...

}

6.对象延迟创建,当不确定某个类是否将会被使用,那这个类里面的实例对象需要时再创建


例如:

public class Car {

private Button button = new JButton();

public Car() {

... initialize button ...

}

public final JButton getButton() {

return button;

}

}

——————————>

public class Car {

private Button button = null;

public Car() { ... }

public final JButton getButton() {

if (button == null) { // button not yet created, so create it

button = new JButton();

... initialize button ...

}

return button;

}

}

时间: 2024-10-10 20:37:13

java性能时间与空间消耗的相关文章

java性能调优——技客时间

一.java性能调优的标准 cpu:有的应用需要大量计算,他们会长时间.不间断地占用 CPU 资源,导致其他资源无法争夺到 CPU 而响应缓慢,从而带来系统性能问题.例如,代码递归导致的无限循环,正则表达式引起的回溯,JVM 频繁的 FULL GC,以及多线程编程造成的大量上下文切换等,这些都有可能导致 CPU 资源繁忙. 内存:Java 程序一般通过 JVM 对内存进行分配管理,主要是用 JVM 中的堆内存来存储 Java 创建的对象.系统堆内存的读写速度非常快,所以基本不存在读写性能瓶颈.但

Java性能优化指南系列(二):Java 性能分析工具

进行JAVA程序性能分析的时候,我们一般都会使用各种不同的工具.它们大部分都是可视化的,使得我们可以直观地看到应用程序的内部和运行环境到底执行了什么操作,所以性能分析(性能调优)是依赖于工具的.在第2章,我强调了基于数据驱动的性能测试是非常重要的,我们必须测试应用的性能并理解每个指标的含义.性能分析和数据驱动非常类似,为了提升应用程序的性能,我们必须获取应用运行的相关数据.如何获取这些数据并理解它们是本章的主题.[本章重点介绍JDK中提供的性能分析工具] 操作系统工具及其分析 程序分析的起点并不

最大化 AIX 上的 Java 性能,第 1 部分: 基础

http://www.ibm.com/developerworks/cn/aix/library/es-Javaperf/es-Javaperf1.html 最大化 AIX 上的 Java 性能,第 1 部分: 基础 这个由五个部分组成的系列提供了若干技巧和技术,这些技巧和技术通常用于优化 Java™ 应用程序,以便在 AIX® 上实现最佳的性能.其中还提供了有关每个技巧的适用性讨论.使用这些技巧,您应该能够快速优化 Java 环境,以适合应用程序的需要. 引言 存在若干可用于运行 AIX 的

最大化 AIX 上的 Java 性能,第 5 部分: 参考资料和结论

http://www.ibm.com/developerworks/cn/aix/library/es-Javaperf/es-Javaperf5.html 最大化 AIX 上的 Java 性能,第 5 部分: 参考资料和结论 这是由五个部分组成的系列的结束部分,此系列提供了相关技巧和技术,这些技巧和技术通常用于优化 Java™ 应用程序,以便在 AIX® 上实现最佳的性能.我们将讨论 AIX 上的 Java 性能优化的其他有趣方面,并查看几个案例研究,然后通过有用的参考资料列表结束此系列. 查

最大化 AIX 上的 Java 性能,第 2 部分: 速度需求

http://www.ibm.com/developerworks/cn/aix/library/es-Javaperf/es-Javaperf2.html 最大化 AIX 上的 Java 性能,第 2 部分: 速度需求 这个由五个部分组成的系列提供了若干技巧和技术,这些技巧和技术通常用于优化 Java™ 应用程序,以便在 AIX® 上实现最佳的性能.其中还提供了有关每个技巧的适用性讨论.使用这些技巧,您应该能够快速优化 Java 环境,以适合应用程序的需要. 查看本系列更多内容 | 0 评论:

Java 性能分析工具 , 第 2 部分:Java 内置监控工具

引言 本文为 Java 性能分析工具系列文章第二篇,第一篇:操作系统工具.在本文中将介绍如何使用 Java 内置监控工具更加深入的了解 Java 应用程序和 JVM 本身.在 JDK 中有许多内置的工具,其中包括: jcmd:打印一个 Java 进程的类,线程以及虚拟机信息.适合用在脚本中.使用 jcmd - h 来查看使用方法. jconsole:提供 JVM 活动的图形化展示,包括线程使用,类使用以及垃圾回收(GC)信息. jhat:帮助分析内存堆存储. jmap:提供 JVM 内存使用信息

Java性能优化攻略详解

如何让Java应用程序运行是一回事,但让他们跑得快就是另外一回事了.在面对对象的环境中,性能问题就像来势凶猛的野兽.但JVM的复杂性将性能调整的复杂程度增加了一个级别.这里Refcard涵盖了JVM internals.class loading(Java8中更新以映射最新的元空间).垃圾回收.故障诊断.检测.并发性,等等. 当前Java是目前软件开发领域中使用最广泛的编程语言之一.Java应用程序在许多垂直领域(银行.电信.医疗保健等)中都有广泛使用.Refcard的目的是,帮助开发者通过专注

java性能

一.关于性能的基本知识  1.性能的定义  在我们讨论怎样提高Java的性能之前,我们需要明白“性能“的真正含义.我们一般定义如下五个方面作为评判性能的标准.  1) 运算的性能----哪一个算法的执行性能最好  2) 内存的分配----程序需要分配多少内存,运行时的效率和性能最高.  3) 启动的时间----程序启动需要多少时间.  4) 程序的可伸缩性-----程序在用户负载过重的情况下的表现.  5) 性能的认识------用户怎样才能认识到程序的性能.  对于不同的应用程序,对性能的要求

83.JAVA编程思想——关于JAVA性能

83.JAVA编程思想--关于JAVA性能 Java 语言特别强调准确性,但可靠的行为要以性能作为代价.这一特点反映在自动收集垃圾.严格的运行期检查.完整的字节码检查以及保守的运行期同步等等方面.对一个解释型的虚拟机来说,由于目前有大量平台可供挑选,所以进一步阻碍了性能的发挥. "先做完它,再逐步完善.幸好需要改进的地方通常不会太多." 1     基本方法 只有正确和完整地检测了程序后,再可着手解决性能方面的问题: (1) 在现实环境中检测程序的性能.若符合要求,则目标达到.若不符合