Java 什么';当我需要调整源代码列表时,addAll是最有效的等价物吗?
如果我想将一个列表添加到另一个列表中,我会调用Java 什么';当我需要调整源代码列表时,addAll是最有效的等价物吗?,java,collections,java-8,Java,Collections,Java 8,如果我想将一个列表添加到另一个列表中,我会调用target.adAll(源代码) 但如果我需要先处理列表中的每个值,该怎么办 我可以做类似的事情 for(String s: source) { target.add(s.toLowerCase()); } 或使用java 8: source.stream().map(x->x.toLowerCase()).forEachOrdered(target::add); 但不管怎样,我似乎失去了addAll的性能优势。执行此操作最有效的方法
target.adAll(源代码)
但如果我需要先处理列表中的每个值,该怎么办
我可以做类似的事情
for(String s: source) {
target.add(s.toLowerCase());
}
或使用java 8:
source.stream().map(x->x.toLowerCase()).forEachOrdered(target::add);
但不管怎样,我似乎失去了
addAll
的性能优势。执行此操作最有效的方法是什么?您可以使用来自的collect()
模式,collect()
相当于JDK的map()
MutableList<String> source = Lists.mutable.with("A", "B", "C");
MutableList<String> target = source.collect(String::toLowerCase);
如果无法从列表更改列表
:
List<String> source = Arrays.asList("A", "B", "C");
List<String> target = ListAdapter.adapt(source).collect(String::toLowerCase);
List<String> source = Arrays.asList("A", "B", "C");
List<String> target = new ArrayList<>(source);
target.replaceAll(String::toLowerCase);
上述解决方案可能更有效,也可能不更有效,但是,它们都预先确定了目标列表的大小
注意:我是Eclipse集合的贡献者。那么,“addAll的性能优势是什么?”?最后,
addAll
必须将所有元素添加到目标集合中。如果目标是阵列列表
,主要好处是确保没有不必要的容量增加操作
但请注意,这是以创建临时数组为代价的,请参见。为了超过这一开销,您必须添加大量元素
如果我们要增加的元素超过目标公司当前的容量,那么增加操作是不可避免的。因此,addAll
只提供了一个好处,如果您只需使用add
,目标必须多次增加容量。由于容量提高了1.5倍,且容量等于或高于当前大小,我们必须添加至少超过其当前大小一半的元素,以预期不必要的容量增加操作
如果您真的认为这将是一个问题,很容易解决:
if(target instanceof ArrayList)
((ArrayList)target).ensureCapacity(target.size()+source.size());
source.stream().map(String::toLowerCase).forEachOrdered(target::add);
当然,在某些情况下,add
的成本要高得多,例如,CopyOnWriteArrayList
。对于此目标集合类型,首先通过collect(Collectors.toList())
将数据收集到列表中,然后通过addAll
进行收集可能是有益的。或者创建一个简单的惰性集合
,作为中间步骤:
public static <T> Collection<T> lazyCollection(Supplier<? extends Stream<T>> s) {
return new AbstractCollection<T>() {
public Iterator<T> iterator() { return s.get().iterator(); }
public int size() { return (int)s.get().count(); }
public Object[] toArray() { return s.get().toArray(); }
};
}
如果一个集合在获取一个迭代器之前先请求size()
,那么这种方法会遇到两次对流求值的问题,但好吧,没有标准集合会这样做。他们要么使用迭代器而不依赖于预测的大小,要么求助于toArray()
,比如ArrayList.addAll
或CopyOnWriteArrayList.addAll
do。我想JIT应该能够将第二段代码转换为第一段。顺便说一句,你能做的再好不过了。谢谢你,这是一个很好的解释。你能解释一下为什么LinkedList.addAll实现调用源集合上的数组吗?这让人怀疑我是否还缺少其他一些“性能魔法”(如L2缓存),因为我看不出有任何其他原因。如果源代码是线程安全的集合,那么在单个调用中获取所有元素可能是原子的,这取决于源代码的类型,从而确保某些组合的安全性,但由于没有指定这样的行为,因此没有理由实现它。此外,如果向其自身添加一个列表,这可以确保其工作,但另一方面,有一种廉价的方法来测试这种特殊情况,因此不必以这种方式处理所有源。一般来说,请求完整数组表明对源代码的迭代器有一些不信任,但我们不确定是什么导致了这个决定。
public static <T> Collection<T> lazyCollection(Supplier<? extends Stream<T>> s) {
return new AbstractCollection<T>() {
public Iterator<T> iterator() { return s.get().iterator(); }
public int size() { return (int)s.get().count(); }
public Object[] toArray() { return s.get().toArray(); }
};
}
target.addAll(lazyCollection(() -> source.stream().map(String::toLowerCase)));