【问题标题】:get/setPreferredSize without allocating a Dimension object every timeget/setPreferredSize 无需每次都分配 Dimension 对象
【发布时间】:2013-05-22 10:26:42
【问题描述】:

有没有一种很好的方法来使用 set/getPreferredSize、set/getMinimumSize 和 set/getMaximumSize(或绕过它们的方法),而无需不时分配 Dimension 对象? (除了扩展 JComponents)

我的应用程序在鼠标拖动时大量使用此方法来调整大小/移动,我担心周围的所有 Dimension 对象。

我知道 set/getBounds 之类的方法可以处理传递给它们的已分配对象,但我似乎找不到其他方法的等效方法。

【问题讨论】:

  • 这不应该是一个问题 - 永远。 Dimension 对象的重量非常轻。
  • 在为 android 编程时,即使是迭代器也会限制在渲染循环中使用列表(很快就会出现 OutOfMemoryException)。虽然这不是一个与 android 相关的问题,但我只是说这并不是在渲染循环上的微优化。
  • 如果这不是一个问题,那么他们为什么要努力制作 getBounds(Rectangle rv)、getLocation(Point p) 和 getSize(Dimension d)?简短的回答是,Preferred/Minimum/Maximum 不会经常更改。在我的特殊情况下该怎么做?这就是问题所在。

标签: java performance swing memory allocation


【解决方案1】:

我对 JComponent 的 get/setPreferredSize 机制和分配行为感兴趣

注意:我对默认布局管理器的工作方式感兴趣。让我们考虑一下,我将 preferredSize 值用于与默认布局管理器无关的 X 目的。

官方文档没有给出解决方案,我们看一下JRE源码:

这个叫超...

//JComponent.java (1642 ~ 1663)
public Dimension getPreferredSize() {
   if (isPreferredSizeSet()) {
       return super.getPreferredSize();
   }

   Dimension size = null;
   if (ui != null) {
       size = ui.getPreferredSize(this);
   }
   return (size != null) ? size : super.getPreferredSize();
}

这个也叫超...

//JComponent.java (1628 ~ 1639)
public void setPreferredSize(Dimension preferredSize) {
   super.setPreferredSize(preferredSize);
}

JComponent扩展了Container,我们来看看:

//Container.java (1751 ~ 1800)
public Dimension getPreferredSize() {
   return preferredSize();
}

@Deprecated
public Dimension preferredSize() {
   Dimension dim = prefSize;
   if (dim == null || !(isPreferredSizeSet() || isValid())) {
       synchronized (getTreeLock()) {
           prefSize = (layoutMgr != null) ?
               layoutMgr.preferredLayoutSize(this) :
               super.preferredSize();
           dim = prefSize;
       }
   }

   if (dim != null){
       return new Dimension(dim);
   }
   else{
       return dim;
   }
}

正如我所想,它正在返回一个新实例。但是让我们看看 Component.java 中的 setter 会发生什么(因为 Container 扩展了 Component 但没有覆盖该方法):

//Component.java (2560 ~ 2585)
public void setPreferredSize(Dimension preferredSize) {
   Dimension old;
   if (prefSizeSet) {
       old = this.prefSize;
   }
   else {
       old = null;
   }

   this.prefSize = preferredSize;
   prefSizeSet = (preferredSize != null);
   firePropertyChange("preferredSize", old, preferredSize);
}

这只是引用我通过的对象!,完美! (确实很危险,但它适合我目前的需求!)

我得出结论,我正在设置我的组件持有和使用的实际对象实例,所以(至少对于这个 JRE 版本)没有必要再次用同一个对象重复调用 setPreferredSize(但如果不这样做会确实很危险,因为这个源代码可以在不同的实现中改变),即使这样 setPreferredSize 如果我们用同一个 Dimension 实例调用它也不会产生垃圾。另一方面,调用 getPreferredSize 显然会分配一个新的 Dimension 对象来返回值。

答案是:在某处跟踪您的首选尺寸以尽量减少 getPreferredSize 的使用,并且为了对 API 有礼貌,调用 setPreferredSize,但使用相同的 Dimension 实例来避免垃圾

Dimension pref = null;

void getNewPrefOnlyWhenNeeded() {
    pref = component.getPreferredSize();
}

void calledEveryMillisecond() {
    //Here or on the Constructor, MouseListener or wherever
    if (pref == null || someCondition) getNewPrefOnlyWhenNeeded();

    // do stuff with pref...
    ...

    //setPreferredSize can actually be skipped if we call our layout manager after all is resized. It would be dirty tho...   
    component.setPreferredSize(pref);
}

经过进一步分析,最大和最小尺寸方法共享这种机制,所以一切都解决了。

【讨论】:

  • 脚注1:首先感谢大家的帮助。但我不得不说我讨厌stackoverflow变成一个“创伤支持小组”,回答像“别担心”,“你不需要这样做”,即使OP解释无济于事,他/她只是想要去做。它让我想起了告诉 OP 去看医生的医学论坛成员。我知道我们都在努力提供帮助,但是……这无济于事。无意冒犯任何人。
  • 脚注 2:一个项目的开源性如何使它的文档很差,这不是很棒吗? :)
【解决方案2】:

“我担心周围的所有 Dimension 对象。”

不用担心会很快死掉的几千个微型物体。 GC 可以处理这个问题。如果您遇到任何性能问题,它们将无法通过避免创建一些 Dimension 来解决。您应该知道,像 setPreferredSize() 这样的调用在内部比作为参数传递的那个微不足道的维度要高得多(查看 JRE 源代码,它做了很多 更多 > 内部)。

【讨论】:

  • 确实,检查我看到的代码会触发一个 PropertyChanged 事件,这在我使用它的方式上看起来既昂贵又多余。然而,这是我可以解决的问题,我想专注于 Dimension 对象,它来自哪里以及去哪里。
【解决方案3】:

有没有什么好的方法可以和set/getPreferredSize, set/getMinimumSize, and set/getMaximumSize一起工作

是的:不要使用它们!请改用适当的LayoutManager。 LayoutManager 负责计算这些值,所以最好的方法是将其委托给LayoutManagerBorderLayoutGridBagLayout 已经为布局提供了一个非常好的起点。您可能需要不时使用FlowLayout 和/或GridLayout。如果您愿意使用第三方库,MigLayout 也是有效的LayoutManager

考虑阅读这篇关于Should I avoid the use of set[Preferred|Maximum|Minimum]Size methods in Java Swing?answer

无论如何,Dimension 对象的内存占用非常小,通常在实例化后很快就会被垃圾回收,因此您不必担心它们。

如果您觉得自己遇到了性能问题,请使用分析器来帮助您找到它们。

【讨论】:

  • 不,布局管理器负责读取这些值、解释它们并找出正确的界限。您应该(最好)设置一次首选、最小值和最大值。你不应该接触的是 setSize 和 setLocation。
  • @マルちゃんだよ 不,你不应该调用这些方法(阅读我提供的链接)。 LayoutManager 负责计算这些首选尺寸,而不是您。如果要强制自定义组件的大小,则覆盖getPreferredSize()
  • 链接指向一个关于个人喜好的问题。为了客观起见,至少 BoxLayout、GroupLayout 和 SpringLayout (AFAIK),请阅读这些值。其他布局,如 Grid、GridBag 等,不要使用它们,因为它们的设计方式(即:GridLayout 拉伸所有内容以填充容器)。 docs.oracle.com/javase/tutorial/uiswing/layout/index.html。请看docs.oracle.com/javase/tutorial/uiswing/layout/custom.html具体layoutContainer方法的描述,了解LayoutManager的作用。
  • 反正我们有点跑题了。我不想使用getPreferredSize,我想直接或间接操作它的Dimension对象而不分配任何东西。
  • @マルちゃんだよ GridLayoutGridBagLayout 也使用它们(但它们不一定尊重它们,取决于提供的约束)。首选大小是根据包含的子项的首选大小递归计算的(这是由LayoutManager 完成的)。最终,您最终会使用“非容器”组件(例如JCheckBox, JTextField, JTextArea,...)通过将其委托给关联的XXXUI 来实现他们的首选大小,或者您最终会使用自定义组件,可以覆盖getPreferredSize()。不过,没有必要打电话给setPreferredSize()
【解决方案4】:

您必须每秒至少分配数千个这样的对象才能开始担心。 HotSpot 的 GC 方案专门针对短寿命对象的低成本进行了优化。它们在 Eden 空间中分配,如果 Eden 填满时没有幸存者,则只需更新指针即可释放它们。所以最好的建议是先关注其他性能问题;这个优先级应该很低。

【讨论】:

  • 这个是在渲染循环中调用的,所以每十毫秒一个几乎和每秒几千个一样大,抱歉我应该澄清一下这是为了某种渲染循环。
  • 每秒只有一百个对象。
  • 好吧,那么你至少有一个案例要考虑。您是否使用visualgc 或至少使用jvisualvm 监控分配?您应该看到堆的快速增长和收缩——这就是内存流失。如果您看到了这一点,那么您可能遇到了与分配相关的问题,可能仍然没有占性能的 10-20% 以上。
  • 1+ 不错的工具,等我再次拿到代码时再去看看。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多