Java 与ArrayList相比,向量填充所需的时间更少
我正在浏览以下文章: 文章说, 您知道,Vector和Hashtable是Java历史早期存在的两个集合,它们从一开始就是为线程安全而设计的(如果您有机会查看它们的源代码,您将看到它们的方法都是同步的!)。但是,它们很快就会暴露出多线程程序中的低性能。您可能知道,同步需要锁,而锁总是需要时间来监视,这会降低性能 [我还使用卡尺进行了基准测试;请听我讲完这一点] 还提供了一个示例代码:Java 与ArrayList相比,向量填充所需的时间更少,java,arraylist,vector,collections,Java,Arraylist,Vector,Collections,我正在浏览以下文章: 文章说, 您知道,Vector和Hashtable是Java历史早期存在的两个集合,它们从一开始就是为线程安全而设计的(如果您有机会查看它们的源代码,您将看到它们的方法都是同步的!)。但是,它们很快就会暴露出多线程程序中的低性能。您可能知道,同步需要锁,而锁总是需要时间来监视,这会降低性能 [我还使用卡尺进行了基准测试;请听我讲完这一点] 还提供了一个示例代码: public class CollectionsThreadSafeTest { public vo
public class CollectionsThreadSafeTest {
public void testVector() {
long startTime = System.currentTimeMillis();
Vector<Integer> vector = new Vector<>();
for (int i = 0; i < 10_000_000; i++) {
vector.addElement(i);
}
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
System.out.println("Test Vector: " + totalTime + " ms");
}
public void testArrayList() {
long startTime = System.currentTimeMillis();
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 10_000_000; i++) {
list.add(i);
}
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
System.out.println("Test ArrayList: " + totalTime + " ms");
}
public static void main(String[] args) {
CollectionsThreadSafeTest tester = new CollectionsThreadSafeTest();
tester.testVector();
tester.testArrayList();
}
}
但当我在我的机器上运行它时,它给了我以下结果:
Test Vector: 521 ms
Test ArrayList: 2273 ms
我觉得这很奇怪。我认为做一个微型基准测试会更好。因此,我使用卡尺为上述内容编写了一个基准测试。代码如下:
Test Vector: 9266 ms
Test ArrayList: 4588 ms
public class CollectionsThreadSafeTest extends SimpleBenchmark {
public static final int ELEMENTS = 10_000_000;
public void timeVector(int reps) {
for (int i = 0; i < reps; i++) {
Vector<Integer> vector = new Vector<>();
for (int k = 0; k < ELEMENTS; k++) {
vector.addElement(k);
}
}
}
public void timeArrayList(int reps) {
for (int i = 0; i < reps; i++) {
List<Integer> list = new ArrayList<>();
for (int k = 0; k < ELEMENTS; k++) {
list.add(k);
}
}
}
public static void main(String[] args) {
String[] classesToTest = { CollectionsThreadSafeTest.class.getName() };
Runner.main(classesToTest);
}
}
我有点困惑。这里发生了什么?我是不是做错了什么(那真的很尴尬)
如果这是预期的行为,那么背后的解释是什么
更新#1 在读取了之后,我通过更改
向量
和阵列列表
的初始容量值来运行卡尺测试。以下是计时(毫秒):
感谢所有的输入:)在多线程环境中,向量的速度应该会较慢。在您的情况下,它应该是轻量级的。最好通过从10000个不同线程添加这些项来进行测试,ArrayList和Vector都有相同的添加方法:
ensureCapacity();
elementData[elementCount++] = newElement;
区别只有一个。Vector的add方法已同步,ArrayList的则未同步。从理论上讲,同步方法比不同步方法慢
要提高add方法的性能,必须在构造函数中指定
initialCapacity
,或调用方法ensureCapacity
。这将创建您需要的内部数组,因此无需重新创建它。您并不是在这里真正测试add()
方法。您正在测试向量
和数组列表
的不同增长方式。Vector
满时大小会翻倍,但是ArrayList
有一些更精确的逻辑,可以防止内部数组以指数形式增长并浪费内存
如果两个类的初始容量都大于10000000,则它们不需要调整大小,您只需分析添加的部分。Ok。但是他们为本文中的示例代码提供的输出呢?他们只是在不使用线程的情况下对添加操作进行计时。这并不能解释为什么在单线程环境中添加操作会更快。这如何回答这个问题?小提示:除非在某些使用模式下,使用同步方法并不能使类线程安全。例如,给定一个
向量foos
,在列表中出现一个foo1
,序列foos.remove(foo1);foos.add(foo1)
由两个线程同时运行或多或少会导致列表中出现两次foo1
,或仅出现一次。无论基准测试的结果如何,尽管存在缺陷,但您不应该使用这些遗留类,哈希表
和向量
。这不是速度或线程“安全”的问题,而是使用不过时的替代品的问题,这些替代品缺少遗留类型的多余粗糙。已经十九年了;有足够的时间来适应更换。@LewBloch使用的卡尺也不好,或者他们的方法总体上不好?因此,如果我指定了尺寸,我将能够得到实际时间?取决于您所说的“实际”是什么意思。您将从等式中删除大小调整。在向量上调整大小的速度更快,因为一段时间后,内部容量变得巨大,不再需要调整大小,而数组列表
以更平静的方式增长,节省内存,但如果事先没有计划,则需要更多的调整大小。我将初始容量设置为10_000_000,然后再次运行卡尺测试。获得ArrayList:67.1ms
和Vector:49.2ms
。所以,有点像我得到的。我不熟悉卡钳。JMH似乎是我在这里看到的基准测试中最受欢迎的。在基准测试中这样猜测不是一个好主意。首先,您需要使用JMH复制结果,以确保这不是基准测试中的错误,然后如果您仍然感兴趣,您可以进一步研究它。
Initial Capacity Vector ArrayList
-------------------------------------
10_000_000 49.2 67.1
10_000_001 48.9 71.2
10_000_010 48.1 61.2
10_000_100 43.9 70.1
10_001_000 45.6 70.6
10_010_000 44.8 68.0
10_100_000 52.8 64.6
11_000_000 52.7 71.7
20_000_000 74.0 51.8
-------------------------------------
ensureCapacity();
elementData[elementCount++] = newElement;