Java Vector和HashSet之间的巨大性能差异
我有一个程序(使用Hibernate)从数据库中获取记录,并将它们填充到Java Vector和HashSet之间的巨大性能差异,java,performance,vector,hashset,Java,Performance,Vector,Hashset,我有一个程序(使用Hibernate)从数据库中获取记录,并将它们填充到向量中。操作性能出现问题,我用HashSet替换了向量,进行了测试。有了300000条记录,速度提升是巨大的-45分钟到2分钟 所以我的问题是,是什么造成了如此巨大的差异?是因为Vector中的所有方法都是同步的,还是因为Vector内部使用数组而HashSet没有?还是别的什么 代码在单个线程中运行 编辑: 代码仅在向量(在另一种情况下,哈希集)中插入值。向量默认同步;哈希集不是。那是我的猜测。获取访问监视器需要时间 我不
向量中。操作性能出现问题,我用HashSet
替换了向量
,进行了测试。有了300000条记录,速度提升是巨大的-45分钟到2分钟
所以我的问题是,是什么造成了如此巨大的差异?是因为Vector
中的所有方法都是同步的,还是因为Vector
内部使用数组而HashSet
没有?还是别的什么
代码在单个线程中运行
编辑:
代码仅在向量
(在另一种情况下,哈希集
)中插入值。向量默认同步;哈希集不是。那是我的猜测。获取访问监视器需要时间
我不知道您的测试中是否有读取,但是如果使用get()
访问向量条目,那么Vector和HashSet都是O(1) 如果它试图将向量
用作一个集合,并在添加它之前检查是否存在记录,那么填充向量就变成了一个O(n^2)操作,而哈希集
则是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");
}
}
这是一个巨大的区别——它需要42秒而不是38毫秒。这显然要糟糕得多——但距离45分钟还有很长的路要走——我怀疑我的桌面速度是你的60倍。如果你在中间或开头插入它们,而不是在结尾,那么向量需要一直移动它们。每一个插页。另一方面,hashmap实际上并不关心或必须做任何事情。向量已经过时,不应该再使用了。使用ArrayList或LinkedList配置文件(取决于您如何使用列表),您将看到差异(同步与不同步)。
为什么要在单线程应用程序中使用Vector?在正常情况下,将300000条记录插入Vector
比将相同的记录插入哈希集中要花费43分钟的时间是完全不可信的
然而,我认为对可能发生的事情有一个可能的解释
首先,来自数据库的记录必须有很高比例的重复项。或者至少,根据记录类的equals/hashcode方法的语义,它们必须是重复的
下一步,我想你一定是快要填满这堆东西了
因此,HashSet
解决方案速度更快的原因是大部分记录都被set.add
操作替换。相比之下,Vector
解决方案保留了所有记录,JVM花费了大部分时间试图通过反复运行GC来压缩最后0.05%
的内存
测试这一理论的一种方法是使用更大的堆运行应用程序的Vector
版本
不管怎样,调查此类问题的最佳方法是使用探查器运行应用程序,并查看所有CPU时间的去向。根据Heinz Kabutz博士的说法,他在他的一篇文章中这样说
旧的Vector类以一种简单的方式实现序列化。它们只是执行默认序列化,将整个对象[]
原样写入流中。因此,如果我们在列表中插入一组元素,然后清除它,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();
}
}
向量在序列化时可以使用惊人的字节数。这里的教训是什么永远不要在可序列化的对象中使用向量作为列表。灾难的可能性太大了。导入java.util.*;
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");
}
}
公开课考试{
公共静态void main(字符串[]args){
长启动=System.currentTimeMillis();
向量=新向量();
对于(int i=0;i<300000;i++){
if(向量包含(i)){
向量加上(“虚拟值”);
}
}
long end=System.currentTimeMillis();
System.out.println(“所用时间:”+(结束-开始)+“毫秒”);
}
}
如果在向量中插入元素之前检查重复元素,则需要更多的时间,具体取决于向量的大小。最好的方法是使用HashSet实现高性能,因为HashSet不允许重复,并且在插入之前不需要检查重复元素。如果看不到您对集合执行的操作,很难知道瓶颈在哪里。它们是不同的数据结构,因此,他们显然有不同的优势和劣势。使用最适合您需要的数据结构,如果数据结构的错误选择破坏了性能,请不要感到惊讶。但是正如@spender所说,在不知道你对这些数据结构实际做了什么的情况下,不可能说出为什么一个比另一个更快。编辑帖子以添加执行的操作-如果你能发布一些实际的代码,那会有所帮助。-1-对于@abhin4v来说,用明目张胆的谎言浪费我们的时间,比如“我只是调用Vector.add
”。没有明确的唯一性检查。只是插入值。@abhin4v:如何插入值,例如
LinkedList used 107 bytes
ArrayList used 117 bytes
Vector used 1310926 bytes
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");
}
}