Java中的資料結構(轉載)

pingyuan發表於2007-03-18
Java中的資料結構[@more@]是否選擇了合適的資料結構進行資料處理對系統的效能有著極大的影響, JDK 中提供了常用的資料結構的實現類,比如連結串列、堆疊、雜湊表,很多第三方開源庫也進行了有益的擴充套件。關於這些類的原理以及使用可以參考相關的手冊,在本節中重點講解一些使用中需要注意的問題 。

1.1.1. 增量記憶體分配

ArrayList 、 HashMap 、 Vector 等類都允許我們向其中加入任意多的物件,從而進行處理的,我們在享受它們帶來的便利的同時也要注意一定的效能問題。以 ArrayList 為例,我們來看一下在很多情況下是如何編寫出低效能的程式碼的:


Cownew開源原創:

http://www.blogjava.net/huanzhugege
在一個陣列中有若干個物件,物件的型別都是 PersonInfo , PersonInfo 的類結構如下:

public class PersonInfo

{

// 身份證號碼

private String id;

// 姓名

private String name;

// 年齡

private int age;

public PersonInfo(String id, String name, int age)

{

super();

this.id = id;

this.name = name;

this.age = age;

}



public int getAge()

{

return age;

}



public String getId()

{

return id;

}



public String getName()

{

return name;

}

}

請將所有這些 PersonInf 的身份證號碼,也就是 id 屬性,提取出來,放到另一個 List 型別的變數中。

實現程式碼 1 :

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();

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

{

PersonInfo pInfo = persons[i];

list.add(pInfo.getId());

}

程式執行正常,好像沒有出現任何問題。程式也確實真的不會出現問題,因為其邏輯如此簡單,不會但來問題。但是這個程式效能是最優的嗎?

讓我們來看看 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);

}

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/7199667/viewspace-905690/,如需轉載,請註明出處,否則將追究法律責任。

相關文章