只是对 String 说不吗?
当它要创建临时变量时, String 类是最大的罪人中的一个. 为了演示, 在第一部分我写了一个正规表达式匹配的例子, 通过和一个类似的但是经过仔细设计的接口相比较, 演示了看起来无害的接口是怎样引起大量对象的创建, 而慢上几倍. 这里是原来的和好一些的类的接口:
坏的正则表达式匹配实例: public class BadRegExpMatcher { public BadRegExpMatcher(String regExp); public String match(String inputText); }
好的正则表达式匹配实例:
class BetterRegExpMatcher { public BetterRegExpMatcher(...);//省略了一些
public int match(String inputText); public int match(char[] inputText); public int match(char[] inputText, int offset, int length); public int getMatchLength(); public String getMatchText(); } 大量使用 BadREgExpMatcher 的程序比使用 BtterRegExpMatcher 的要慢好多. 首先,调用者不得不创建一个 String 传入 match(), 接着 match() 又创建了一个 String 来返回匹配的文本. 结果是每次调用都有两个对象创建, 看起来不多, 但是如果要经常调用match(), 这些对象创建带给性能的代价就太大了. BadRegExpMatcher 的性能问题不是在它的实现中, 而是它的接口; 象它定义的接口, 没有办法避免一些临时变量的创建。
BetterRegExpMatcher 的 match() 用原类型(整数和字符数组)代替了 String 对象; 不需要创建中间对象来在调用者和 match() 之间传递信息.
既然在设计时候避免性能问题要比写完整个系统以后再修改要容易一些, 你应该注意你的类中控制对象创建的方法. 在 RegExpMatcher 的例子中, 它的方法要求和返回 String 对象, 就应该为潜在的性能冒险提个警告信号. 因为 String 类是不可变的, 除了最常用以外, 所有的 String 参数在每次调用处理函数时都需要创建一个新的 String.
不可变性对于性能来说是否很坏?
因为 String 经常和大量的对象创建联系在一起, 一般来说归咎于它的不可变性. 许多程序员认为不可变的对象与生俱来对性能没有好处. 但是, 事实多少会更复杂一些. 实际上, 不可变性有时候提供了性能上的优势, 可变性的对象有时候导致性能问题. 不管可变性对性能来说有帮助或者有害, 依赖于对象是怎么使用的.
程序经常处理和修改文本字符串 -- 这和不可变性非常不匹配。每次你想处理一个 String --想查找和解析出前缀或者子串, 变小写或者大写, 或者把两个字符串合并 -- 你必须创建一个新的 String 对象. (在合并的情况下, 编译器也会隐藏地创建一个 StringBuffer() 对象)
另一个方面, 一个不可变的对象的一个引用可以自由共享, 而不用担心被引用的对象要被修改, 这个比可变对象提供性能优势, 就象下一节例子所说的。
可变的对象有它们自己的临时对象问题.
在 RegExpMatcher 的例子中, 你看见了 当一个方法返回一个 String 类型时, 它通常需要新建一个 String 对象. BadRegExpMatcher 的一个问题就是 match() 返回一个对象而不是一个原类型 -- 但是只因为一个方法返回一个对象, 不意味着必须有一个新对象创建. 考虑一下 java.awt 中的几何类, 象 Point 和 Rectangle. 一个 Rectangle只是四个整数(x, y, 宽度, 长度)的容器, AWT Component 类存储组件的位置, 通过getBounds()作为一个Rectangle 返回
public class Component { ... public Rectangle getBounds(); }
在上面的例子中, getBounds() 只是一个存储元 -- 它只使一些 Component 内部的一些状态信息可用. getBounds() 需要创建它返回的 Rectangle 吗? 可能. 考虑一下下面getBounds() 可能的实现.
public class Component { ... protected Rectangle myBounds; public Rectangle getBounds() { return myBounds; } }
当一个调用者调用上面例子中的 getBounds(), 没有新对象创建 -- 因为组件已经知道它在哪里 -- 所以 getBounds() 效率很高. 但是 Rectangle 的可变性又有了其他问题. 当一个调用者运行一下程序会发生什么呢?
Rectangle r = component.getBounds(); ... r.height *= 2;
因为 Rectangle 是可变的, 它在 Component 不知道的情况下使 Component 移动. 对象AWT 这样的 GUI 工具箱来说, 这是个灾难, 因为当一个组件移动以后, 屏幕需要重绘, 组件监听器需要被通知, 等等. 所以上面的实现 Component.getBounds() 的代码看起来很危险. 一个安全一点的实现就象下面这样:
public Rectangle getBounds() { return new Rectangle(myBounds.x, myBounds.y, myBounds.height, myBounds.width); }
但是现在, 每一个 getBounds() 的调用都创建一个新对象, 就象 RegExpMatcher 一样.实际上, 下面的代码片段创建了 4 个临时对象:
int x = component.getBounds().x; int y = component.getBounds().y; int h = component.getBounds().height; int w = component.getBounds().width;
在 String 的情况中, 对象创建是必要的, 因为 String 是不可变的. 但在这个例子中,对象的创建也是必要的, 因为 Rectangle 是可变的. 我们使用 String 避免了这个问题,在我们的接口中没有使用对象. 虽然在 RegExpMatcher 的情况下很好, 这个方法不总是可行的或者是希望的. 幸运的是, 你可以在实际类的时候可以使用一些技巧, 来免除太多小对象的问题, 而不是完全避免小对象.
减少对象的技巧 1: 加上好的存取函数
在 Swing 工具箱的初始版本中, 小对象的临时创建, 象 Point, Rectangle 和 Dimension极大地阻碍了性能. 把它们放在一个 Point 或者 Rectangle 中来一次返回多个值, 看起来更有效, 实际上, 对象的创建比多个方法调用代价更高. 在 Swing 的最后发布之前, 通过给 Component 和其他一些类加一些新的存取方法, 问题就简单地解决了, 就象下面这样:
public int getX() { return myBounds.x; } public int getY() { return myBounds.y; } public int getHeight() { return myBounds.height; } public int getWidth() { return myBounds.width; }
现在一个调用者可以这样获取边界而不用创建对象:
int x = component.getX(); int y = component.getY(); int h = component.getHeight(); int w = component.getWidth();
getBounds() 的旧形式仍然支持; 好的存取方法简单地提供了有效的方法来达到相同的目的. 结果是, Rectangle 的接口全部在 Component 中使用. 当修改 Swing 包支持和使用这样的存取函数后, 在许多 Swing 操作中比以前要快到两倍. 这很好, 因为 GUI 代码非常注意性能 -- 用户等待发生一些事, 希望 UI 操作瞬间完成.
使用这个技术不好的地方就是你的对象提供了更多的方法, 有多于一个的方法来得到相同的信息, 就使文档更大更复杂, 可能使用户害怕. 但是就象 Swing 的例子显示的, 在关注性能的情况下, 这样的优化技术是有效的.
技巧 2: 利用可变性
除了给 Component 加上原类型的存储函数 -- 象上面讨论的 getX() 函数 -- 以外, Java 2 在 AWT 和 Swing 中也使用了另一种技术来减少对象创建, 允许一个调用者把边界作为一个 Rectangle 得到, 但是不需要任何临时对象的创建.
public Rectangle getBounds(Rectangle returnVal) { returnVal.x = myBounds.x; returnVal.y = myBounds.y; returnVal.height = myBounds.height; returnVal.width = myBounds.width; return returnVal; }
调用者仍然需要创建一个 Rectangle 对象, 但它可以在后来的调用中重用. 如果一个调用者在一系列的 Component 中循环, 可以只创建一个 Rectangle 对象, 在每个 Component 中重用. 注意这个技术只用于可变性对象; 你不能用这种方法消除 String 的创建.
技巧 3: 得到两个中的最好的.
一个解决在简单类(象 Point 之类)的对象创建的问题, 更好的方法是使 Point 对象不可变,但是定义一个可变的子类, 就象下面这样:
public class Point { protected int x, y; public Point(int x, int y) { this.x = x; this.y = y; } public final int getX() { return x; } public final int getY() { return y; } }
public class MutablePoint extends Point { public final void setX(int x) { this.x = x; } public final void setY(int y) { this.y = y; } }
public class Shape { private MutablePoint myLocation; public Shape(int x, int y) { myLocation = new MutablePoint(x, y); } public Point getLocation() { return (Point) myLocation; } }
在上面的例子中, Shape 可以安全返回一个 myLocation 的引用, 因为调用者试图修改域或者调用设置函数会失败. (当然, 调用者仍然可以把 Point 转换为 MutablePoint, 但这明显不安全, 这样的调用者可能得到他们想要的) 。
这个技巧 -- 返回一个具有可变的和不可变的类, 只允许读对象, 而不创建新对象 --在 Java 1.3 类库 java.math.BigInteger 类中使用. MutableBigInteger 类不可见 --它是一个只在 java.math 类库中内部使用的私有类型. 但是既然 BigInteger 的一些方法(象 gcd()) 在许多数学操作中都有, 在一个地方操作比创建上百个临时变量性能提高非常大.
结论
所有的性能优化的建议中, 值得记住的是有许多程序的性能可以完全接受的情况. 在这些情况下, 不值得牺牲可读性, 可维护性, 抽象, 或者其他可取的程序属性来获得性能. 但是, 既然许多性能问题的种子在设计时就种下了, 要注意到设计思想潜在地对性能的冲击,当你设计的类在关注性能的情况使用, 你可以有效地使用这里提到的技巧来减少临时对象的创建
|
相关推荐
在Java编程中,性能优化是不...总的来说,减少对象创建是Java性能优化的重要策略,但这需要在不影响代码清晰度和抽象层次的前提下进行。设计时应权衡性能和可维护性,合理利用各种工具和技术,确保程序高效且易于理解。
3. **减少对象创建**:频繁创建和销毁对象会增加垃圾收集的负担,尽量重用对象或使用基本类型和数组代替对象,特别是在循环和高频率调用的代码段中。 4. **使用final修饰符**:final类和方法可以提高编译器的优化...
- **代码效率**:编写高效代码,避免不必要的计算和资源浪费,优化循环结构,减少对象创建。 - **JVM调优**:调整JVM参数,如堆大小、新生代与老年代比例、GC策略等。 - **数据库优化**:优化SQL查询,合理设计数据...
此外,还可以通过其他手段提升Java性能,例如减少对象创建、优化数据结构、利用并发和多线程、使用适当的算法以及进行性能测试和分析等。在实际开发中,结合代码重构和持续优化,能够帮助Java应用达到更好的性能表现...
常见的Java性能瓶颈包括垃圾收集、数据库连接管理、线程同步问题、过度的对象创建等。了解这些瓶颈有助于我们针对性地优化。 提高Java程序性能的步骤大致分为六步: a) 明确性能目标:在项目开始前,明确性能指标...
Java性能优化是一个涵盖广泛的主题,它涉及到代码质量、内存管理、并发处理、数据库交互等多个方面。以下是一些关键的知识点,可以帮助你提升Java程序的性能: 1. **代码优化**: - **避免过度设计**:简洁的代码...
在代码层面,书中会讲解如何编写高效且无副作用的Java代码,包括避免过度使用同步、合理使用数据结构、优化循环和减少不必要的对象创建等。这些实践原则有助于编写出更健壮、性能更佳的程序。 另外,书中还涵盖了...
- **减少对象创建**:避免不必要的对象实例化,可以采用对象池等方式复用对象。 - **弱引用与软引用**:利用JVM提供的弱引用和软引用来辅助内存管理,实现更加灵活的对象生命周期控制。 #### 并发编程 - **线程池...
4. **减少对象创建**:过多的对象创建会导致垃圾收集频繁,考虑复用或使用池化技术。 5. **避免大量使用synchronized**:同步锁会降低并发性能,可考虑使用ConcurrentHashMap等并发容器。 6. **使用适当的集合类型**...
### 学习笔记之对象的创建(Java) #### Java技术与Java虚拟机 Java作为一种广泛使用的编程语言,其独特之处在于“一次编写,到处运行”的理念。这一特性主要得益于Java虚拟机(JVM)的存在。Java技术主要包括以下几...
2. **减少对象创建**:尽量复用对象,减少不必要的new操作,例如使用StringBuilder而非String进行字符串拼接。 3. **TLAB优化**:利用线程私有的分配缓冲区,减少多线程环境下的内存竞争。 4. **对象初始化优化**...
1. **设计模式**:合理运用设计模式可以避免不必要的性能损耗,例如使用工厂模式可以减少对象创建的开销。 2. **组件选择**:选择合适的数据结构和库可以显著提高性能,例如使用`ConcurrentHashMap`替代`...
1. 频繁的对象创建与销毁:过多的对象创建会增加垃圾回收的压力,影响性能。 2. 数据库查询效率:不当的SQL语句、索引缺失或不合理的连接管理可能导致性能下降。 3. 内存溢出:程序占用内存过多可能导致系统响应变慢...
例如,使用工厂模式时,可以预先创建一个基础对象并使用`clone()`来快速生成新实例,以减少对象创建的时间。 2. **非阻塞I/O**:在JDK较新版本中,提供了非阻塞I/O(NIO)API,以避免传统阻塞I/O导致的线程等待问题...
Java性能优化是提升软件效率和用户体验的关键环节,尤其是在大规模应用和高并发场景中。本教程将深入探讨如何通过各种技术和策略来优化Java程序,确保其高效运行。 首先,理解Java性能的基础是JVM(Java虚拟机)。...
Java性能优化通常指的是对Java应用程序进行一系列修改,以提高其运行效率和降低资源消耗。性能优化可能包括但不限于代码优化、垃圾收集调优、内存管理、线程优化等多方面。Java性能优化的目的是为了让程序运行更快、...
3. **减少对象创建**:频繁创建对象会增加内存分配和垃圾回收的压力。应尽量重用对象,或者使用基本数据类型和数组代替对象,特别是在循环和高频调用的函数中。 4. **利用final修饰符**:final类和方法能提升性能,...
解决方法包括:合理设置JVM堆大小,避免创建大量长生命周期的对象,及时释放不再使用的对象,检查并修复内存泄漏,使用内存分析工具定位问题源等。 总的来说,这个“Java性能调优大全”涵盖了从基础的JVM配置到复杂...
- **减少对象创建**:通过对象池、缓存和重用已存在的对象来避免频繁创建新对象。 - **有效使用集合类**:根据需求选择适合的数据结构,如ArrayList和LinkedList在不同场景下的性能差异。 - **避免过度同步**:同步...