科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道Java进阶:减少对象的创建提高java性能

Java进阶:减少对象的创建提高java性能

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

Java进阶:减少对象的创建提高java性能

作者:baocl 来源:赛迪网技术社区 2007年11月17日

关键字: 提高性能 减少创建 java

  • 评论
  • 分享微博
  • 分享邮件

许多通常的 Java 性能问题都起源于在设计过程早期中的类设计的思想, 早在许多开发者开始考虑性能问题之前. 在这个系列中, Brian Goetz 讨论了通常的 Java 性能上的冒险以及怎么在设计时候避免它们. 在第二部分, 他讨论了减少临时对象创建的一些技术。
                                                                                    
  虽然许多程序员把性能管理一直推迟到开发过程的最后, 性能考虑应该从第一天起就和设计周期结合在一起. 这个系列探索一些早期的设计思想能够极大影响应用程序性能的方法.在这篇文章里, 我继续探索大量临时对象创建的问题, 并且提供一些避免它们的一些技术.

  临时对象就是一些生命周期比较短的对象, 一般用于保存其他数据而再没有其他用途. 程序员一般用临时变量向一个方法传递数据或者从一个方法返回数据. 第一部分探讨了临时对象是怎样给一个程序的性能带来负面的冲击, 以及一些类接口的设计思想怎样提供了临时对象的创建. 避免了那些接口的创建, 你就能极大地减少那些影响你的程序性能的临时对象创建的需求         

  只是对 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()) 在许多数学操作中都有, 在一个地方操作比创建上百个临时变量性能提高非常大.

  结论

  所有的性能优化的建议中, 值得记住的是有许多程序的性能可以完全接受的情况. 在这些情况下, 不值得牺牲可读性, 可维护性, 抽象, 或者其他可取的程序属性来获得性能. 但是, 既然许多性能问题的种子在设计时就种下了, 要注意到设计思想潜在地对性能的冲击,当你设计的类在关注性能的情况使用, 你可以有效地使用这里提到的技巧来减少临时对象的创建

查看本文来源
    • 评论
    • 分享微博
    • 分享邮件
    闂傚倸鍊搁崐鎼佸磹妞嬪孩顐介柨鐔哄Т閻骞栧ǎ顒€濡肩紒鎰殜閺岋繝宕堕埡浣锋睏闂佸搫顑呴柊锝夊蓟閺囷紕鐤€閻庯綆浜炴禒鐐節濞堝灝鐏犻柕鍫熸倐瀵寮撮敍鍕澑闁诲函缍嗘禍鏍磻閹捐鍐€妞ゆ挶鍔庣粙蹇涙⒑鐠恒劌娅愰柟鍑ゆ嫹

    婵犵數濮烽弫鍛婃叏閻戝鈧倹绂掔€n亞鍔﹀銈嗗坊閸嬫捇鏌涢悢閿嬪仴闁糕斁鍋撳銈嗗坊閸嬫挾绱撳鍜冭含妤犵偛鍟灒閻犲洩灏欑粣鐐烘⒑瑜版帒浜伴柛鎾寸洴閹儳煤椤忓應鎷洪梻鍌氱墛閸楁洟宕奸妷銉ф煣濠电姴锕ょ€氼參宕h箛鏃傜瘈濠电姴鍊绘晶娑㈡煕鐎c劌濡介柕鍥у瀵粙濡歌閳ь剚甯¢弻鐔兼寠婢跺﹥娈婚梺鍝勭灱閸犳牠骞冨⿰鍫濈厸闁稿本绋撹ぐ瀣煟鎼淬値娼愭繛鍙壝悾婵堢矙鐠恒劍娈鹃梺鍓插亝濞叉牠鎮″☉銏$厱閻忕偛澧介惌瀣箾閸喐鍊愭慨濠勭帛閹峰懐绮电€n亝鐣伴梻浣规偠閸斿宕¢崘鑼殾闁靛繈鍊曢崘鈧銈嗗姂閸庡崬鐨梻鍌欑劍鐎笛呯矙閹寸姭鍋撳鐓庡籍鐎规洑鍗冲畷鍗炍熼梹鎰泿闂備線娼ч悧鍡涘箠鎼淬垺鍙忔い鎺嗗亾闁宠鍨块崺銉╁幢濡炲墽鍑规繝鐢靛О閸ㄦ椽鏁嬮柧鑽ゅ仦娣囧﹪濡堕崨顔兼闂佺ǹ顑呴崐鍦崲濞戙垹骞㈡俊顖濐嚙绾板秹鏌f惔銏e妞わ妇鏁诲璇差吋閸偅顎囬梻浣告啞閹搁箖宕版惔顭戞晪闁挎繂顦介弫鍡椼€掑顒婂姛闁活厽顨嗙换娑㈠箻閺夋垹鍔伴梺绋款儐閹瑰洭寮婚敐鍛婵炲棙鍔曠壕鎶芥⒑閸濆嫭婀扮紒瀣灴閸╃偤骞嬮敃鈧婵囥亜閺囩偞鍣洪柍璇诧功缁辨捇宕掑▎鎴濆濡炪們鍔岄幊姗€骞嗗畝鍕<闁绘劙娼х粊锕傛煙閸忚偐鏆橀柛鏂跨焸閹偤宕归鐘辩盎闂佸湱鍎ら崹鐢割敂閳哄懏鍊垫慨姗嗗墻濡插綊鏌曢崶褍顏€殿喕绮欐俊姝岊槼闁革絻鍎崇槐鎾存媴缁涘娈┑鈽嗗亝缁诲牆顕f繝姘亜缁炬媽椴搁弲锝夋偡濠婂啰效闁诡喗锕㈤幊鐘活敆閸屾粣绱查梺鍝勵槸閻楀嫰宕濇惔锝囦笉闁绘劗鍎ら悡娑㈡倶閻愯泛袚闁哥姵锕㈤弻鈩冩媴閻熸澘顫掗悗瑙勬礈閸犳牠銆佸鈧幃鈺呮惞椤愩倝鎷婚梻鍌氬€峰ù鍥х暦閸偅鍙忛柟鎯板Г閳锋梻鈧箍鍎遍ˇ顖炲垂閸岀偞鐓㈡俊顖滃皑缁辨岸鏌ㄥ┑鍡╂Ц缂佲偓鐎n偁浜滈柡宥冨妿閳藉绻涢崼鐔虹煉婵﹨娅e☉鐢稿川椤斾勘鈧劕顪冮妶搴′簼婵炶尙鍠栧畷娲焵椤掍降浜滈柟鍝勬娴滈箖姊洪幐搴㈢┛濠碘€虫搐鍗遍柟鐗堟緲缁秹鏌涢锝囩畼妞ゆ挻妞藉铏圭磼濡搫顫岄悗娈垮櫘閸撴瑨鐏冮梺鍛婁緱閸犳岸宕㈤幖浣光拺闁告挻褰冩禍浠嬫煕鐎n亜顏柟顔斤耿閺佸啴宕掑☉姘箞闂佽鍑界紞鍡涘磻閸℃ɑ娅犳い鎺戝€荤壕濂告煕鐏炲墽鈽夌紒妞﹀洦鐓欓柣鐔告緲椤忣參鏌熼悡搴㈣础闁瑰弶鎸冲畷鐔兼濞戞瑦鐝¢梻鍌氬€搁崐椋庣矆娓氣偓楠炴牠顢曢妶鍌氫壕婵ê宕崢瀵糕偓瑙勬礀缂嶅﹪寮婚崱妤婂悑闁告侗鍨界槐閬嶆煟鎼达紕鐣柛搴ㄤ憾钘濆ù鍏兼綑绾捐法鈧箍鍎遍ˇ浼存偂閺囥垺鐓涢柛銉e劚婵$厧顭胯閸ㄤ即婀侀梺缁樓圭粔顕€顢旈崼鐔虹暢闂傚倷鐒︾€笛呮崲閸屾娑樜旈崨顓犲幒闂佸搫娲㈤崹娲偂閸愵亝鍠愭繝濠傜墕缁€鍫熸叏濡寧纭鹃柦鍐枛閺屾洘绻涜鐎氱兘宕戦妸鈺傗拺缂備焦锚婵洦銇勯弴銊ュ籍闁糕斂鍨藉鎾閳ユ枼鍋撻悽鍛婄叆婵犻潧妫楅埀顒傛嚀閳诲秹宕堕妸锝勭盎闂婎偄娲︾粙鎰板箟妤e啯鐓涢悘鐐靛亾缁€瀣偓瑙勬礋娴滃爼銆佸鈧幃銏$附婢跺澶�

    重磅专题
    往期文章
    最新文章