`
yzhming
  • 浏览: 33051 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

[转] java代码性能的提高

    博客分类:
  • java
 
阅读更多
简介
Java的诸多优点已经广为称道。特别是“一次编程,到处运行”的承诺使开发人员可以自由地进行跨平台应用程序的开发而不存在预处理器指令的开销。通常认为Java的弱点在于其性能方面。
在当前这种认识并不是完全正确的,有很多产品可以提高Java程序的性能并能够使其在很多应用程序中不再成为一个问题。例如,TowerJ是一种将Java字节代码转换成高度优化的本地可执行程序的后期编译器,Jrockit是一种具有自适应优化能力的服务器端的Java虚拟机。尽管如此,运用一些简单的技巧可以使你不必购买上述的这些工具也能够改进Java代码的性能。在文本中我将说明其中的一些。
本文的讨论主要基于那些高吞吐量的代码(服务器端)。鉴于主要的开销是由那些涉及到对象创建和再创建的GUI代 码所引起的,对服务器端代码进行有效的性能估算指针是方法的执行时间。因此,对于所涉及到的示例代码,我记录了执行方法所需的平均时间。记录一个方法的精 确执行时间并不是切实可行的,因此我对一系列方法进行计时并计算其平均值。这样做有效地模拟了那些以性能为关键的代码的执行。
每个示例都带有对字节代码操作进行解释的伪代码。所产生的实际的字节代码可以从CUJ的Web站点(www.cuj.com/code)获取。对所有字节代码的解释可以从Javasoft的站点获得。
改善字符串处理的性能
同C++一样,Java库中定义了自己的String类型。在其外表之下,这种类型是由一个char型数组所实现的,然而使用字符串并不需要理解这一点。NULL符(’/0’)是导致很多学生在学习和使用C++的过程中受挫的祸根;使用Java则不必为此分心,程序员可以专注于应用程序本身以及创建应用程序所用的工具。但是存在着与这种省心的字符串处理方式相关的不利方面,那就是字符串的连接操作符‘+’。
这个操作符看起来十分有用。多数需要向流写入数据的应用程序都使用‘+’。例如:
String name = new String("Joe");
System.out.println(name + " is my name.");
在上面的代码段中,看起来似乎在println语句中无法作出什么修改以改善其执行的速度。然而,这个语句所产生的字节代码(在此用伪代码表示)却揭示了事实,见程序清单1。

清单 1:描述由字符串连接符‘+’所产生的字节代码操作的伪代码
create new String (STR_1)
duplicate the String
load the constant String "Joe" (STR_2)
call String constructor
store this String in the local variable array position 0
get the static out field from the java.io.PrintStream class (OUT)
create a new StringBuffer (STR_BUF_1)
duplicate the StringBuffer
call StringBuffer constructor
store this StringBuffer in the local variable array position 1
invoke the append method on STR_BUF_1 with STR_1 as the argument
load the constant String " is my name." (STR_3)
invoke the append method on STR_BUF_1 with STR_3 as the argument
invoke toString method on STR_BUF_1 (STR_4)
invoke the println method on OUT

这段简单的代码创建了5个对象[注1]:STR_1, STR_2, STR_3, STR_4, and STR_BUF_1。
需要注意对象创建相对来讲是非常耗费资源的。必须为每个类和类的每一个超类的所有实例变量分配堆存储空间;所有的实例变量必须被初始化;而且类的构造函数和每个超类的构造函数必须被执行。为了创造高效的代码,只限于在绝对必须的情况下进行对象的创建是十分必要的。
那么,刚才的代码是否可以用一种更高效的方法重写呢?请考虑下面的程序段:
StringBuffer name = new StringBuffer("Joe");
System.out.println(name.append(" is my name.").toString());
相应的字节代码/伪代码请参见程序清单2。

清单2:使用StringBuffer的append操作符的字节代码/伪代码
create new StringBuffer (STR_BUF_1)
duplicate the StringBuffer
load the constant String "Joe" (STR_1)
call StringBuffer constructor
store this StringBuffer in the local variable array position 1
get the static out field from the java.io.PrintStream class (OUT)
load STR_BUF_1
load the constant String " is my name." (STR_2)
invoke the append method on STR_BUF_1 with STR_2 as the argument
invoke toString method on STR_BUF_1 (STR_3)
invoke the println method on OUT

上面的代码值创建了4个对象:STR_1, STR_2, STR_3, 和 STR_BUF_1。你可能会认为减少一个对象的创建并不能起到多大作用。然而,县棉的代码创建了8个对象:
String name = new String("Joe");
name+=" is my";
name+=" name.";
而这段代码仅创建了5个:
StringBuffer name = new StringBuffer("Joe");
name.append(" is my");
name.append(" name.").toString();
第二段代码执行的速度比第一段快两倍还多[注2]。
结论:使用StringBuffer来改进字符串处理代码的性能。其目标是使新对象的创建达到最少,这可以通过使用在StringBuffer之上使用append方法来代替在String上使用连接操作符来实现。
更快的日志记录(faster logging)
在我参与开发的每个软件项目中,都要求有一 种适当的日志记录机制。在应用程序中包括日志记录功能的原因有很多。主要的原因是为了使维护更加容易。为了在发行的应用程序中实现错误报告,有必要设置开 始点。在很多情况下,用户提交的报告含义不清,其描述的问题可能是由多方面因素造成的。如果有一种适当的机制能够使用户收集关于此问题的额外信息,那么解 决问题的周期会大大地缩减。
并没有标准的方法来产生这种信息,通常这有赖于开发人员如何适当地建立这种机制。然而,日志记录机制的实现对应用程序的性能会造成较大的影响。我们的目标是建立一种能够输出有价值的运行时信息但同时使其对运行时性能的影响达到最小的机制。
避免运行时开销的最显而易见的办法是不将日志记录包括在发行的应用程序中;换句话说,如果执行日志记录的实际代码没有编译到应用程序中的话,那么也就不会对性能造成影响。程序清单3展示了一个定义这样一种记录机制的类。可以对其进行设置使日志记录代码从所产生的字节代码中忽略。这个类将是一个单元素(Singleton)以避免创建不必要的Logger类的实例。

清单3:可以进行配置使其在发布版本中不产生代码的]单元素的Logger类
public class Logger {
// 这个类的实例
private static Logger theLogger = null;
// 调试消息(debug messages)的控制器,设为true时允许调试消息,false则反之
public static final boolean CAN_DEBUG = true;
/** 私有的构造函数——只允许一个实例 */
private Logger() {}
/** 返回这个类所创建的唯一实例 */
public static Logger getInstance() {
    if(theLogger == null) { theLogger = new Logger(); }
    return theLogger;
}
public void debugMsg(String msg) { System.out.println(msg); }
}
正如你所看到的,这个非常简单的类包括一个类型变量,一个类型常量,两个方法和一个构造函数。要使用这个类,只需简单地获取其实例,检查debug是否被启动,并调用debugMsg,如下所示:
...
Logger myLogger = Logger.getInstance();
...
if (Logger.CAN_DEBUG) {
   myLogger.debugMsg("some debug message");
}
设想Logger.CAN_DEBUG为false。当创建应用程序的时候,将会排除死代码同时不会有由此而产生的字节代码。这是因为编译器知道final static变量Logger.CAN_DEBUG总是为false。如果Logger.CAN_DEBUG为true,那么代码将会被编译并产生相应的字节代码。这样一来,开启了调试消息的编译将会导致产生更多的字节代码。
这种方法可以扩展到允许对所产生信息的更细微的处理。例如,可以声明一个新的static final的布尔型变量CAN_INFO,还可以实现一个新的public void infoMsg(String msg)方法。
从性能的角度来看,这是可以使用的最佳方法。几种不同的版本可以相互协作以反映所支持的不同层次的消息。例如,你可以发布一个产品版本和一个调试版本。如果在产品版本中出现了问题,则可以用调试版本与其交换以查明问题出现的所在。
这种方法的主要缺点在于无法在运行时进行设置,例如将其作为一个系统属性。
大多数日志记录机制中,主要的性能影响因素在于String对象的创建。这样的话,我们的目标应该是使这种开销达到最小。因此解决方法中需要包括StringBuffer。程序清单4中的Logger类提供了一种可配置的日志记录级别。

清单4:提供可配置日志记录级别Logger类
public class Logger {
...
// 信息消息的控制器
public static final int CAN_INFO = 1;
// 调试消息的控制器
public static final int CAN_DEBUG = 2;
// 调试级别——缺省为信息消息
public int LOG_LEVEL = 1;
...
public void setLogLevel(int level) {
    if(level >= 0) { LOG_LEVEL = level; }
}
/** 如果CAN_INFO位被设置则返回true */
public boolean canInfo() {
    if((LOG_LEVEL & CAN_INFO) == CAN_INFO) { return true; }
    return false;
}
/** 如果CAN_DEBUG位被设置则返回true */
public boolean canDebug() {
    if((LOG_LEVEL & CAN_DEBUG) == CAN_DEBUG) { return true; }
    return false;
}
public void debugMsg(String msg) { System.out.println(msg); }
public void infoMsg(String msg) { System.out.println(msg); }
}
如上的代码示例提供了一种两级的日志记录方法。它可以处理调试消息和信息消息,还可以很容易地扩展到处理更多的类型。这个类为日志记录机制提供了坚实的基础。
在应用程序中使用这种实现方法有两种选项。第一是创建一个实现简单API的基类,应用程序将对其进行扩展。第二是由应用程序实现一个定义了简单API的接口。下面是接口的示例:
public interface LogAPI {
   public void createMsg();
   public void appendLog(String str);
   public void appendLog(int i);
   public void logDebugMsg();
   public void logInfoMsg();
}
在TestLogger.java(程序清单5)中提供了这个接口的一个实现。

清单5:TestLogger.java—定义简单API的接口的示例实现
/* Copyright (c) 2000 Stepping Stone Software Ltd, John Keyes */
public class TestLogger implements LogAPI {
static Logger myLogger = Logger.getInstance();
StringBuffer msg = new StringBuffer();
public static void main(String args[]) {
    String strLevel = System.getProperty("app.loglevel");
    if(strLevel != null) {
    int level = Integer.parseInt(strLevel);
    myLogger.setLogLevel(level);
    }
    TestLogger testLog = new TestLogger();
    testLog.test();
}
TestLogger() { }
public void test() {
    int age = 24;
    String name = "Joe";
    if(myLogger.canDebug()) {
      createMsg();
      appendLog(" DEBUG/n Name:");
      appendLog(name);
      appendLog(" Age:");
      appendLog(age);
      logDebugMsg();
    }
    if(myLogger.canInfo()) {
      createMsg();
      appendLog(" INFO/n Name:");
      appendLog(name);
      appendLog(" Age:");
      appendLog(age);
      logInfoMsg();
    }
}

public void createMsg() { msg.setLength(0); }
public void appendLog(String str) { msg.append(str); }
public void appendLog(int i) { msg.append(i); }

public void logDebugMsg() {
    myLogger.debugMsg(msg.toString());
}
public void logInfoMsg() {
    myLogger.infoMsg(msg.toString());
}
}
对StringBuffer对象的重用是此处的关键。通常你会编写如下的代码作为调试消息:
debugMsg("Name:" + name + " Age:" + age);
如前文中所讨论过的,String类型的创建对性能产生不利的影响。如果像TestLogger.java中所示的那样重写,那么获得的性能提升将会是明显的。
日志记录的级别现在可以使用Logger类中定义的setLogLevel方法在运行时定制。在系统属性的帮助下完成这项工作是一个好主意。你必须定义自己的属性;在本例中它被命名为“app.loglevel”。如果所讨论的问题是一个应用程序,那么你可以对JVM使用-D开关设置“app.loglevel”属性[注3]。例如:
java –D app.loglevel=3 myApp
另一方面,如果你的程序是一个applet,可以使用
标签进行设置:
于是,为了设置日志记录级别,你所要做的一切就是获取属性值并对结果调用SetLogLevel方法:
String strLevel = System.getProperty("app.loglevel");
If(strLevel != null) {
   int level = Integer.parseInt(strLevel);
   Logger.getInstance().setLogLevel(level);
}
这种方法的好处在于:
l         减少对象创建,也就是说,对象被重用
l
分享到:
评论

相关推荐

    提高 Java 代码的性能

    提高 Java 代码的性能,互相学习。。。

    提高java代码性能各种技巧

    非常详细的代码性能提高技巧介绍总结,使代码更清晰明了!

    eclipse开发性能优化、java代码性能优化

    eclipse 开发 性能 优化 java 代码 性能 优化 特别从事android开发方面eclipse会很卡,这里提供比较全面的优化技巧,方便提高开发速度。

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

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

    java代码性能的优化

    编写代码规范,提高代码的质量和系统的性能

    java代码优化简介

    可供程序利用的资源(内存、CPU时间...养成好的代码编写习惯非常重要,比如正确地、巧妙地运用java.lang.String类和java.util.Vector类,它能够显著地提高程序的性能。下面我们就来具体地分析一下这方面的问题。 .....

    java程序性能优化

    第3章从代码层面介绍如何编写高性能的Java程序。第4章介绍了并行开发和如何通过多线程提高系统性能。第5章立足于JVM虚拟机层面,介绍如何通过设置合理的JVM参数提升Java程序的性能。第6章为工具篇,介绍了获取和监控...

    Java程序性能优化

    第3章从代码层面介绍如何编写高性能的Java程序。第4章介绍了并行开发和如何通过多线程提高系统性能。第5章立足于JVM虚拟机层面,介绍如何通过设置合理的JVM参数提升Java程序的性能。第6章为工具篇,介绍了获取和监控...

    java代码优化编程

    优化通常包含两方面的内容:减小代码的体积,提高代码的运行效率。 本文讨论的主要是如何提高代码的效率。 在 Java程序中,性能问题的大部分原因并不在于Java语言,而是在于程序本身。养成好的代码编写习惯非常重要...

    29个要点帮你完成java代码优化

    通过java代码规范来优化程序,优化内存使用情况,防止内存泄露 可供程序利用的资源(内存、CPU时间、网络带宽等)是有限的,优化的目的就是让程序用尽可能少的资源完成预定的任务。优化通常包含两方面的内容:减小...

    JAVA性能瓶颈和漏洞检测

    无须修改应用,JProbe就能对桌面或远程服务器上的应用进行分析,实现强大的信息展示和Java代码性能诊断功能。利用JProbe先进的数据收集功能,可以实现自动化的性能信息采集,缩短应用开发和优化周期。 JProbe在简单...

    一本介绍如何将现有的 Java 代码重构为 Kotlin 代码的书籍

    以下是该书的主要特点和内容概要: 适合 Java 开发者: 该书适合已经熟悉 Java ...代码质量和性能: 书中还涵盖了如何通过 Kotlin 的特性来提高代码质量和性能,包括空安全性、扩展函数、Lambda 表达式等方面的内容。

    JAVA上百实例源码以及开源项目源代码

     Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员。 Java访问权限控制源代码 1个目标文件 摘要:Java源码,...

    java性能的优化-如何提高java运行效率

    通过java源代码,详细描述了如何提高java运行效率,

    java源码包---java 源码 大量 实例

     Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员。 Java访问权限控制源代码 1个目标文件 摘要:Java源码,...

    JAVA上百实例源码以及开源项目

     Java二进制IO类与文件复制操作实例,好像是一本书的例子,源代码有的是独立运行的,与同目录下的其它代码文件互不联系,这些代码面向初级、中级Java程序员。 Java访问权限控制源代码 1个目标文件 摘要:Java源码,...

Global site tag (gtag.js) - Google Analytics