Java容器研究及優化篇(一)
在Java環境中,一切都是以對象角色而存在,我們需要一種機制來集中管理這些對象,並對其中每個對象進行方便的插入、檢索、修改及刪除操作,這種機制稱之爲集合,也叫做容器。而我們最爲常用的容器包括:List、Set、Map、Stack及Queue,它們都有各自的特性和擅長,也有不同的繼承接口,內部的數據存儲算法或格式也有所不同。本篇爲集合研究的第一部分,主要以滿足使用爲前提,對常用集合的使用進行介紹;而第二部分則爲深入的研究下這些集合,並在最後給出個封裝的工具供參考使用。
l 容器存放內容
l 容器編程方式
l 常用容器特性
l 堆棧隊列實現
l 如何迭代容器
1、容器存放內容
在所有的java容器中,存放的內容都爲對象類型,不論是基本數據類型、系統默認對象或是自定義的對象。如果類型爲基本數據類型,那麼容器會自動將其升級轉爲對應的封裝對象類型,具體如下所示:
publicvoid test(String args) {
List<Object> lists = newArrayList<Object>();
// 基本數據類型
lists.add(1);
// 系統默認對象
lists.add(new Date());
lists.add(new String(args));
// 自定義的對象
Custom custom = newCustom("Jakey","13155578923");
lists.add(custom);
for(Object obj : lists) {
logger.log(Level.DEBUG,"數據類型:"+obj.getClass().getTypeName());
}
lists.clear();
lists = null;
}
顯示結果:
2、容器編程方式
在java編程中,面向接口或是抽象編程是個不錯的選擇,而java中的容器編程也可以選擇這種優雅的方式。一般,我們會將使用的具體的容器類向上轉型爲基礎接口,只在創建時指定具體的子容器類,這樣做的好處就是可以減少代碼的修改,做到抽象和共用的效果,以List爲例說明:
List<Object>lists = new ArrayList<Object>();
而List即爲接口基類,ArrayList爲List的派生子類,只不過增加了特有的功能實現。List的常用子類還有LinkedList,那麼實現類似:
List<Object>lists = new LinkedList<Object>();
對於其它的容器類,如map、set、queue等方式與List類似,這裏不做介紹。上面的面向接口方式,在Java容器中並不總是奏效,如果使用子類時,用到了子類新增的特有功能,而List接口並沒有實現,那麼自然而然就必須使用子類來具體實現,所以需要結合具體的使用而論。不過依經驗而論,容器基礎接口類的功能最爲常用,因爲基本可以滿足業務需求哦。
注意:
面向接口或是抽象編程,是我們必須要掌握的技能,請查閱相關資料學習。
3、常用容器特性
在Java編程中,最爲常用的容器有:List、Map、Set及Queue,他們各自有自己擅長的一面,可以從存儲和性能兩方面考慮他們的異同,下面具體從它們的性能和存儲方式兩方面介紹下:
A、List
List容器可以將內部元素維護在特定的序列中,默認是按照元素插入的順序排列內部元素,其有兩種類型的子類,分別爲:
ArrayList:
該容器適合隨機訪問元素,查詢獲取元素,而不擅長在List中間插入和刪除元素,所以適合用在存儲和檢索元素。
LinkedList:
該容器在隨機訪問元素較慢,適合耗用較低代價在List中插入和刪除元素,同時,它的特性集相對ArrayList更豐富。
所以,在查詢大於修改List存儲結構的情況下,建議使用List容器;反之,極力推薦使用LinkedList容器,據實際使用需求而定。
B、Map
Map容器可以實現將對象映射到其它對象,實際上是通過對象的引用來實現,因爲Map容器存放數據的格式是鍵-值形式,採用了高讀寫的離散算法來實現數據存取,所以它在速度上較快,而且其存儲的值爲對象的引用,元素的順序是離散不確定的,通過該引用可以關聯到對應的實體類對象,現有如下幾種Map容器:
HashMap:
訪問速度最快,鍵-值元素並沒有排序,插入和刪除速度相對較慢;
TreeMap:
訪問速度較HashMap慢些,因爲其保持了“鍵”處於排序狀態;
LinkedHashMap:
訪問速度較快,僅次於HashMap訪問速度,同時其也保持了插入元素的次序,並通過散列提供了快速的訪問能力。
HashTable:
HashTable是舊版的jdk中的容器,已經被新版的HashMap所替代,
而與HashMap的不同點如下:
HashTable是同步的,而HashMap非同步;
1)HashTable鍵-值都不允許爲空,而HashMap反之都允許;
2)兩者的遍歷方式不同,前者使用Enumeration,後者使用Iterator;
3)哈希值使用不同,前者使用對象的hashCode,後者重新計算hash值;
4)默認大小不同,前者默認大小爲11,並且以old*2+1增加,而後者默認大小爲16,並以2的指數增長;
C、Set
Set容器不保存重複的元素內容,如果試圖添加重複元素,則Set會阻止該行爲,而常用的Set集合類型有:HashSet、TreeSet及LinkedHashSet,它們的存在足夠解決實際元素存儲的需求了。
HashSet:
出於性能考慮,HashSet內元素沒有順序可循,使用了散列算法。其維護的元素的順序與TreeSet和LinkedHashSet都不同,因爲它們存儲元素的方式不同,所以HashSet的查詢速度極快。
TreeSet:
與HashSet不同,TreeSet內元素是規律排序的,其元素存儲在紅-黑數據結構中,而HashSet使用散列函數生成。當然,其在查詢方面,不如HashSet快些,適合查找小量排序元素及插入和刪除頻繁的情況使用。
LinkedHashSet:
該容器彌補了HashSet和TreeSet各自的不足,其內部元素即是排序,查詢速度也比較快,速度介於HashSet和TreeSet之間,是因爲內部使用鏈表形式排序存儲元素,並且採用散列加快元素查詢的速度。如果在不確定使用HashSet或是TreeSet時,就可以使用它了。
D、Queue
隊列是先進先出的容器(FIFO),即從容器的一端插入元素,從另一端取出,並且取出元素的順序與入容器的順序是相同的,其在併發編程中使用比較頻繁,是一種可靠的將對象從一個區域傳遞到另一個區域的方法。
在Java SE5中,引入了PriorityQueue隊列,該隊列內元素的順序爲將對象入隊的自然順序排序,也可以通過Comparator進行修改排序。PriorityQueue可以保證使用peek()、poll()及remove()時,獲取到優先級最高的元素哦,前提我們只需要對入隊的元素設置有限權重值即可。
E、Stack
“堆棧”通常是後進先出(LIFO)的容器,因爲最後入棧的元素,最先彈出堆棧。鑑於Java設計中,Stack的設計不靈活,有缺陷,一般會使用LinkedList來替代實現更靈活的Stack類似功能,該實現將在下面第5部分介紹,這裏不在介紹。
4、堆棧隊列實現
這裏我們使用LinkedList容器來靈活實現堆棧和隊列功能,因爲其已經具備直接實現棧和隊列的功能方法,所以使用其實現比較順理成章了。下面具體介紹下如何實現,這裏會用到泛型的概念,這個會在後續文章介紹,這裏各位只需要知道泛型是一個可以兼容不同對象類型的數據類型即可。
A、堆棧
Stack代碼:
publicclass Stack<T> {
private Logger logger =null;
// 選用實現容器
private LinkedList<T> storage =new LinkedList<T>();
public Stack() {
logger = LogManager.getLogger(Stack.class.getName());
logger.debug("Java容器堆棧實現日誌...");
}
// 元素壓入堆棧
public void push(T v) {
logger.log(Level.DEBUG,"元素壓入堆棧...,內容:"+v.toString());
storage.addFirst(v);
}
// 取出頂部元素
public T peek() {
logger.log(Level.DEBUG,"取出頂部元素...,內容:"+storage.getFirst().toString());
return storage.getFirst();
}
// 從棧刪除元素
public T pop() {
logger.log(Level.DEBUG,"從棧刪除元素...,內容:"+storage.removeFirst());
return storage.removeFirst();
}
// 判斷棧是否空
public boolean empty() {
logger.log(Level.DEBUG,"判斷棧是否空...,結果:"+storage.isEmpty());
return storage.isEmpty();
}
// 字符格式化棧
public String toString() {
logger.log(Level.DEBUG,"字符格式化棧...,結果:"+storage.toString());
return storage.toString();
}
}
單元測試代碼:
@Test
publicvoid logPrintStack() {
Stack<Object> stack = newStack<Object>();
// 壓入堆棧元素
stack.push("2016年");
stack.push("10月");
stack.push("19日");
// 獲得堆棧元素
stack.peek();
// 堆棧的空判斷
if(!stack.empty()) {
stack.pop();
}
// 字符串化堆棧
stack.toString();
}
刪除元素前結果:
刪除元素後結果:
請大家仔細對比下,刪除元素前後的結果變化,會發現結果完全符合上面所論述的事實。需要注意的是,我們自定義的Stack與系統默認的Stack名字衝突,所以在使用時需要加上包名字路徑(前提是不在同一個包)。
B、隊列
Queue代碼:
publicclass Queue<T> {
private Logger logger =null;
// 選用實現容器
private LinkedList<T> storage =new LinkedList<T>();
public Queue() {
logger = LogManager.getLogger(Queue.class.getName());
logger.debug("Java容器隊列實現日誌...");
}
// 元素壓入隊列
public void offer(T v) {
logger.log(Level.DEBUG,"元素壓入隊列...,內容:"+v.toString());
storage.addLast(v);
}
// 隊列取出元素
public T peek() {
logger.log(Level.DEBUG,"隊列取出元素...,內容:"+storage.getFirst().toString());
return storage.getFirst();
}
// 隊列刪除元素
public T poll() {
logger.log(Level.DEBUG,"隊列刪除元素...,內容:"+storage.getFirst());
return storage.removeFirst();
}
// 判斷隊列是否空
public boolean empty() {
logger.log(Level.DEBUG,"判斷隊列是否空...,結果:"+storage.isEmpty());
return storage.isEmpty();
}
// 字符格式化隊列
public String toString() {
logger.log(Level.DEBUG,"字符格式化隊列...,結果:"+storage.toString());
return storage.toString();
}
}
單元測試代碼:
@Test
public void logPrintQueue() {
Queue<Object> queue = newQueue<Object>();
// 壓入堆棧元素
queue.offer("2016年");
queue.offer("10月");
queue.offer("19日");
// 獲得堆棧元素
queue.peek();
// 堆棧的空判斷
if(!queue.empty()) {
queue.poll();
}
// 字符串化堆棧
queue.toString();
}
刪除元素前結果:
刪除元素後結果:
5、如何迭代容器
正如上面介紹了容器,容器中一般存在很多元素,我們不可能一一的手動獲取,而是需要遍歷容器的元素,並最終查看或是修改容器元素。那麼,我們目前可以使用兩種容器迭代器,分別爲Iterator和Iterable。
A、Iterator
Iterator迭代器接口能做的事情有:
1)使用iterator()要求返回一個Iterator,其並返回第一個元素;
2)使用hasNext()檢查容器序列中是否還有元素存在;
3)使用next()獲得容器序列的下一個元素;
4)使用remove()刪除新近返回的元素;
如果我們只需要向前遍歷容器,並不需要修改容器本身,那麼我們可以使用更遍潔的Iterable的foreach語法會更好,具體使用如下:
@Test
publicvoid logPrintScan() {
// Iterator
List<Object> lists = newArrayList<Object>();
lists.add("2016年");
lists.add("10月");
lists.add("19日");
Iterator<Object> it = lists.iterator();
while(it.hasNext()) {
String item = (String) it.next();
logger.log(Level.DEBUG,"容器元素如下:"+item);
}
lists.clear();
lists = null;
}
結果顯示:
如果我們需要雙向遍歷容器集合,也需要修改元素容器本身結構,那麼可以使用Iterator的子類型ListIterator,前提是其只能用來遍歷List容器,不能遍歷Map容器哦,具體用法如下:
@Test
public void logPrintScan() {
// Iterator
List<Object> lists = newArrayList<Object>();
lists.add("2016年");
lists.add("10月");
lists.add("19日");
// 前向遍歷
ListIterator<Object> it2 = lists.listIterator();
while(it2.hasNext()) {
String item = (String) it2.next();
logger.log(Level.DEBUG,"容器前向遍歷元素如下:"+item+"元素位置:"+it2.nextIndex());
}
// 後向遍歷
while(it2.hasPrevious()) {
String item = (String) it2.previous();
logger.log(Level.DEBUG,"容器後向遍歷元素如下:"+item+"元素位置:"+it2.previousIndex());
}
lists.clear();
lists = null;
}
結果顯示:
B、Iterable
Iterable是Java SE5引入的新的接口,該接口包含了一個能夠產生Iterator的iterator()方法,並且該Iterable接口被foreach用來在容器元素序列中移動。因此,如果創建了任何實現了Iterable的類,都可以將它用在foreach語法中。有些同學會問,爲什麼在容器中可以直接使用foreach,並不需要實現Iterable接口?答案是否定的,因爲在Java容器中,Collections都已經實現了該接口(除了Map容器),具體實例如下:
@Test
publicvoid logPrintScan() {
List<Object> lists =new ArrayList<Object>();
lists.add("2016年");
lists.add("10月");
lists.add("19日");
// Iterable
for(Object item : lists) {
String itemStr = (String) item;
logger.log(Level.DEBUG,"容器遍歷元素如下:"+itemStr);
}
lists.clear();
lists = null;
}
結果顯示:
Java容器研究及優化(一)就介紹到這裏,由於作者水平有限,如有問題請在評論發言或是QQ羣(245389109(新))討論,謝謝。
轉載請標明出處,原創不易,請尊重作者勞作成果,標明轉載地址:
http://blog.csdn.net/why_2012_gogo/article/details/52861232