Vector的问题。

煜知搬砖者 2002-12-21 02:31:38
Vector初始的对象容纳量是10,如果对象数量增加,容量就会加倍,......
但是看到一本书上这样说:
//原文如下:
Vector vt = new Vector(100); //定义初始容量为100
例如,如果你再刚才定义的Vector对象中最终存储了7000个对象,那么实际它的
空间却可以容纳12800个对象。如果每个对象引用占4字节,那么这样会多占20kb的
内存。
//结束

老大们:我存7000个对象引用,怎么会到12800呢??请指教
...全文
96 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
煜知搬砖者 2002-12-22
  • 打赏
  • 举报
回复
我刚看到的,贴出来大家看看:
Java列表对象的性能分析和测试

[ 作者: 仙人掌工作室 添加时间: 2001-12-12 11:08:35 ]




作者:仙人掌工作室
来源:www.ccidnet.com

SDK提供了有序集合接口java.util.List的几种实现,其中三种最为人们熟知的是Vector、ArrayList和LinkedList。有关这些List类的性能差别是一个经常被问及的问题。在这篇文章中,我要探讨的就是LinkedList和Vector/ArrayList之间的性能差异。

为全面分析这些类之间的性能差异,我们必须知道它们的实现方法。因此,接下来我首先从性能的角度出发,简要介绍这些类的实现特点。

一、Vector和ArrayList的实现
Vector和ArrayList都带有一个底层的Object[]数组,这个Object[]数组用来保存元素。通过索引访问元素时,只需简单地通过索引访问内部数组的元素:
public Object get(int index)
{ //首先检查index是否合法...此处不显示这部分代码 return
elementData[index]; }



内部数组可以大于Vector/ArrayList对象拥有元素的数量,两者的差值作为剩余空间,以便实现快速添加新元素。有了剩余空间,添加元素变得非常简单,只需把新的元素保存到内部数组中的一个空余的位置,然后为新的空余位置增加索引值:
public boolean add(Object o)
{ ensureCapacity(size + 1); //稍后介绍 elementData[size++] = o; return true;
//List.add(Object) 的返回值 }


把元素插入集合中任意指定的位置(而不是集合的末尾)略微复杂一点:插入点之上的所有数组元素都必须向前移动一个位置,然后才能进行赋值:
public void add(int index, Object element) {
//首先检查index是否合法...此处不显示这部分代码
ensureCapacity(size+1);
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}


剩余空间被用光时,如果需要加入更多的元素,Vector/ArrayList对象必须用一个更大的新数组替换其内部Object[]数组,把所有的数组元素复制到新的数组。根据SDK版本的不同,新的数组要比原来的大50%或者100%(下面显示的代码把数组扩大100%):
public void ensureCapacity(int minCapacity) {
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object oldData[] = elementData;
int newCapacity = Math.max(oldCapacity * 2, minCapacity);
elementData = new Object[newCapacity];
System.arraycopy(oldData, 0, elementData, 0, size);
}
}


Vector类和ArrayList类的主要不同之处在于同步。除了两个只用于串行化的方法,没有一个ArrayList的方法具有同步执行的能力;相反,Vector的大多数方法具有同步能力,或直接或间接。因此,Vector是线程安全的,但ArrayList不是。这使得ArrayList要比Vector快速。对于一些最新的JVM,两个类在速度上的差异可以忽略不计:严格地说,对于这些JVM,这两个类在速度上的差异小于比较这些类性能的测试所显示的时间差异。

通过索引访问和更新元素时,Vector和ArrayList的实现有着卓越的性能,因为不存在除范围检查之外的其他开销。除非内部数组空间耗尽必须进行扩展,否则,向列表的末尾添加元素或者从列表的末尾删除元素时,都同样有着优秀的性能。插入元素和删除元素总是要进行数组复制(当数组先必须进行扩展时,需要两次复制)。被复制元素的数量和[size-index]成比例,即和插入/删除点到集合中最后索引位置之间的距离成比例。对于插入操作,把元素插入到集合最前面(索引0)时性能最差,插入到集合最后面时(最后一个现有元素之后)时性能最好。随着集合规模的增大,数组复制的开销也迅速增加,因为每次插入操作必须复制的元素数量增加了。

二、LinkedList的实现
LinkedList通过一个双向链接的节点列表实现。要通过索引访问元素,你必须查找所有节点,直至找到目标节点:
public Object get(intindex) {
//首先检查index是否合法...此处不显示这部分代码
Entry e = header; //开始节点
//向前或者向后查找,具体由哪一个方向距离较
//近决定
if (index < size/2) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}


把元素插入列表很简单:找到指定索引的节点,然后紧靠该节点之前插入一个新节点:
public void add(int index, Object element) {
//首先检查index是否合法...此处不显示这部分代码
Entry e = header; //starting node
//向前或者向后查找,具体由哪一个方向距离较
//近决定
if (index < size/2) {
for (int i = 0; i <= index; i++)
e = e.next;
} else {
for (int i = size; i > index; i--)
e = e.previous;
}
Entry newEntry = new Entry(element, e, e.previous);
newEntry.previous.next = newEntry;
newEntry.next.previous = newEntry;
size++;
}


线程安全的LinkedList和其他集合
如果要从Java SDK得到一个线程安全的LinkedList,你可以利用一个同步封装器从Collections.synchronizedList(List)得到一个。然而,使用同步封装器相当于加入了一个间接层,它会带来昂贵的性能代价。当封装器把调用传递给被封装的方法时,每一个方法都需要增加一次额外的方法调用,经过同步封装器封装的方法会比未经封装的方法慢二到三倍。对于象搜索之类的复杂操作,这种间接调用所带来的开销不是很突出;但对于比较简单的方法,比如访问功能或者更新功能,这种开销可能对性能造成严重的影响。

这意味着,和Vector相比,经过同步封装的LinkedList在性能上处于显著的劣势,因为Vector不需要为了线程安全而进行任何额外的间接调用。如果你想要有一个线程安全的LinkedList,你可以复制LinkedList类并让几个必要的方法同步,这样你可以得到一个速度更快的实现。对于所有其它集合类,这一点都同样有效:只有List和Map具有高效的线程安全实现(分别是Vector和Hashtable类)。有趣的是,这两个高效的线程安全类的存在只是为了向后兼容,而不是出于性能上的考虑。

对于通过索引访问和更新元素,LinkedList实现的性能开销略大一点,因为访问任意一个索引都要求跨越多个节点。插入元素时除了有跨越多个节点的性能开销之外,还要有另外一个开销,即创建节点对象的开销。在优势方面,LinkedList实现的插入和删除操作没有其他开销,因此,插入-删除开销几乎完全依赖于插入-删除点离集合末尾的远近。

三、性能测试
这些类有许多不同的功能可以进行测试。LinkedList应用比较频繁,因为人们认为它在随机插入和删除操作时具有较好的性能。所以,下面我分析的重点将是插入操作的性能,即,构造集合。我测试并比较了LinkedList和ArrayList,因为这两者都是非同步的。

插入操作的速度主要由集合的大小和元素插入的位置决定。当插入点的位置在集合的两端和中间时,最差的插入性能和最好的插入性能都有机会出现。因此,我选择了三个插入位置(集合的开头、末尾和中间),三种典型的集合大小:中等(100个元素),大型(10,000个元素),超大型(1,000,000个元素)。

在本文的测试中,我使用的是JAVA SDK 1.2.0和1.3.0系列的SUN JVM。此外,我还用HOTSPOT JVM 2.0进行了测试,这个版本可以在1.3.0 SDK找到。在下面的表格中,各个测量得到的时间都以其中一次SDK 1.2 VM上的测试时间(表格中显示为100%的单元)为基准显示。测试期间使用了默认的JVM配置,即启用了JIT编译,因此对于所有JVM,堆空间都必须进行扩展,以避免内存溢出错误。表格中记录的时间是多次测试的平均时间。为了避免垃圾收集的影响,在各次测试之间我强制进行了完全的内存清理(参见测试源代码了解详情)。磁盘监测确保磁盘分页不会在测试过程中出现(任何测试,如果它显示出严重的磁盘分页操作,则被丢弃)。所有显示出数秒应答时间的速度太慢的测试都重复进行,直至记录到一个明显合理的时间。
表1:构造一个中等大小的集合(100个元素)。括号中的数字针对预先确定大小的集合。
  1.2 JVM 1.3 JVM HotSpot 2.0 JVM
总是插入到ArrayList的开头 100% (48.0%) 184.9% (152.0%) 108.0% (66.7%)
总是插入到LinkedList的开头 135.5% 109.1% 85.3%
总是插入到ArrayList的中间 130.0% (40.6%) 187.4% (158.0%) 84.7% (46.0%)
总是插入到LinkedList的中间 174.0% 135.0% 102.3%
总是插入到ArrayList的末尾 63.3% (20.7%) 65.9% (25.0%) 60.3% (29.3%)
总是插入到LinkedList的末尾 106.7% 86.3% 80.3%

对于规模较小的集合,ArrayList和LinkedList的性能很接近。当元素插入到集合的末尾时,即追加元素时,ArrayList的性能出现了突变。然而,追加元素是ArrayList特别为其优化的一个操作:如果你只想要一个固定大小的静态集合,Java数组(例如Object[])比任何集合对象都具有更好的性能。除了追加操作,测量得到的时间数据差别不是很大,它们反映了各个JVM的优化程度,而不是其他什么东西。

例如,对于把元素插入到集合的开始位置来说(表1的前两行),HotSpot 2.0 JVM加LinkedList具有最好的性能(85.3%),处于第二位的是 1.2 JVM加ArrayList(100%)。这两个结果显示出,1.2中简单的JIT编译器在执行迭代和复制数组等简单的操作时具有很高的效率。在HotSpot中复杂的JVM加上优化的编译器能够改进复杂操作的性能,比如对象创建(创建LinkedList节点),并能够利用代码内嵌(code-inlining)的优势。1.3 JVM的结果似乎显示出,在简单操作方面它的性能有着很大的不足,这一点很可能在以后的JVM版本中得到改进。

在这里我特别进行测试的是ArrayList相对于LinkedList的另一个优点,即预先确定集合大小的能力。具体地说,创建ArrayList的时候允许指定一个具体的大小(例如,在测试中ArrayList可以创建为拥有100个元素的容量),从而避免所有随着元素增多而
jieshen 2002-12-21
  • 打赏
  • 举报
回复
高手呀,学习ing
sun1979song 2002-12-21
  • 打赏
  • 举报
回复
呵呵,你算算for (int i = 0; i < 10000; i++);这句执行完要多长时间。
有些JDBC Driver版本较低,结果集不可滚动,除了next()有效别的都没着。既然你的能支持,那多好呀,直接absoluten(n)就行了,还非用next()不可了!
xiaofenguser 2002-12-21
  • 打赏
  • 举报
回复
next方法分页很慢,刚测试的结果
D:\>java test2
1000条记录NEXT 用的时间:20
1000条记录getRow用的时间:0
D:\>java test2
5000条记录NEXT 用的时间:50
5000条记录getRow用的时间:0
D:\>java test2
10000条记录NEXT 用的时间:80
10000条记录getRow用的时间:0
D:\>java test2
20000条记录NEXT 用的时间:170
20000条记录getRow用的时间:0
环境:win2000+JDK1.4.0+Access
数据库:id+title
测试代码;
import java.util.Date;
import java.sql.*;
class test2
{
public static void main(String[] args) throws SQLException,ClassNotFoundException
{
Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Connection con=DriverManager.getConnection("jdbc:odbc:mysite");
String query="select * from test";
Statement stmt=con.createStatement();
ResultSet rs=stmt.executeQuery(query);
int i=0;
int k=0;
long time1=new Date().getTime();
while (rs.next())
{
i++;
}
long time2=new Date().getTime();
rs.close();
stmt.close();
Statement stmt2=con.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
ResultSet rs2=stmt2.executeQuery(query);
long time3=new Date().getTime();
rs2.last();
k=rs2.getRow();
long time4=new Date().getTime();
rs2.close();
stmt2.close();
con.close();
time1=time2-time1;
time3=time4-time3;
System.out.println(i+"条记录NEXT 用的时间:"+time1);
System.out.println(k+"条记录getRow用的时间:"+time3);
}
}
xhan2000 2002-12-21
  • 打赏
  • 举报
回复
java21使用arraylist和hashmap

来替代vector和hashtable
sun1979song 2002-12-21
  • 打赏
  • 举报
回复
呵呵,ArrayList的增容方式跟Vector不同:新容量=(旧容量 * 3 / 2 + 1) 和 实际需要容量 的最大值。Array可以添加一个集合进去,因此增长方式可以使跳跃的

to: xieha(浪客剑心)
一般来讲,只对ResultSet进行next()操作是很快的,但getXXXX()就很耗时间和空间了。这个规律是客观的,就是让sun的工程师来写,速度和效率也提不上去!
其次,为了友好,一页显示记录条数一般不大于100条,全部显示出来程序是好写,但效率低,也难看。
建议分页,确定页长比如50,第一次查1-50条记录,后面的next()掉,计算总数,以后根据参数确定取哪一页,前面的next()掉,后面的也next()掉....
HelloWorldd 2002-12-21
  • 打赏
  • 举报
回复
xx8081(xiao):其实快和慢很多时候都是相对的,一般情况下差别很细微,其实可以无所谓。可是在一些特定的场合下,比如用在网络上时,就不得不多加小心了,如你所说的几万条记录,你本机运行可能感觉没什么,可当很多人都在调用这几万条记录呢!
综山所述,只要我们在使用的时候“留心点”就行了,然后多做测试,以确保万无一失。
xx8081 2002-12-21
  • 打赏
  • 举报
回复
同意楼上的。vector很慢吗?我曾经用它保存从数据库中取出的几万条纪录,也不慢啊.
HelloWorldd 2002-12-21
  • 打赏
  • 举报
回复
to: xieha(浪客剑心)
关于结果为什么是12800,xiaofenguser(风雨)说得很对,如果只是想知道这个答案,那么可以不往下看了,如果你想知道“增量”的一些细节,则可继续。
首先,在定义了一个Vector的时候,就已经同时定义了它的初始容量和增量。最常见的定义如Vector v = new Vector(100);这时的增量是默认的。即一旦面临溢出,容量就加倍。但这样常会造成极大的资源浪费。所以最常用的构造方法是:Vector v = new Vector(int m,int n);n就是你自定义的增量。今后,一旦需重定位时:m=m+n,首先这个过程本身就比较浪费资源,所以m,n的定义都须慎重才好。

另,以下几点只得注意:
1。Vector属于“对象的容器”类,即只能容纳类的实例。基础数据类型(int,double等)需要包装成类的对象后才可加入。
2。Vector是异构的,即可把不同类型的对象装入一个Vector中。
3。在从Vector中取出对象时,必须用强转方式将其转换为原先的正确
类型。“不管你进来之前是什么,进来了就是个Vector,出去的时候
在找回身份吧”。
如:Item currentItem = (Item)v.elementAt(n);

先说这几点吧(离题越来越远了:),希望你能从中有所收益,共勉
吧。
gdsean 2002-12-21
  • 打赏
  • 举报
回复
Vector已经deprecated?
话不能乱讲
ArrayList不是一个同步的容器
Vector内部控制了多线程访问,所以性能当然比较差些
wang_zheng_wz 2002-12-21
  • 打赏
  • 举报
回复
java 2还是用ArrayList吧,Vector已经deprecated了
煜知搬砖者 2002-12-21
  • 打赏
  • 举报
回复
to:sun1979song(十步杀一人)
我观察Tomcat的后台,发现从数据库里面取得数据很快
但是通过Vector显示到前台速度很慢,尤其是记录大于100条的时候。加入记录大于500,1000或者更多呢?当然可以采取分页显示(比如说一次取5条),但是除了这个方法,还有没有别的方法?请指教!
sun1979song 2002-12-21
  • 打赏
  • 举报
回复
to xieha(浪客剑心):
用Vector储存记录对象当然可以,也可以把数据库表的主键字段提取出来作为HashMap的key,把记录对象作为value放在HashMap里,应用场合不同,没有可比性
to xiaofenguser(风雨):
在单线程下用ArrayList就够了,Vector跟ArrayList相比,基本只有一点差异:Vector支持多线程,ArrayList不支持。类似的弟兄还有Hashtable(多线程)和HashMap。
xiaofenguser 2002-12-21
  • 打赏
  • 举报
回复
ArrayList要比Vector好
煜知搬砖者 2002-12-21
  • 打赏
  • 举报
回复
楼上的朋友们:
Vector我主要用来存放表中的数据,一个对象对应一条记录,
不知道用Vector来存放合不合适?HashMap之类的可以么?那个更好点,请
指点
sun1979song 2002-12-21
  • 打赏
  • 举报
回复
哎,说来话长,其实Vector里面是用Object[]来存放对象的,默认情况下new Vector()时Object[]的长度为10,增长量为0,new Vector(100)时,长度为100,增长量也为0,当new Vector(100, 20)时,Object[]的长度为100,增长量为20。如果我们不断的addElement(),Object[]肯定容量不够,当发生这种情况时,Vector就会首先new一个长度更大的Object[],其长度具体有多大随后再说,然后把旧Object[]里的对象一个一个的复制到新Object[]里,最后再把当前正发生addElement()的对象也放到最后边,这样,我们只管addElement()就行了,大可放心去写程序,不用担心里面的细节。再说新Object[]的长度是怎样确定的:当增长量为0时,新长度是旧长度的2倍,当增长量为>0时,新长度=旧长度+增长量。到这就完全明白了吧!所以,楼主可以new Vector(7000,1),这样存7000个对象引用,一个也不会多浪费。可是,还有一个矛盾:当我增长量定得很小比如1时,如果经常发生Object[]容量不足,那么就惨了,每次都要重新new Object[],然后还要一个一个地复制,性能可就差了。所以,用Vector时,如果可能的总容量不是很大比如<100,这比较常见,简单的new Vector就行了,如果很大,那么最好能大致估计好尺寸比如大概是2000,那么new Vector(2000, n)好点,n不可太吝啬,具体多少自己定吧。
xiaofenguser 2002-12-21
  • 打赏
  • 举报
回复
如是是加倍就是
初始100>>200>>400>>800>>1600>>3200>>6400>>12800
7000比6400大,所以就要12800了,不知道真实系统的是不是这么加倍的.
觉得这样加倍方式不大好.
study_body 2002-12-21
  • 打赏
  • 举报
回复
关注,虚心学习,顶

62,614

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧