Java中的数据结构-2(杨中科原创)

huanzhugege 2007-03-16 12:43:20
让我们来看看 ArrayList 类的实现 :

public class ArrayList extends AbstractList implements List, RandomAccess,

Cloneable, java.io.Serializable

{

……

private transient Object elementData[];

……

public ArrayList(int initialCapacity)

{

super();

if (initialCapacity < 0)

throw new IllegalArgumentException("Illegal Capacity: "

+ initialCapacity);

this.elementData = new Object[initialCapacity];

}

public ArrayList()

{

this(10);

}

……

public void ensureCapacity(int minCapacity)

{

modCount++;

int oldCapacity = elementData.length;

if (minCapacity > oldCapacity)

{

Object oldData[] = elementData;

int newCapacity = (oldCapacity * 3) / 2 + 1;

if (newCapacity < minCapacity)

newCapacity = minCapacity;

elementData = new Object[newCapacity];

System.arraycopy(oldData, 0, elementData, 0, size);

}

}



public boolean add(Object o)

{

ensureCapacity(size + 1);

elementData[size++] = o;

return true;

}

}

正如其名字所暗示的, ArrayList 内部是使用数组保存的数据, Java 中的数组是固定大小的,要想改变数组的大小,就要重新开辟一个新的大小的内存区域,把原先的数据复制到新内存区域中,这就是增量性数组。由于需要重新开辟内存区域并进行数据的复制,因此改变数组的大小是非常耗时的,我们要尽量避免改变数组的大小。

从 ArrayList 的内部实现我们可以发现, ArrayList 中储存数据用的数组的默认大小为 10 ,当调用 add 方法向其中增加数据的时候,如果数据已经超过了数组的大小, ArrayList 就要增加数组的大小以便容纳更多的数据。在我们的这个人例子中,由于数据已经超过 10 条,当增加到第 11 条的时候, ArrayList 就会进行一次“扩容”,这是一个非常耗时的操作,因此我们必须想方设法避免。

我们注意到 ArrayList 有一个带参数的构造函数: public ArrayList(int initialCapacity) ,这里的 initialCapacity 代表构造此 ArrayList 的时候,数组的大小。我们可以使用此构造函数来避免“扩容”。

实现代码 2 :

PersonInfo[] persons = new PersonInfo[] {

new PersonInfo("00001", "Tom", 20),

new PersonInfo("00002", "Tim", 23),

new PersonInfo("00003", "Sally", 26),

new PersonInfo("00005", "Lily", 20),

new PersonInfo("00006", "Lucy", 30),

new PersonInfo("00008", "Kitty", 20),

new PersonInfo("00011", "Smith", 20),

new PersonInfo("00031", "Ketty", 22),

new PersonInfo("00051", "Melly", 20),

new PersonInfo("00022", "Blues", 20),

new PersonInfo("00033", "Tid", 18),

new PersonInfo("00101", "Duoliaos", 30),

new PersonInfo("00201", "Yang", 26),

new PersonInfo("03001", "King", 20),

new PersonInfo("05001", "Lee", 20),

new PersonInfo("10001", "Wang", 20),

new PersonInfo("06001", "Pizza", 60) };



List list = new ArrayList(persons.length);

for (int i = 0, n = persons.length; i < n; i++)

{

PersonInfo pInfo = persons[i];

list.add(pInfo.getId());

}

我们已经知道了 list 将放置 persons.length 条数据,因此我们就使 list 中数组的默认大小设置为 persons.length ,这样系统的性能就好了很多。

不仅是 ArrayList ,我们在使用 Vector 、 HashMap 等类的同时,同样要注意这个问题。

选用合适的类

List 接口最常用的实现类有两个: ArrayList 、 LinkedList ,我们一般都是通过 List 接口来调用这些类的实现方法,所以它们的使用方式是几乎相同的,其区别就在于其实现方式。

正如 4.5.1 中阐述的那样, ArrayList 内部是使用数组保存的数据,而 LinkedList 则使用链表保存的数据。数组方式和链表方式储存数据的优缺点比较如下 :

数组中的数据是顺序排列的,因此要向数组中插入数据或者从数组中删除数据,就必须对其他数据进行位置的改变,因此效率是非常低的;但是由于数组的数据是按下标读取的,所以从数组中检索数据是非常快的 。

链表中的数据是通过指针连在一起的,向链表中插入数据或者从链表中删除数据只需要断开指针关系即可,效率非常高;但是从链表中检索数据的时候,必须从链表头向后遍历,效率非常低 。

因此 ArrayList 适合于保存很少插入、删除,但是经常读取的场合,而 LinkedList 适合于经常插入、删除,但是很少读取的场合。合理的使用这两个类,将会提高系统的效率。

选用合适的数据结构

很多程序员都意识到了 Map 的方便性和实用性,因此也造成了 Map 的滥用。比如:

一个数组中有若干字符串,请验证,这些字符串是否有重复。

实现代码 1 :

String[] data = { "11", "22", "33", "55", "11", "66" };

Map map = new HashMap();

for (int i = 0, n = data.length; i < n; i++)

{

if (map.containsKey(data[i]))

{

System.out.println(" 数据重复 ");

return;

}

map.put(data[i], "");

}

运行结果 :

数据重复



这段代码利用了 Map 中键不能重复的特性,实现了要求的效果,但是看起来怪怪的,因为 Map 中的数据是“键 - 值对”,而这里只用到了“键”,对于“值”则只是简单的塞了一个空字符串进去应付差事。显然这对 Map 来说是误用。

实现代码 2 :

String[] data = { "11", "22", "33", "55", "11", "66" };

Set set = new HashSet();

for (int i = 0, n = data.length; i < n; i++)

{

if (set.contains(data[i]))

{

System.out.println(" 数据重复 ");

return;

}

set.add(data[i]);

}

运行结果 :

数据重复



JDK 中的 Set 接口代表中数学中的“集合”(注意和 JDK 中的 Collection 区分开),即不重复的数据项的容器。显然使用 Set 接口就完全能满足我们的要求,同时我们又使用了采用哈希算法的 HashSet ,这就保证了判断数据重复时的效率。案例系统中的 PermissionServiceImpl 类(包 com.cownew.PIS.base.permission.bizLayer 下)就是用 Set 来完成权限项名称重复验证的;类 ServerUserContext (包 com.cownew.PIS.framework.server.sessionMgr 下)的 getPermissonNameSet 方法返回 Set 类型的意义也在于此 。

关于返回值

经常需要使用 List 、 Map 、 Set 等类做为方法返回值以返回多个数据,但是 JDK1.5 之前是不支持泛型的,我们只能看到方法返回一个 List 、 Map 或者 Set 类型的返回值,至于这些容器内存放着什么类型的数据则无法得知,只能通过查手册才能得知。在读取数据的时候又要进行类型转换。这给系统留下了很多不确定性因素。在 JDK1.5 之前唯一能在编译器就确定容器中数据的类型的 Java 结构就是数组,因此如果在返回数据的时候能够确定数据的类型以及大小,并且确定调用者只是读取返回值,那么我们就应该尽量使用数组来返回数据,这样程序的可读性就增强了,而且也减少了很多不确定性因素。

在使用返回值的时候还要注意一些惯用法。

( 1 )数组,一定不能返回 NULL

Object[] fooBar()

{

//do something

return null;

}

极少有人这样使用此方法:

Object[] objArray = fooBar();

if (objArray != null)

{

for (int i = 0; i < objArray.Length; ++i)

{

//do something

}

}

应该这样写 fooBar 方法:

Object[] fooBar()

{

//do something

return new Object[0];

}

( 2 )集合,同样不能返回 NULL

Set fooBar()

{

//do something

return null;

}

应该这样写:

Set fooBar()

{

//do something

return new HashSet(0);

}

...全文
302 5 打赏 收藏 举报
写回复
5 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
huanzhugege 2007-03-23
你只能说SunJDK中的“HashSet是用HashMap实现的”
  • 打赏
  • 举报
回复
huanzhugege 2007-03-23
to Veryx:
我不管它是怎么实现的,我是和Set的接口打交道。
  • 打赏
  • 举报
回复
Veryx 2007-03-20
不知道LZ有没有看过HashSet的代码,HashSet是用HashMap实现的
  • 打赏
  • 举报
回复
frilly 2007-03-18
再学习,哈哈
  • 打赏
  • 举报
回复
i2u112233 2007-03-16
mark
  • 打赏
  • 举报
回复
相关推荐
发帖
Java SE
加入

6.2w+

社区成员

Java 2 Standard Edition
申请成为版主
帖子事件
创建了帖子
2007-03-16 12:43
社区公告
暂无公告