就使用的内存和对垃圾收集器的影响而言,我想知道这两种实现之间是否存在差异:
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);
}
另外,如果有任何功能差异,请告诉我!
答案 0 :(得分:3)
第一个示例用新的对象替换旧对象。如果您每次都以任何方式创建了一个新列表,那么这很好。如果你的set()应该获取列表的副本,因为调用者可能会更改它,第二个例子可能值得考虑。
第二个例子,重用List,这可以是
一般情况下,您应该执行您认为最简单的解决方案,并在测量应用程序时担心性能,例如:使用内存分析器。它告诉你有问题。除非你测量,否则你只是猜测。
AFAIK,没有Java JVM支持内存管理的引用计数。如果它没有被标准禁止,但是有太多的问题要考虑在Java中使用它。例如循环引用。有趣的是,C ++中的智能指针使用引用计数:(
答案 1 :(得分:3)
第一个只是将其对列表的引用替换为调用者提供的列表。如果无法从其他任何地方访问旧列表,则可以使用GC。
第二个使用对象的现有列表,并将每个调用者列表的项目引用复制到其中。如果这增加了集合的大小,那么这可能涉及在内部分配更大的数组(使较小的数组可收集)。
物品本身的可达性不会改变;两个版本都会导致列表引用完全相同的项目。
第一个通常要快一点,因为你要替换一个引用。这比将一堆引用从一个列表复制到另一个列表中的工作要少。
然而,从封装的角度来看,第二个通常更好。想象一下......
yourThingie.set(myItems);
myItems.add(objectYouNeverWouldHaveAllowed);
对于第一个版本,_data
现在包含objectYouNeverWouldHaveAllowed
。您无法再对_data
强制执行类的约束;因为你让调用者在那里走私他们自己的列表,他们现在可以控制你对象的内部状态。它们可以远距离打破你的物体,甚至是偶然的。
第二个没有这样的问题。你保留对_data
的控制权,调用者对其列表的后续更改不会影响你,如果你没有做坏事(比如提供一个检索裸体对象的getter),那么你就是安全的。 / p>
答案 2 :(得分:0)
我只想使用您使用_data = newData;
的第一种方法。 _data是指向列表的指针。因此,通过这样做,您将会使_data指向newData。既然_data指向之前不再指向它,那么垃圾收集器就会处理掉它。
然而,你的第二种方法成本很高,我不会这样做。是的,它实现了同样的目的,但不是简单地移动指针,而是进行更多的计算,这将花费你。
答案 3 :(得分:0)
根据文档,这是clear()
的作用:
public void clear() {
modCount++;
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
所以,我认为制作一个新的List
实际上会更快(当列表大小太大时)但不确定它是否会使它更加效率< / em>就内存使用而言。
有时效率不仅涉及速度。
答案 4 :(得分:0)
第一个是单个参考分配。第二个必须将引用列表从'newData'复制到'_event',可能会在进程中调整'_event',这需要进一步复制。
但......“如果万恶之源就过早优化。” (如果那不响铃,请查阅!)
在大多数情况下,语义差异可能更为重要。在第一个中,您只是引用提供给'set'的数据 - 如果调用代码稍后更改它,它也会在'_events'中更改(因为'_events'IS'newData')。如果不是这样的话,这可能会导致危险的副作用。
在第二个中,对'newData'的后续更改不会反映为'_events'的更改,因为'_events'是新数据的副本。哪个是正确的取决于您的应用程序的语义。反过来,如果你有一个'get'匹配这个'set',情况也是如此。
如果您要仅更新'_events'的内容而不是更改它引用的内容,您还应该考虑将其作为最终内容。
嗯。我只是注意到'_data'在你的例子中只提到过一次。我上面假设'_data'和'_events'是相同的东西(即它是一个错字) - 否则为什么你会提到'_data'?
答案 5 :(得分:0)
主要区别是:
清单实施:
传入的List
可能不是ArrayList
,可能是(例如)LinkedList
。如果您通过索引直接访问列表中的元素,那么仅使用传入的列表可能会严重影响应用程序的性能; ArrayList.get(i)
是O(1),但LinkedList.get(i)
是O(n)。
数据完整性:
如果你只是使用那个递给你的列表,那么调用者可以保留对它的引用,并且(稍后)修改列表而不需要你的班级知道。这可能会导致您的应用程序出现不可预测的行为。例如,您可以存储有关列表内容的信息,这些信息在列表中无效,但在没有您的类知识的情况下被修改,导致数据不一致。同样,您的类可能会修改列表,而客户端也不会(通常)知道,因此反之亦然 - 客户端可能存在数据不一致。
内存泄漏: 呼叫者和您的班级可以对列表进行引用。这可能导致列表在预期时不被垃圾收集。
由于这些原因(可能还有更多),最佳做法通常是复制传入的列表,除非有充分的理由不和这一事实你没有制作副本有很好的记录,所以每个人都知道其含义是什么。