在java中 使用最频繁 同时也是滥用最多的一个类或许就是java lang String 它也是导致代码性能低下最主要的原因之一 请考虑下面这个例子
String s = Testing String String s = Concatenation Performance String s = s + + s
几乎所有的Java程序员都知道上面的代码效率不高 那么 我们应该怎么办呢?也许可以试试下面这种代码
StringBuffer s = new StringBuffer()s append( Testing String )s append( )s append( Concatenation Performance )String s = s toString()
这些代码会比第一个代码片段效率更高吗?答案是否定的 这里的代码实际上正是编译器编译第一个代码片段之后的结果 既然与使用多个独立的String对象相比 StringBuffer并没有使代码有任何效率上的提高 那为什么有那么多Java书籍批评第一种方法 推荐使用第二种方法?
第二个代码片段用到了StringBuffer类(编译器在第一个片段中也将使用StringBuffer类) 我们来分析一下StringBuffer类的默认构造函数 下面是它的代码
public StringBuffer() { this( )}
默认构造函数预设了 个字符的缓存容量 现在我们再来看看StringBuffer类的append()方法
public synchronized StringBuffer append(String str) {if (str == null) { str = String valueOf(str) } int len = str length() int newcount = count + len if (newcount >value length) expandCapacity(newcount) str getChars( len value count) count = newcountreturn this}
append()方法首先计算字符串追加完成后的总长度 如果这个总长度大于StringBuffer的存储能力 append()方法调用私有的expandCapacity()方法 expandCapacity()方法在每次被调用时使StringBuffer存储能力加倍 并把现有的字符数组内容复制到新的存储空间
在第二个代码片段中(以及在第一个代码片段的编译结果中) 由于字符串追加 *** 作的最后结果是 Testing String Concatenation Performance 它有 个字符 StringBuffer的存储能力必须扩展两次 从而导致了两次代价昂贵的复制 *** 作 因此 我们至少有一点可以做得比编译器更好 这就是分配一个初始存储容量大于或者等于 个字符的StringBuffer 如下所示
StringBuffer s = new StringBuffer( )s append( Testing String )s append( )s append( Concatenation Performance )String s = s toString()
再考虑下面这个例子
String s = int sum = for(int I= I<I++) { sum += I s = s + + +I }s = s + = + sum
分析一下为何前面的代码比下面的代码效率低
StringBuffer *** = new StringBuffer()int sum = for(int I= I<I++){ sum + = I *** append(I) append( + ) }String s = *** append( = ) append(sum) toString()
原因就在于每个s = s + + + I *** 作都要创建并拆除一个StringBuffer对象以及一个String 对象 这完全是一种浪费 而在第二个例子中我们避免了这种情况
我们再来看看另外一个常用的Java类——java util Vector 简单地说 一个Vector就是一个java lang Object实例的数组 Vector与数组相似 它的元素可以通过整数形式的索引访问 但是 Vector类型的对象在创建之后 对象的大小能够根据元素的增加或者删除而扩展 缩小 请考虑下面这个向Vector加入元素的例子
Object obj = new Object()Vector v = new Vector( )for(int I= I<I++) { v add( obj)}
除非有绝对充足的理由要求每次都把新元素插入到Vector的前面 否则上面的代码对性能不利 在默认构造函数中 Vector的初始存储能力是 个元素 如果新元素加入时存储能力不足 则以后存储能力每次加倍 Vector类就象StringBuffer类一样 每次扩展存储能力时 所有现有的元素都要复制到新的存储空间之中 下面的代码片段要比前面的例子快几个数量级
Object obj = new Object()Vector v = new Vector( )for(int I= I<I++) { v add(obj)}
同样的规则也适用于Vector类的remove()方法 由于Vector中各个元素之间不能含有 空隙 删除除最后一个元素之外的任意其他元素都导致被删除元素之后的元素向前移动 也就是说 从Vector删除最后一个元素要比删除第一个元素 开销 低好几倍
假设要从前面的Vector删除所有元素 我们可以使用这种代码
for(int I= I<I++){ v remove( )}
但是 与下面的代码相比 前面的代码要慢几个数量级
for(int I= I<I++){ v remove(v size() )}
从Vector类型的对象v删除所有元素的最好方法是
v removeAllElements()
假设Vector类型的对象v包含字符串 Hello 考虑下面的代码 它要从这个Vector中删除 Hello 字符串
String s = Hello int i = v indexOf(s)if(I != ) v remove(s)
这些代码看起来没什么错误 但它同样对性能不利 在这段代码中 indexOf()方法对v进行顺序搜索寻找字符串 Hello remove(s)方法也要进行同样的顺序搜索 改进之后的版本是
String s = Hello int i = v indexOf(s)if(I != ) v remove(i)
这个版本中我们直接在remove()方法中给出待删除元素的精确索引位置 从而避免了第二次搜索 一个更好的版本是
String s = Hello v remove(s)
最后 我们再来看一个有关Vector类的代码片段
for(int I= I<v size()I+=){}如果v包含 个元素 这个代码片段将调用v size()方法 次 虽然size方法是一个简单的方法 但它仍旧需要一次方法调用的开销 至少JVM需要为它配置以及清除堆栈环境 在这里 for循环内部的代码不会以任何方式修改Vector类型对象v的大小 因此上面的代码最好改写成下面这种形式
int size = v size()for(int I= I<sizeI++){}
lishixinzhi/Article/program/Java/gj/201311/27752
看懂程序的性能
对客户端程序而言 拙劣的性能会严重影响用户体验 界面停顿 抖动 响应迟钝等问题会遭到用户不停的抱怨 一个典型的例子就是Eclipse IDE工具在Full GC时会出现程序假死现象 相信一定被不少开发人员所诟病 对于服务器程序来说 性能问题则更为重要 相信不少后台服务器软件都有各自的性能目标 以Web服务器为例 服务器的响应时间 吞吐量就是两个重要的性能参数 当服务器承受巨大的访问压力时 可能出现响应时间变长 吞吐量下降 甚至是抛出内存溢出异常而崩溃 这些问题 都是性能调优需要解决的
一般来说 程序的性能通过以下几个方面来表现
执行速度 程序的反映是否迅速 响应时间是否足够短
内存分配 内存分配是否合理 是否过多地消耗内存或者存在泄漏
启动时间 程序从运行到可以正常处理业务需要花费多长时间
负载承受能力 当系统压力上升时 系统的执行速度 响应时间的上升曲线是否平缓
返回目录 Java程序性能优化 让你的Java程序更快 更稳定
编辑推荐
Visual C++音频/视频技术开发与实战
Oracle索引技术
lishixinzhi/Article/program/Java/gj/201311/27850
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)