跳到主要內容

Java 物件容器筆記

在Java裡,對於物件的收集、處理有兩種不同的概念
1.Collection -
   收集物件並形成一個具有相同處理規範的物件集合
   List 在收集物件時會記錄其索引順序,所以可藉由索引取回物件
   Set 會過濾掉已經收集過的物件,形成一組不重複的物件集合
   Queue 以佇列的形式收集物件,對於佇列兩端的物件有較方便的操作

2.Map -
   Map是一群成對的key-value物件,讓使用者可以透過另外一個物件查詢目標值
   對於物件集合,提供了搜尋的方便性


java.util.Collection 介面定義了 add()、remove()等method
另外其子類別容器也都要實作 java.lang.Iterable 介面,所以容器中的物件也都可以逐一被處理

ArrayList實作時使用Object陣列來保存收的物件
陣列在記憶體中是連續的線性空間
根據索引進行隨機存取的速度較快,但對調整索引順序的處理效率則較差
此外,因使用連續空間,陣列的長度固定也需要考量
ArrayList內部陣列長度不夠時會建立新陣列,並將舊陣列的參考指定給新陣列
如果大致知道需要的空間,事先建立足夠長度的內部陣列可以節省建立新陣列的成本
相關可以看物件容器容量大小的調整一文

LinkedList使用了Link結構,只有在加入新物件時才會耗費成本
但Link結構找物件時都必須從第一個Node開始
以指定索引隨機存取物件的情況來說效率較差
相對的在調整索引順序再有較好的表現

set的目的則是不收集重複的物件
例如HashSet的運作原理是在加入物件時,會先呼叫物件的hashCode()取得雜湊碼
並將其放入相對應雜湊碼的雜湊桶(Hash bucket)
若裡面已事先有物件則再呼叫equals()進行比較
只有equals()為false表示兩個並非重複物件時才可以收藏

實際上許多Java判斷物件是否重複的方法都會呼叫hashCode()和equals()
Java的規格書建議兩個方法必須同時實作,如:
class demo {
    private String name;
    private int number;
    demo(String name, int number) {
        this.name = name;
        this.number = number;
    }

    @Override
    public bolean equals(Object obj) {
        if (obj == null) {  return false;}
        if (getClass() != obj.getClass()) {  return false;}
        return true;
    }
    @Override
    public int hashCode() {  return number;}
}

如果希望收集物件的方式是用佇列
也就是物件會從佇列尾端開始放入,由前端開始取出,則可以使用實作Queue介面的物件
例如LinkedList除了實作List介面外,也實作了Queue的子介面Deque,所以也可以當佇列使用
Queue繼承了add()、remove()、element()等方法,但也另外定義了offer()、poll()、peek()等方法
兩者的差別在於 add()、remove()等在操作失敗時會拋出例外,Queue介面定義的方法在失敗時回傳特定值
所以打算使用Queue,且佇列長度有所限制時,建議使用offer()、poll()、peek()
offer()會在佇列尾端加入物件,且成功時回傳true,失敗則回傳false
poll()用來取出佇列前端物件,如果佇列裡沒有物件則回傳 null
peek()則是取得,但不取出佇列前端物件,如果佇列裡沒有物件則回傳 null


Collection介面中定義了Iterator,所以無論是List、Set、Queue都可以取得Iterator
透過Iterator的 hasNext()可以判斷是否有下一個物件,有的話再透過 next() 取得
依這樣的操作,可以繞遍容器中的每個物件並做處理
ArrayList < Integer > al = new ArrayList < Integer > ();
al.add(1);al.add(3);al.add(5);
Iterator < Integer > i = al.iterator();
while (i.hasNext()) {
    System.out.println(i.next());
}

在JDK5有增強式for迴圈後,也可以利用這個語法糖少寫一些程式
編譯時JVM在底層一樣會展開成上面的形式
for(int num : al){
    System.out.println(num);
}

排序也是容器的重要功能之一,java.util.Collection本身就有sort() method 可以用
但若物件不是基本型別或排序的規則不符我們的需求
則需另外讓物件實作Comparable介面、或建立Comparator物件後讓sort()引用

class demoObj implements Comparable {
    private int value;
    ....
    @Override
    public int comparaTo(Object o) {
        demoObj other = (demoObj) o;
        return this.value - other.value
    }
}

Collections.sort(arraylist, new Comparator() {
    public int compare(Object o1, Object o2) {
        HashMap m1 = (HashMap) o1;
        HashMap m2 = (HashMap) o2;
        return Integer.parseInt(m2.get(rank).toString()) - Integer.parseInt(m1.get(rank).toString());
    }
});


Map的主要目的就是快速的查詢物件,下述例子以HashMap為例解釋Map的運作方式
HashMap<String, Integer> hm = new HashMap<>();
hm.put("1st", 1);hm.put("2nd", 2);

使用put()放入方便查詢的key以及儲存用的value
儲存時Map物件會比對hashCode()和equals()兩個方法,確保在Map中絕對不會有重複的key

java.util..Properties則是另一個常用的Map子類別,常用於存取設定檔的資料
在國際化、資料庫的部分幾乎確定會再用到它
建立一個.properties 副檔名的檔案,並在其中寫入資料,如:

demo.properties======

paramA=1
paramB=2

定義一個專門處理properties檔的類別

public class PropertiesOperater {
    private Properties prop;

    public boolean loadProperties(String filePath) {
        prop = new Properties();
        try {
            prop.load(new FileInputStream(filePath)); //load結束後會自動關閉InputStream,JDK7 auto closable
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return false;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    public boolean saveProperties(String filePath) {
        try {
            prop.store(new FileOutputStream(filePath), null);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        prop.clear();
        return true;
    }

    public String getProperties(String key) {
        return prop.getProperty(key);
    }

    public void setProperties(String key, String value) {
        prop.setProperty(key, value);
    }

    public void removeProperties(String key) {
        prop.remove(key);
    }

    public Map getPropertiesMaps() {
        return prop;
    }
}

實際使用:
PropertiesOperater po = new PropertiesOperater();
if (po.loadProperties("D:\\demo.properties")) {
    Map < String, String > map = po.getPropertiesMaps();
    for (Map.Entry < String, String > m: map.entrySet()) {
        out.print(m.getKey() + " " + m.getValue() + "<br />");
    }
}

/*output:
**paramB 2
**paramA 1
*/

想走訪完Map裡的每一個物件,可以使用entrySet() 取得Set物件
entrySet()是效率較keySet()好的方法,因為他只在取出的時候判斷一次型別
取出的每個元素都是Map.Entry的Instance,可以呼叫getKey()取得Key,或getValue()取得value
如下範例:
for (Map.Entry < Integer, String > m : map.entrySet()) {
    out.print(m.getKey() + " " + m.getValue() + "<br />");
}

如果要排序Map,最簡單的方式是建立一個TreeMap
TreeMap再放入資料時便會依照Key進行排序,之後取出entrySet就可以看到依Key順序走訪
如果排序的基準與Key無關,則需要將它轉型成LinkedList後,再重新排序放入LinkedHashMap
例如以下示範資料依 Value 重新排序:
List < Map.Entry < String, String >> list = new LinkedList < Map.Entry < String, String >> (result.entrySet());
Collections.sort(list, new Comparator < Map.Entry < String, String >> () {
    public int compare(Map.Entry < String, String > m1, Map.Entry < String, String > m2) {
        String s1 = m1.getValue();
        String s2 = m2.getValue();
        return s1.compareTo(s2);
    }
});
Map < String, String > temp = new LinkedHashMap < String, String > ();
for (Iterator < Map.Entry < String, String >> it = list.iterator(); it.hasNext();) {
    Map.Entry < String, String > entry = it.next();
    temp.put(entry.getKey(), entry.getValue());
}

return temp;

留言