专家释疑:轻松提高Java代码的性能

性能

  尾递归转换能加快应用程序的速度,但不是所有的 JVM 都会做这种转换,很多算法用尾递归方法表示会显得格外简明。编译器会自动把这种方法转换成循环,以提高程序的性能。但在 Java 语言规范中,并没有要求一定要作这种转换,因此,并不是所有的 Java 虚拟机(JVM)都会做这种转换。这就意味着在 Java 语言中采用尾递归表示可能导致巨大的内存占用,而这并不是我们期望的结果。Eric Allen 在本文中阐述了动态编译将会保持语言的语义,而静态编译则通常不会。他说明了为什么这是一个重要问题,并提供了一段代码来帮助判断您的即时(JIT)编译器是否会在保持语言语义的同时做尾递归代码转换。<?XML:NAMESPACE PREFIX = O />

  尾递归及其转换

  相当多的程序包含有循环,这些循环运行的时间占了程序总运行时间的很大一部分。这些循环经常要反复更新不止一个变量,而每个变量的更新又经常依赖于其它变量的值。

  如果把迭代看成是尾递归函数,那么,就可以把这些变量看成是函数的参数。简单提醒一下:如果一个调用的返回值被作为调用函数的值立即返回,那么,这个递归调用就是尾递归;尾递归不必记住调用时调用函数的上下文。 

  由于这一特点,在尾递归函数和循环之间有一个很好的对应关系:可以简单地把每个递归调用看作是一个循环的多次迭代。但因为所有可变的参数值都一次传给了递归调用,所以比起循环来,在尾递归中可以更容易地得到更新值。而且,难以使用的 break 语句也常常为函数的简单返回所替代。  

  但在 Java 编程中,用这种方式表示迭代将导致效率低下,因为大量的递归调用有导致堆栈溢出的危险。  

  解决方案比较简单:因为尾递归函数实际上只是编写循环的一种更简单的方式,所以就让编译器把它们自动转换成循环形式。这样您就同时利用了这两种形式的优点。  

  但是,尽管大家都熟知如何把一个尾递归函数自动转换成一个简单循环,Java 规范却不要求做这种转换。不作这种要求的原因大概是:通常在面向对象的语言中,这种转换不能静态地进行。相反地,这种从尾递归函数到简单循环的转换必须由 JIT 编译器动态地进行。  

  要理解为什么会是这样,考虑下面一个失败的尝试:在 Integers 集上,把 Iterator 中的元素相乘。  

  因为下面的程序中有一个错误,所以在运行时会抛出一个异常。但是,就象在本专栏以前的许多文章中已经论证的那样,一个程序抛出的精确异常(跟很棒的错误类型标识符一样)对于找到错误藏在程序的什么地方并没有什么帮助,我们也不想编译器以这种方式改变程序,以使编译的结果代码抛出一个不同的异常。  

  清单 1. 一个把 Integer 集的 Iterator 中的元素相乘的失败尝试 

import java.util.Iterator; 

public class Example { 

  public int product(Iterator i) { 
    return productHelp(i, 0); 
  } 

  int productHelp(Iterator i, int accumulator) { 
    if (i.hasNext()) { 
      return productHelp(i, accumulator * ((Integer)i.next()).intValue()); 
    } 
    else { 
      return accumulator; 
    } 
  } 

  注意 product 方法中的错误。product 方法通过把 accumulator 赋值为 0 调用 productHelp。它的值应为 1。否则,在类 Example 的任何实例上调用 product 都将产生 0 值,不管 Iterator 是什么值。  

  假设这个错误终于被改正了,但同时,类 Example 的一个子类也被创建了,如清单 2 所示: 

  清单 2. 试图捕捉象清单 1 这样的不正确的调用 

import java.util.*; 

class Example { 

  public int product(Iterator i) { 
    return productHelp(i, 1); 
  } 

  int productHelp(Iterator i, int accumulator) { 
    if (i.hasNext()) { 
      return productHelp(i, accumulator * ((Integer)i.next()).intValue()); 
    } 
    else { 
      return accumulator; 
    } 
  } 

// And, in a separate file: 

import java.util.*; 

public class Example2 extends Example { 
  int productHelp(Iterator i, int accumulator) { 
    if (accumulator < 1) { 
      throw new RuntimeException("accumulator to productHelp must be >= 1"); 
    } 
    else { 
      return super.productHelp(i, accumulator); 
    } 
  } 

  public static void main(String[] args) { 
    LinkedList l = new LinkedList(); 
    l.add(new Integer(0)); 
    new Example2().product(l.listIterator()); 
  } 

  类 Example2 中的被覆盖的 productHelp 方法试图通过当 accumulator 小于“1”时抛出运行时异常来捕捉对 productHelp 的不正确调用。不幸的是,这样做将引入一个新的错误。如果 Iterator 含有任何 0 值的实例,都将使 productHelp 在自身的递归调用上崩溃。  

  现在请注意,在类 Example2 的 main 方法中,创建了 Example2 的一个实例并调用了它的 product 方法。由于传给这个方法的 Iterator 包含一个 0,因此程序将崩溃。  

  然而,您可以看到类 Example 的 productHelp 是严格尾递归的。假设一个静态编译器想把这个方法的正文转换成一个循环,如清单 3 所示:  

  清单 3. 静态编译不会优化尾调用的一个示例

int productHelp(Iterator i, int accumulator) { 
    while (i.hasNext()) { 
      accumulator *= ((Integer)i.next()).intValue(); 
    } 
    return accumulator; 
  } 

  于是,最初对 productHelp 的调用,结果成了对超类的方法的调用。超方法将通过简单地在 iterator 上循环来计算其结果。不会抛出任何异常。  

  用两个不同的静态编译器来编译这段代码,结果是一个会抛出异常,而另一个则不会,想想这是多么让人感到困惑。

时间: 2024-11-27 00:08:52

专家释疑:轻松提高Java代码的性能的相关文章

提高 Java 代码的性能

性能 尾递归转换能加快应用程序的速度,但不是所有的 JVM 都会做这种转换 很多算法用尾递归方法表示会显得格外简明.编译器会自动把这种方法转换成循环,以提高程序的性能.但在 Java 语言规范中,并没有要求一定要作这种转换,因此,并不是所有的 Java 虚拟机(JVM)都会做这种转换.这就意味着在 Java 语言中采用尾递归表示可能导致巨大的内存占用,而这并不是我们期望的结果.Eric Allen 在本文中阐述了动态编译将会保持语言的语义,而静态编译则通常不会.他说明了为什么这是一个重要问题,并

诊断 Java 代码: 提高 Java 代码的性能 尾递归转换能加快应用程序的速度,但不是所有的 JVM 都会做这种转换

简介: 很多算法用尾递归方法表示会显得格外简明.编译器会自动把这种方法转换成循环,以提高程序的性能.但在 Java 语言规范中,并没有要求一定要作这种转换,因此,并不是所有的 Java 虚拟机(JVM)都会做这种转换.这就意味着在 Java 语言中采用尾递归方法将导致巨大的内存占用,而这并不是我们期望的结果.Eric Allen 在本文中阐述了动态编译将会保持语言的语义,而静态编译则通常不会.他说明了为什么这是一个重要问题,并提供了一段代码来帮助判断您的即时(JIT)编译器是否会在保持语言语义的

诊断Java代码: 提高Java代码的性能

很多算法用尾递归方法表示会显得格外简明.编译器会自动把这种方法转换成循环,以提高程序的性能.但在 Java 语言规范中,并没有要求一定要作这种转换,因此,并不是所有的 Java 虚拟机(JVM)都会做这种转换.这就意味着在 Java 语言中采用尾递归方法将导致巨大的内存占用,而这并不是我们期望的结果.Eric Allen 在本文中阐述了动态编译将会保持语言的语义,而静态编译则通常不会.他说明了为什么这是一个重要问题,并提供了一段代码来帮助判断您的即时(JIT)编译器是否会在保持语言语义的同时做尾

实例jie如何提高Java Web 服务性能优化实践

本文介绍如何提升 Java Web 服务性能,主要介绍了三种方法:一是采用 Web 服务的异步调用,二是引入 Web 服务批处理模式,三是压缩 SOAP 消息.重点介绍在编程过程中如何使用异步 Web 服务以及异步调用和同步调用的差异点.本文还示范了如何在项目中使用以上三种方法,以及各种方法所适合的应用场景. Java Web 服务简介 Web 服务是一种面向服务架构的技术,通过标准的 Web 协议提供服务,目的是保证不同平台的应用服务可以互操作.Web 服务(Web Service)是基于 X

提高 Java 代码性能的各种技巧

Java 6,7,8 中的 String.intern – 字符串池 这篇文章将要讨论 Java 6 中是如何实现 String.intern 方法的,以及这个方法在 java 7 以及 Java 8 中做了哪些调整. 字符串池 字符串池(有名字符串标准化)是通过使用唯一的共享 String 对象来使用相同的值不同的地址表示字符串的过程.你可以使用自己定义的 Map<String, String> (根据需要使用 weak 引用或者 soft 引用)并使用 map 中的值作为标准值来实现这个目

提高Java代码重用性的三个措施

措施一:改写类的实例方法 通过类继承实现代码重用不是精确的代码重用技术,因此它并不是最理想的 代码重用机制.换句话说,如果不继承整个类的所有方法和数据成员,我们无法 重用该类里面的单个方法.继承总是带来一些多余的方法和数据成员,它们总是 使得重用类里面某个方法的代码复杂化.另外,派生类对父类的依赖关系也使得 代码进一步复杂化:对父类的改动可能影响子类;修改父类或者子类中的任意一 个类时,我们很难记得哪一个方法被子类覆盖.哪一个方法没有被子类覆盖;最 后,子类中的覆盖方法是否要调用父类中的对应方法

小技巧轻松提高U盘写入性能

  主流的U盘已经8G.16G了,写入速度相对显得不足,现在提供一个方法,可以修改一下系统就大大提升Windows下的U盘的性能. 这个办法跟我们所知的提升SATA硬盘性能的方法差不多,也就是开启"高级写入缓存(advanced write caching)",这种方法修改之后会增大作为写入缓存的系统内存容量,从而提升写入U盘的性能.不过它并不能让你更快的塞满一个8G大小的U盘,而是使得U盘对其他系统进程的响应速度更快,对于向U盘传输大量小文件或者是在优盘上运行某个应用程序的情形下性能

诊断Java代码

诊断Java代码: Broken Dispatch错误模式 诊断Java代码: Double Descent错误模式 诊断Java代码: Impostor Type错误模式 诊断Java代码: Java编程中的断言和时态逻辑 诊断Java代码: Liar View错误模式 诊断Java代码: Repl提供交互式评价 诊断Java代码: 单元测试与自动化代码分析协同工作 诊断Java代码: 将时态逻辑用于错误模式 诊断Java代码: 进行记录器测试以正确调用方法 诊断Java代码: 空标志错误模式

如何优化JAVA程序设计和编码,提高JAVA性能

通过使用一些辅助性工具来找到程序中的瓶颈,然后就可以对瓶颈部分的代码进行优化.一般有两种方案:即优化代码或更改设计方法.我们一般会选择后者,因为不去调用以下代码要比调用一些优化的代码更能提高程序的性能.而一个设计良好的程序能够精简代码,从而提高性能. 下面将提供一些在JAVA程序的设计和编码中,为了能够提高JAVA程序的性能,而经常采用的一些方法和技巧. 1.对象的生成和大小的调整. JAVA程序设计中一个普遍的问题就是没有好好的利用JAVA语言本身提供的函数,从而常常会生成大量的对象(或实例)