假设我有以下代码:
public Set<String> csvToSet(String src) {
String[] splitted = src.split(",");
Set<String> result = new HashSet<>(splitted.length);
for (String s : splitted) {
result.add(s);
}
return result;
}
所以我需要将数组转换为Set。
并且Intellij Idea建议用Collection.addAll
单行替换我的for-each循环,所以我得到:
...
Set<String> result = new HashSet<>(splitted.length);
result.addAll(Arrays.asList(splitted));
return result;
完整的检查信息是:
当调用批量方法(例如collection.addAll(listOfX))时,可以在循环中调用某些方法(例如collection.add(x))时,此检查会发出警告。 如果选中复选框“Use Arrays.asList()来包装数组”,则即使原始代码在数组上迭代而批量方法需要Collection,检查也会发出警告。在这种情况下,快速修复操作将使用Arrays.asList()调用自动换行数组。
从检查描述来看,它听起来像预期的那样。
如果我们引用关于将数组转换为Set(How to convert an Array to a Set in Java)的问题的最佳答案,则建议使用相同的衬垫:
Set<T> mySet = new HashSet<T>(Arrays.asList(someArray));
即使从数组创建一个ArrayList是O(1),我也不喜欢创建一个额外的List对象。
通常我信任Intellij检查并假设它没有提供任何效率低下的东西。
但今天我很好奇为什么两者:top SO答案和Intellij Idea(默认设置)建议使用相同的单行程创建无用的中间List对象,而JDK 6后也有Collections.addAll(destCollection, yourArray)
。
我看到它的唯一原因是(检查和答案)都太旧了。如果是这样,这就是改善intellij想法并给出更多投票给出Collections.addAll()
的答案的理由:)
答案 0 :(得分:4)
提示为什么Intellij不建议Arrays.asList
替换
Set<String> result = new HashSet<>(splitted.length);
result.addAll(Arrays.asList(splitted));
return result;
位于source code for HashSet(Collection)
:
public HashSet(Collection<? extends E> c) {
map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
addAll(c);
}
请注意,集的容量的大小不是c
。
因此,更改在语义上不等同。
不要担心创建List
。 真的便宜。它不是免费的;但你必须在一个真正的性能关键循环中使用它才能注意到。
答案 1 :(得分:1)
我编写了一个小函数来测量将数组添加到HashSet的三种方法的性能,结果如下。
首先使用的所有基本代码生成maxSize
数组,其值介于0-100
int maxSize = 10000000; // 10M values
String[] s = new String[maxSize];
Random r = new Random();
for (int i = 0; i < maxSize; i++) {
s[i] = "" + r.nextInt(100);
}
然后是基准功能:
public static void benchmark(String name, Runnable f) {
Long startTime = System.nanoTime();
f.run();
Long endTime = System.nanoTime();
System.out.println("Total execution time for: " + name + ": " + (endTime-startTime) / 1000000 + "ms");
}
所以第一种方法是使用带循环的代码,10M values
使用150ms and 190ms
(我为每种方法运行基准测试几次)
Main.benchmark("Normal loop ", () -> {
Set<String> result = new HashSet<>(s.length);
for (String a : s) {
result.add(a);
}
});
其次是使用result.addAll(Arrays.asList(s));
,它花费在180ms and 220ms
Main.benchmark("result.addAll(Arrays.asList(s)): ", () -> {
Set<String> result = new HashSet<>(s.length);
result.addAll(Arrays.asList(s));
});
第三种方式是使用Collections.addAll(result, s);
,它花费在170ms and 200ms
Main.benchmark("Collections.addAll(result, s); ", () -> {
Set<String> result = new HashSet<>(s.length);
Collections.addAll(result, s);
});
现在解释。从运行时复杂性来看,它们都在O(n)
中运行,这意味着N values
N operations
将会运行(基本上会添加N
个值)。
从内存复杂性的角度来看,对所有O(N)
来说也是如此。只创建了新的HashSet
。
Arrays.asList(someArray)
没有创建 new 数组,只是创建一个对该数组有reference
的新对象。你可以在java代码中看到它:
private final E[] a;
ArrayList(E[] array) {
a = Objects.requireNonNull(array);
}
除此之外,所有addAll
方法都将完全按照您的方式执行,for-loop
:
// addAll method for Collections.addAll(result, s);
public static <T> boolean addAll(Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}
// addAll method for result.addAll(Arrays.asList(s));
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
总而言之,运行时差异非常小,IntelliJ提出了一种以更清晰的方式编写代码并减少代码的方法。