Vector和HashSet之间的巨大性能差异

时间:2010-07-06 09:59:47

标签: java performance vector hashset

我有一个程序从数据库中获取记录(使用Hibernate)并在Vector中填充它们。关于操作性能存在问题,我在Vector替换为HashSet的情况下进行了测试。拥有300000条记录,速度增加非常快 - 45分钟到2分钟!

所以我的问题是,造成这种巨大差异的原因是什么?是Vector中的所有方法同步还是内部Vector使用数组而HashSet不使用数据的点?或其他什么?

代码在一个线程中运行。

修改的: 代码仅在Vector(以及另一种情况下,HashSet)中插入值。

7 个答案:

答案 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作为列表。灾难的可能性太大了。