JAVA容器

(1)Collection接口和Collections類的區別

Collection是個java.util下的接口,它是各種集合結構的父接口。

Collections是個java.util下的類,它包含有各種有關集合操作的靜態方法。

Collection 層次結構中的根接口。Collection 表示一組對象,這些對象也稱爲 collection元素。一些 collection 允許有重複的元素,而另一些則不允許。一些 collection 是有序的,而另一些則是無序的。JDK 不提供此接口的任何直接 實現:它提供更具體的子接口(如 Set List)實現。此接口通常用來傳遞 collection,並在需要最大普遍性的地方操作這些 collection

Collection Collections 的區別。

Collection是集合類的上級接口,繼承與他的接口主要有Set List.

Collections是針對集合類的一個幫助類,他提供一系列靜態方法實現對各種集合的搜索、排序、線程安全化等操作

(2)實現比較的Comparable接口和Comparator

ü  Comparable接口(裏面只定義了一個compareTo方法)

public interface Comparable<T> {

    public int compareTo(T o);

}

ü  Comparator

public interface Comparator<T> {

    int compare(T o1, T o2);

    boolean equals(Object obj);

}

(3)ListMapSet三個接口,存取元素時,各有什麼特點

ü  ListSet具有相似性,都是單列元素的集合,因此他們都有一個共同的父接口Collection

ü  List表示先後順序的集合,因此可以插入相等的元素,而Set裏面不允許有重複的元素,即不能有兩個相等的對象,這個相等取決於對象的equals方法;

ü  下面我們來深入討論底層中HashSetadd方法的實現:

public class HashSet<E>

    extends AbstractSet<E>

    implements Set<E>, Cloneable, java.io.Serializable

{

    private transient HashMap<E,Object> map;

    // Dummy value to associate with an Object in the backing Map

    private static final Object PRESENT = new Object();

    public HashSet() {

        map = new HashMap<E,Object>();

    }

    public boolean contains(Object o) {

        return map.containsKey(o);

    }

    public boolean add(E e) {

        return map.put(e, PRESENT)==null;

    }

}

驚訝地發現,HashSet元素的唯一性是藉助HashMapKey的唯一性來實現的。因此現在再深入看看HashMap中是如何保證Key的唯一性的:

public V put(K key, V value) {

    if (key == null)

       return putForNullKey(value);

    int hash = hash(key.hashCode());

    int i = indexFor(hash, table.length);

for (Entry<K,V> e = table[i]; e != null; e = e.next)

{

       Object k;

        if (e.hash == hash && ((k = e.key) == key

                                      || key.equals(k))) {

                V oldValue = e.value;

                e.value = value;

                e.recordAccess(this);

                return oldValue;

      }

}

從這段代碼中可以看出,HashMap中的Key是根據對象的hashCode() euqals()來判斷是否唯一的。

結論:爲了保證HashSet中的對象不會出現重複值,在被存放元素的類中必須要重寫hashCode()equals()這兩個方法。

心得:以前寫貪食蛇程序的時候就曾經遇到過用重寫HashMapequals方法來判斷添加的元素是否一樣,後來也知道了還需要重寫hashCode()方法,但是卻沒想到去看源代碼,可見做研究的必須得做深入了,這樣才能真正鞏固和學習到新知識。

擴展:綜上所述,不難推斷出:HashMap中的Key完全可以使用自定義的類,而關鍵之處就在於要同時重寫equals方法和hashCode方法,比如說有個User類,其中有個id屬性,要求其id屬性相等就認爲Key相等,故不能重新添加,要實現這樣的功能,就必須去重寫User類的equalshashCode方法了。下面是網上找的一個實例:

Ø  Dummy.java

public class Dummy {
    private int id;

    public Dummy(final int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(final int id) {
        this.id = id;
    }

    @Override
    public int hashCode() {
        return id;
    }

@Override
    public boolean equals(final Object o) {
        if (this == o) {
            return true;
        }
        if (o instanceof Dummy) {
            Dummy anotherDummy = (Dummy) o;
            return this.getId() == anotherDummy.getId();
        }
        return false;
    }

}

Ø  HashCode.java

public class HashCode {

    public static void main(String[] args) {

        Map<Dummy, String> map = new HashMap<Dummy, String>();

        map.put(new Dummy(1), "old value whose key's value is 1");

        Dummy dummy = new Dummy(1);

        map.put(dummy, "new value whose key's value is 1");

        map.put(new Dummy(2), "old value whose key's value is 2");

        for (Map.Entry entry : map.entrySet()) {

            System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());

        }

    }

}

輸出:

key: com.pdm.Dummy@1 value: new value whose key's value is 1
key: com.pdm.Dummy@2 value: old value whose key's value is 2

下面再深入JDKObject類中的equalshashCode方法:

equals hashcode 方法來自於Object類,實現分別如下:

public boolean equals(Object obj) {
         return (this == obj);
}

public native int hashCode();

通過以上兩個方法的實現,對object的這兩個方法(如果我們的自定義類不overwiter這兩個方法,就是如上默認實現),我們可以得出如下結論:

(1)  equals方法默認是對象存放地址的比較

(2)  hashcode是於存放內存地址相關的一個整形值,因爲和內存地址相關,所以用到了native關鍵字,該關鍵字表明該方法需要調用非java的接口來實現,如c,c#

繼續深入:

equals():

用於兩個對象的比較,在Object類中已經實現了這個方法,是對對象內部地址的比較,即如果兩個對象的內部地址是一樣的則是相等的。如果要按照對象內容的進行比較,就需要重載這兩個方法。Java語言對equals()的要求如下,這些要求是必須遵循的:

對稱性:如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。

反射性:x.equals(x)必須返回是“true”。

類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那麼z.equals(x)也應該返回是“true”。

一致性:如果x.equals(y)返回是“true”,只要xy內容一直不變,不管你重複x.equals(y)多少次,返回都是“true”。

    任何情況下,x.equals(null)永遠返回是“false”;x.equals(x不同類型的對象)永遠返回是“false”。

hashCode():

    這個方法返回的是一個用來進行hash操作的整型數,可用於在Collection對象中定位特定對象的位置。Object中默認的實現是把對象內部地址轉化爲整數作爲hashCode

    hashCode()的返回值和equals()的關係如下:

如果x.equals(y)返回“true”,那麼xyhashCode()必須相等。

如果x.equals(y)返回“false”,那麼xyhashCode()有可能相等,也有可能不等。

    這裏有必要說明一下HashMap的原理。爲了優化查找對象的性能,在HashMap中按照鍵值對象的hash值放了若干個箱子,當有一個鍵值對象加入進來時,調用鍵值對象的hashCode()方法,根據計算出的hash值把對象放入對應的箱子。當對鍵值對象進行查找時,首先計算對象的hash值,找到對應的箱子,然後調用equals()與箱子中的對象逐個比較,直到找出相等的對象或者遍歷了一遍。

    如果x.equals(y)返回“true”,而x.hashCode() != y.hashCode()會有什麼後果呢?假設把xy方法HashMap,即hashMap.put(x, xValue)hashMap.put(y, yValue),在hashMap中會存在兩個元素xy,而不是一個。原因很好理解,因爲二者的hashCode不同,在hashMap中會放入不用的箱子,從而會被認爲是兩個對象。

    使用hashCode的目的是爲了把對象散列在不同的地方,從而提高檢索對象的性能,所以編寫一個好的hashCode算法相當重要。在Effective Java 2 edition 中給出了一個簡單的hashCode算法:

l  保存一些常量非0值,例如17,在一個intresult變量中。這個值可以是任意的,但是最好不是0,會增加衝突的可能。

l  對象每一個重要的域f(就是equals方法使用到那些field)做以下操作:

    a)對域f計算一個int的哈希碼c

i.如果fieldboolean型的,計算(f ? 1 : 0)

ii.如果fieldbyte,char,int之類的,計算 (int)f

iii.如果fieldlong, 計算 (int) (f^(f>>32))

iv.如果fieldfloat,計算 Float.floatToIntBits(f)

v.如果fielddouble,計算Double.doubleToLongBits(f),然後使用2.a.iii的方法計算

hash code

vi.如果field是一個對象的引用,並且這個類的此域比較的equals方法使用了遞歸的調用equals,直接遞歸的調用hashCode方法。如果一個更復雜的比較是必須的,對此域計算”canonical representation(規範的表示)”並且在”規範的表示”上調用hashCode(invoke hashCode on the canonical representation)。如果此fieldnull,返回0

vii.如果fieldarray,將數組中的每個元素作爲一個單獨的field,用上述規則來計算,並使用2.b中的方式合併這些值。如果每個元素都是significant,你可以使用1.5版本添加的Arrays.hasCode方法。

    b)合併2.a中哈希碼c的值到result使用下面的方法:

                   result=31*result+c;

l  返回result

l  當你完成hashCode函數之後,自我檢驗一下相等的實例是否有相等的hash code。編寫單元測試去檢驗你所想的結果。如果相等實例有不同的hash code,找出原因並解決。

說了那麼多,應該可以明白了。不過上面還有一個細節,那就是在上面那個實例輸出結果中有個@後面跟了個整數,其實就是hashCode的值,上面由於重寫了DummyhashCode方法,因此返回的是id的值。

public String toString() {

return getClass().getName() + "@" + Integer.toHexString(hashCode());

}

ü  List集合可以使用get(int index)方法取得具體指定的元素ddeCode可以明白了。不過上面還有一個細節,那就是在輸出的,但是Set取元素時,沒法取得第幾個,只能以Iterator接口取得所有的元素,再逐一遍歷各個元素;

ü  MapListSet不同,他是雙列的集合,其中有put方法,定義如下:put(obj key, obj value),每次存儲時,要存儲一對key/value,不能存儲重複的key,這個重複的定義是按照equalshashCode方法來決定的;

(4)ArrayListVector的異同

這兩個類都實現了List接口(List接口繼承了Collection接口),他們都是有序的集

合,即存儲在這兩個集合中的元素的位置都是有順序的,相當於一種動態的數組,我們可以按位置索引出某個元素,並且其中的數據允許重複,區別主要包括:

ü  同步性:Vector是線程安全的,也就是他的方法之間是線程同步的:

public synchronized void ensureCapacity(int minCapacity) {

modCount++;

    ensureCapacityHelper(minCapacity);

}

private void ensureCapacityHelper(int minCapacity) {

    int oldCapacity = elementData.length;

    if (minCapacity > oldCapacity) {

        Object[] oldData = elementData;

        int newCapacity = (capacityIncrement > 0) ?

       (oldCapacity + capacityIncrement) : (oldCapacity * 2);

        if (newCapacity < minCapacity) {

       newCapacity = minCapacity;

        }

            elementData = Arrays.copyOf(elementData, newCapacity);

    }

}

public synchronized void setSize(int newSize) {

    modCount++;

    if (newSize > elementCount) {

        ensureCapacityHelper(newSize);

    } else {

        for (int i = newSize ; i < elementCount ; i++) {

       elementData[i] = null;

        }

    }

    elementCount = newSize;

}

ArrayList是線程不安全的,他的方法之間線程是不同步的。如果只有一個線程會訪問到集合,那最好是使用ArrayList,因爲他不考慮線程安全,效率會高一些;如果有多個線程會訪問到集合,那最好是使用Vector,因爲不需要我們自己再去考慮和編寫線程安全的代碼;

備註:對於VectorArrayListHashtableHashMap,要記住線程安全的問題,記住VectorHashtable是舊的,是java一開始就提供了的,他們是線程安全的,ArrayListHashMapjava2時才提供的,他們是線程不安全的。

ü  數據增長:ArrayListVector都有一個初始容量大小(默認爲10),當存儲他們裏面的

元素個數超過了容量時,就需要增加ArrayListVector的存儲空間,每次要增加存儲空間時,不是隻增加一個存儲單元,而是增加多個存儲單元:

l  對於Vector: int newCapacity = (capacityIncrement > 0) ?

       (oldCapacity + capacityIncrement) : (oldCapacity * 2);

l  對於ArrayList: int newCapacity = (oldCapacity * 3)/2 + 1;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章