6

就使用的内存和对垃圾收集器的影响而言,我想知道这两种实现之间是否有区别:

protected List<T> _data = new ArrayList<T>();

// I want to reset this list using another one. First try :
public void set(List<T> newData) {
    _data = newData;
}

// I want to reset this list using another one. Second try :
public void set(List<T> newData) {
    _data.clear();
    _data.addAll(newData);
}

另外,如果有任何功能差异,请告诉我!

4

5 回答 5

3

第一个只是将其对列表的引用替换为调用者提供的引用。如果从其他任何地方都无法访问旧列表,则它有资格进行 GC。

第二个使用对象的现有列表,并将调用者列表的每个项目引用复制到其中。如果这增加了集合的大小,则可能涉及在内部分配更大的数组(使较小的数组可收集)。

项目本身的可达性不会改变;这两个版本都导致列表引用完全相同的项目。

第一个通常要快很多,因为您要替换单个参考。这比将一堆引用从一个列表复制到另一个列表要少。

不过,从封装的角度来看,第二种通常更好。想象一下这个...

yourThingie.set(myItems);
myItems.add(objectYouNeverWouldHaveAllowed);

在第一个版本中,_data现在包含objectYouNeverWouldHaveAllowed. 你不能再强制你的班级对_data;的约束。由于您让调用者将他们自己的列表偷运到那里,他们现在可以控制您的对象的内部状态。他们可以从远处打破你的物体,甚至是意外。

第二个没有这样的问题。您保留对 的控制_data,调用者对其列表的后续更改不会影响您,并且如果您没有做一些损坏的事情(例如提供检索裸对象的 getter),那么您是安全的。

于 2013-06-21T13:39:52.137 回答
3

第一个示例,用新对象替换旧对象。如果您每次都以任何方式创建一个新列表,这很好。如果您的 set() 应该获取列表的副本,因为它可能会被调用者更改,那么第二个示例可能值得考虑。

第二个例子,重用列表,这可以是

  • 很好,因为它节省了创建副本。
  • 不好,因为您可以在永久空间中拥有一个引用许多年轻对象的对象。如果这种情况发生得足够多,它将减慢您的 GC 时间。

一般来说,您应该做您认为最简单的解决方案,并在您测量应用程序时担心性能,例如使用内存分析器。它告诉你有问题。除非你测量,否则你只是猜测。

AFAIK,没有 Java JVM 支持内存管理的引用计数。如果它没有被标准禁止但是有太多问题可以考虑在 Java 中使用它。例如循环引用。有趣的事实是,C++ 中的智能指针使用引用计数 :(

于 2013-06-21T13:38:25.030 回答
0

The first is a single reference assignment. The second has to copy the list of references from 'newData' into '_event', possibly resizing '_event' in the process, which entails further copying.

BUT... "Premature optimization if the root of all evil." (If that doesn't ring a bell, look it up!)

The semantic difference is probably more important in most cases. In the first you are just referencing the data provided to 'set' - if the calling code later changes it, it is changed in '_events' too (because '_events' IS 'newData'). This could cause dangerous side effects, if it wasn't intended.

In the second, subsequent changes to 'newData' are not reflected as changes to '_events' because '_events' is a copy of new data. Which is right is down to the semantics of your application. The same applies, in reverse, if you have a 'get' matching this 'set'.

If you are going to be updating only the contents of '_events' rather than changing what it references, you should also consider making it final.

Hmmm. I just noticed that '_data' is only mentioned once in your example. I have assumed above that '_data' and '_events' are the same thing (i.e. it is a typo) - otherwise why would you mention '_data' at all?

于 2013-06-21T13:41:55.900 回答
0

我会简单地使用您使用的第一种方法_data = newData;。_data 是指向列表的指针。因此,通过这样做,您将导致 _data 指向 newData。由于 _data 之前指向的任何内容都不再被指向,因此垃圾收集器将处理它。

但是,您的第二种方法非常昂贵,我不会这样做。是的,它实现了同样的目标,但它不是简单地移动指针,而是进行更多的计算,这将花费你。

于 2013-06-21T13:38:38.727 回答
0

主要区别在于:

列表实现:传入
List可能不是 a ArrayList,它可能是(例如)a LinkedList。如果您通过索引直接访问列表中的元素,则仅使用传入的列表可能会严重影响应用程序的性能;ArrayList.get(i)是 O(1),但是LinkedList.get(i)是 O(n)。

数据完整性:
如果您只是使用交给您的列表,调用者可能会保留对它的引用,并且(很多)稍后会在您的班级不知道的情况下修改列表。这可能会导致您的应用程序出现不可预知的行为。例如,您可以存储有关列表内容的信息,然后在您的班级不知情的情况下修改列表时这些信息将无效,从而导致数据不一致。同样,您的班级可能会修改列表,而客户(通常)不会知道,因此反之亦然 - 客户可能存在数据不一致。

内存泄漏:调用者和你的类都可能持有对列表的引用。这可能会导致列表没有按预期进行垃圾收集。

由于这些原因(可能还有更多原因),最佳实践通常是复制传入的列表,除非有充分的理由不这样做,并且您没有制作副本的事实已得到充分记录,以便每个人都知道其中的含义是。

于 2013-06-21T13:48:54.930 回答