我有一个程序从数据库中获取记录(使用Hibernate)并在Vector
中填充它们。关于操作性能存在问题,我在Vector
替换为HashSet
的情况下进行了测试。拥有300000条记录,速度增加非常快 - 45分钟到2分钟!
所以我的问题是,造成这种巨大差异的原因是什么?是Vector
中的所有方法同步还是内部Vector
使用数组而HashSet
不使用数据的点?或其他什么?
代码在一个线程中运行。
修改的:
代码仅在Vector
(以及另一种情况下,HashSet
)中插入值。
答案 0 :(得分:10)
如果它试图将Vector
用作一个集合,并在添加它之前检查是否存在记录,那么填充该向量将成为O(n ^ 2)操作,与HashSet
的O(n)相比。如果在向量的开头而不是在结尾处插入每个元素,它也将成为O(n ^ 2)操作。
如果您只是使用collection.add(item)
,那么我不希望看到那种差异 - 同步不是 慢。
如果您可以尝试使用不同数量的记录进行测试,您可以看到每个版本随着n的增加而增长 - 这样可以更容易地计算出正在发生的事情。
编辑:如果您只是使用Vector.add
,那么听起来似乎还有其他事情 - 例如您的数据库在不同的测试运行之间表现不同。这是一个小测试应用程序:
import java.util.*;
public class Test {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Vector<String> vector = new Vector<String>();
for (int i = 0; i < 300000; i++) {
vector.add("dummy value");
}
long end = System.currentTimeMillis();
System.out.println("Time taken: " + (end - start) + "ms");
}
}
输出:
所花费的时间:38毫秒
现在显然这不是很准确 - System.currentTimeMillis
不是获得准确计时的最佳方式 - 但显然不需要花费45分钟。换句话说,如果真的只是在调用Vector.add(item)
,你应该在别处查找问题。
现在,更改上面的代码以使用
vector.add(0, "dummy value"); // Insert item at the beginning
产生了巨大的差异 - 它需要42 秒而不是38ms。这显然要糟糕得多 - 但距离45分钟还有很长的路要走 - 我怀疑我的桌面速度是你的桌面的60倍。
答案 1 :(得分:2)
如果您将它们插入中间或开头而不是最后,那么Vector需要一直移动它们。每个插页。另一方面,hashmap并不关心或不必做任何事情。
答案 2 :(得分:2)
Vector已过时,不应再使用了。使用ArrayList或LinkedList配置文件(取决于您使用列表的方式),您将看到差异(sync vs unsync)。 你为什么要在单线程应用程序中使用Vector?
答案 3 :(得分:1)
默认情况下,矢量是同步的; HashSet不是。这是我的猜测。获取访问监视器需要时间。
我不知道你的测试中是否有读取,但如果get()
用于访问Vector条目,则Vector和HashSet都是O(1)。
答案 4 :(得分:1)
一般情况下,它是完全不可信在插入300000个记录到一个Vector
将采取43分钟长于插入相同记录成HashSet
但是,我认为有可能解释可能发生的事情。
首先,来自数据库的记录必须具有非常高比例的重复。或者至少,它们必须根据记录类的equals / hashcode方法的语义重复。
接下来,我认为你必须非常接近填满堆。
因此HashSet
解决方案速度快得多的原因在于set.add
操作正在对大部分记录进行替换。相比之下,Vector
解决方案是保持所有的记录,并且Java虚拟机花费大部分时间要挤,去年0.05%
通过了,并且一遍又一遍地运行GC内存。
测试此理论的一种方法是使用更大的堆来运行应用程序的Vector
版本。
无论如何,调查此类问题的最佳方法是使用分析器运行应用程序,并查看所有CPU时间的去向。
答案 5 :(得分:1)
import java.util.*;
public class Test {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Vector<String> vector = new Vector<String>();
for (int i = 0; i < 300000; i++) {
if(vector.contains(i)) {
vector.add("dummy value");
}
}
long end = System.currentTimeMillis();
System.out.println("Time taken: " + (end - start) + "ms");
}
}
如果在向量中插入元素之前检查重复元素,则需要更多时间,具体取决于向量的大小。最好的方法是使用HashSet获得高性能,因为Hashset不允许重复,也不需要在插入之前检查重复元素。
答案 6 :(得分:-1)
根据Heinz Kabutz博士的说法,他在newsletters中提到了这一点。
旧的Vector类以一种天真的方式实现序列化。它们只是执行默认序列化,将整个Object[]
原样写入流中。因此,如果我们将一堆元素插入List,然后清除它,Vector和ArrayList之间的差异是巨大的。
import java.util.*;
import java.io.*;
public class VectorWritingSize {
public static void main(String[] args) throws IOException {
test(new LinkedList<String>());
test(new ArrayList<String>());
test(new Vector<String>());
}
public static void test(List<String> list) throws IOException {
insertJunk(list);
for (int i = 0; i < 10; i++) {
list.add("hello world");
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(baos);
out.writeObject(list);
out.close();
System.out.println(list.getClass().getSimpleName() +
" used " + baos.toByteArray().length + " bytes");
}
private static void insertJunk(List<String> list) {
for(int i = 0; i<1000 * 1000; i++) {
list.add("junk");
}
list.clear();
}
}
当我们运行此代码时,我们得到以下输出:
LinkedList used 107 bytes
ArrayList used 117 bytes
Vector used 1310926 bytes
Vector在序列化时可以使用惊人数量的字节。这里有什么教训? 不要在可序列化的对象中使用Vector作为列表。灾难的可能性太大了。