Java集合詳解


Java集合詳解

一、數組和集合的比較

數組不是面向對象的,存在明顯的缺陷,集合彌補了數組的缺點,比數組更靈活更實用,而且不同的集合框架類可適用不同場合。如下:

  1. 數組能存放基本數據類型和對象,而集合類存放的都是對象,集合類不能存放基本數據類型。數組和集合存放的對象皆為對象的引用地址。
  2. 數組容易固定無法動態改變,集合類容量動態改變。
  3. 數組無法判斷其中實際存有多少元素,length只告訴了數組的容量,而集合的size()可以確切知道元素的個數
  4. 集合有多種實現方式和不同適用場合,不像數組僅采用順序表方式
  5. 集合以類的形式存在,具有封裝、繼承、多態等類的特性,通過簡單的方法和屬性即可實現各種復雜操作,大大提高了軟件的開發效率

二、Java集合

 

 

 

 

 

Collection和Map,是集合框架的根接口。

Collection的子接口:

Set:接口 ---實現類: HashSet、LinkedHashSet
Set的子接口SortedSet接口---實現類:TreeSet
List:接口---實現類: LinkedList,Vector,ArrayList

List集合

有序列表,允許存放重復的元素;
實現類:

  • ArrayList:數組實現,查詢快,增刪慢,輕量級;(線程不安全)
  • LinkedList:雙向鏈表實現,增刪快,查詢慢 (線程不安全)
  • Vector:數組實現,重量級 (線程安全、使用少)
ArrayList

底層是Object數組,所以ArrayList具有數組的查詢速度快的優點以及增刪速度慢的缺點。

而在LinkedList的底層是一種雙向循環鏈表。在此鏈表上每一個數據節點都由三部分組成:前指針(指向前面的節點的位置),數據,后指針(指向后面的節點的位置)。最后一個節點的后指針指向第一個節點的前指針,形成一個循環。

雙向循環鏈表的查詢效率低但是增刪效率高。

ArrayList和LinkedList在用法上沒有區別,但是在功能上還是有區別的。

ArrayList自動擴充機制

實現機制:ArrayList.ensureCapacity(int minCapacity)
首先得到當前elementData 屬性的長度oldCapacity。
然后通過判斷oldCapacity和minCapacity參數誰大來決定是否需要擴容, 如果minCapacity大於 oldCapacity,那么我們就對當前的List對象進行擴容。 擴容的的策略為:取(oldCapacity * 3)/2 + 1和minCapacity之間更大的那個。然后使用數組拷 貝的方法,把以前存放的數據轉移到新的數組對象中 如果minCapacity不大於oldCapacity那么就不進行擴容。

LinkedList

LinkedList是采用雙向循環鏈表實現的。

利用LinkedList實現棧(stack)、隊列(queue)、雙向隊列(double-ended queue )。 它具有方法addFirst()、addLast()、getFirst()、getLast()、removeFirst()、removeLast()等。

經常用在增刪操作較多而查詢操作很少的情況下:

隊列和堆棧。

隊列:先進先出的數據結構。

棧:后進先出的數據結構。

注意:使用棧的時候一定不能提供方法讓不是最后一個元素的元素獲得出棧的機會。

用LinkedList實現隊列:

隊列(Queue)是限定所有的插入只能在表的一端進行,而所有的刪除都在表的另一端進行的線性表。
表中允許插入的一端稱為隊尾(Rear),允許刪除的一端稱為隊頭(Front)。
隊列的操作是按先進先出(FIFO)的原則進行的。
隊列的物理存儲可以用順序存儲結構,也可以用鏈式存儲結構。

用LinkedList實現棧:

棧(Stack)也是一種特殊的線性表,是一種后進先出(LIFO)的結構。
棧是限定僅在表尾進行插入和刪除運算的線性表,表尾稱為棧頂(top),表頭稱為棧底(bottom)。
棧的物理存儲可以用順序存儲結構,也可以用鏈式存儲結構。

Vector

(與ArrayList相似,區別是Vector是重量級的組件,使用使消耗的資源比較多。)

結論:在考慮並發的情況下用Vector(保證線程的安全)。

在不考慮並發的情況下用ArrayList(不能保證線程的安全)。

List常用方法:

void add(int index, Object element) :添加對象element到位置index上
boolean addAll(int index, Collection collection) :在index位置后添加容器collection中所有的元素
Object get(int index) :取出下標為index的位置的元素
int indexOf(Object element) :查找對象element 在List中第一次出現的位置
int lastIndexOf(Object element) :查找對象element 在List中最后出現的位置
Object remove(int index) :刪除index位置上的元素
ListIterator listIterator(int startIndex) :返回一個ListIterator 跌代器,開始位置為startIndex
List subList(int fromIndex, int toIndex) :返回一個子列表List ,元素存放為從 fromIndex 到toIndex之前的一個元素

Set集合

擴展Collection接口
無序集合,不允許存放重復的元素;允許使用null元素
對 add()、equals() 和 hashCode() 方法添加了限制
HashSet和TreeSet是Set的實現

 
 
Set
hashSet-linkedHashSet
 
 
SortedSet
TreeSet

HashSet 的后台有一個HashMap;初始化后台容量;只不過生成一個HashSet的話,系統只提供key的訪問; 如果有兩個Key重復,那么會覆蓋之前的;

實現類 :

HashSet:equals返回true,hashCode返回相同的整數;哈希表;存儲的數據是無序的。
LinkedHashSet:此實現與HashSet的不同之外在於,后者維護着一個運行於所有條目的雙重鏈接列表。存儲的數據是有序的。

HashSet類

HashSet類直接實現了Set接口,其底層其實是包裝了一個HashMap去實現的。HashSet采用HashCode算法來存取集合中的元素,因此具有比較好的讀取和查找性能。

HashSet的特征:

  • 不僅不能保證元素插入的順序,而且在元素在以后的順序中也可能變化(這是由HashSet按HashCode存儲對象(元素)決定的,對象變化則可能導致HashCode變化)
  • HashSet是線程非安全的
  • HashSet元素值可以為NULL

HashSet常用方法:

  • public boolean contains(Object o) :如果set包含指定元素,返回true
  • public Iterator iterator()返回set中元素的迭代器
  • public Object[] toArray() :返回包含set中所有元素的數組public Object[] toArray(Object[] a) :返回包含set中所有元素的數組,返回數組的運行時類型是指定數組的運行時類型
  • public boolean add(Object o) :如果set中不存在指定元素,則向set加入
  • public boolean remove(Object o) :如果set中存在指定元素,則從set中刪除
  • public boolean removeAll(Collection c) :如果set包含指定集合,則從set中刪除指定集合的所有元素
  • public boolean containsAll(Collection c) :如果set包含指定集合的所有元素,返回true。如果指定集合也是一個set,只有是當前set的子集時,方法返回true

實現Set接口的HashSet,依靠HashMap來實現的。 我們應該為要存放到散列表的各個對象定義hashCode()和equals()。

HashSet的equals和HashCode:

前面說過,Set集合是不允許重復元素的,否則將會引發各種奇怪的問題。那么HashSet如何判斷元素重復呢?
HashSet需要同時通過equals和HashCode來判斷兩個元素是否相等,具體規則是,如果兩個元素通過equals為true,並且兩個元素的hashCode相等,則這兩個元素相等(即重復)。
所以如果要重寫保存在HashSet中的對象的equals方法,也要重寫hashCode方法,重寫前后hashCode返回的結果相等(即保證保存在同一個位置)。所有參與計算 hashCode() 返回值的關鍵屬性,都應該用於作為 equals() 比較的標准。
試想如果重寫了equals方法但不重寫hashCode方法,即相同equals結果的兩個對象將會被HashSet當作兩個元素保存起來,這與我們設計HashSet的初衷不符(元素不重復)。
另外如果兩個元素哈市Code相等但equals結果不為true,HashSet會將這兩個元素保存在同一個位置,並將超過一個的元素以鏈表方式保存,這將影響HashSet的效率。
如果重寫了equals方法但沒有重寫hashCode方法,則HashSet可能無法正常工作,比如下面的例子。


import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

public class R {
    private int count;

    public R(int count) {
        this.count = count;
    }

    @Override
    public String toString() {
          return "R{" + "count=" + count + " # hashCode=" + this.hashCode() + '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        R r = (R) o;
        return count == r.count;
    }

// @Override
// public int hashCode() {
// return Objects.hash(count);
// }

    public static void main(String[] args) {
        Set set = new HashSet();
        set.add(new R(5));
        set.add(new R(-3));
        set.add(new R(9));
        set.add(new R(-2));
        System.out.println(set.contains(new R(-3)));
        System.out.println(set);

    }
}

上面注釋了hashCode方法,所以你將會看到下面的結果。

false
[R{count=9 # hashCode=2003749087}, R{count=5 # hashCode=1283928880}, R{count=-3 # hashCode=295530567}, R{count=-2 # hashCode=1324119927}]

取消注釋,則結果就正確了

true
[R{count=5 # hashCode=36}, R{count=9 # hashCode=40}, R{count=-3 # hashCode=28}, R{count=-2 # hashCode=29}]
如何達到不能存在重復元素的目的?

“鍵”就是我們要存入的對象,“值”則是一個常量。這樣可以確保,我們所需要的存儲的信息是“鍵”。而“鍵”在Map中是不能重復的,這就保證了我們存入Set中的所有的元素都不重復。
HashSet如何過濾重復元素
調用元素HashCode獲得哈希碼--》判斷哈希碼是否相等,不相等則錄入 ---》相等則判斷equals()后是否相等,不相等在進行 hashcode錄入,相等不錄入

LinkedHashSet的特征

LinkedHashSet是HashSet的一個子類,LinkedHashSet也根據HashCode的值來決定元素的存儲位置,但同時它還用一個鏈表來維護元素的插入順序,插入的時候即要計算hashCode又要維護鏈表,而遍歷的時候只需要按鏈表來訪問元素。查看LinkedHashSet的源碼發現它是樣的

public class LinkedHashSet<E> extends HashSet<E> implements Set<E>, Cloneable, java.io.Serializable {

    private static final long serialVersionUID = -2851667679971038690L;

    public LinkedHashSet(int initialCapacity, float loadFactor) {
        super(initialCapacity, loadFactor, true);
    }
....

在JAVA8中, LinkedHashSet沒有定義任何方法,只有四個構造函數,它的構造函數調用了父類(HashSet)的帶三個參數的構造方法,父類的構造函數如下

 /** * Constructs a new, empty linked hash set. (This package private * constructor is only used by LinkedHashSet.) The backing * HashMap instance is a LinkedHashMap with the specified initial * capacity and the specified load factor. * * @param initialCapacity the initial capacity of the hash map * @param loadFactor the load factor of the hash map * @param dummy ignored (distinguishes this * constructor from other int, float constructor.) * @throws IllegalArgumentException if the initial capacity is less * than zero, or if the load factor is nonpositive */
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {
        map = new LinkedHashMap<>(initialCapacity, loadFactor);
    }

由此可知,LinkedHashSet本質上也是從LinkedHashMap而來,LinkedHashSet的所有方法都繼承自HashSet, 而它能維持元素的插入順序的性質則繼承自LinkedHashMap.

下面是一個LinkedHashSet維持元素插入順序的例子

import java.util.LinkedHashSet;
import java.util.Set;

/** * ClassName: LinkedHashSets * Description: * date: 2019-11-20 17:18 * * @author chengluchao * @since JDK 1.8 */
public class LinkedHashSets {
    public static void main(String[] args) {
        Set set = new LinkedHashSet();
        set.add("abc");
        set.add("efg");
        set.add("hjk");
        System.out.println(set);
        set.remove(new String("abc"));
        set.add("abc");
        System.out.println(set);
    }
}

輸入如下

[abc, efg, hjk]
[efg, hjk, abc]
TreeSet類的特征

TreeSet實現了SortedSet接口,顧名思義這是一種排序的Set集合,查看jdk源碼發現底層是用TreeMap實現的,本質上是一個紅黑樹原理。 正因為它是排序了的,所以相對HashSet來說,TreeSet提供了一些額外的按排序位置訪問元素的方法,例如first(), last(), lower(), higher(), subSet(), headSet(), tailSet().
TreeSet的排序分兩種類型,一種是自然排序,另一種是定制排序。

自然排序(在元素中寫排序規則)

TreeSet 會調用compareTo方法比較元素大小,然后按升序排序。所以自然排序中的元素對象,都必須實現了Comparable接口,否則會拋出異常。對於TreeSet判斷元素是否重復的標准,也是調用元素從Comparable接口繼承而來額compareTo方法,如果返回0則是重復元素(兩個元素I相等)。Java的常見類都已經實現了Comparable接口,下面舉例說明沒有實現Comparable存入TreeSet時引發異常的情況。

import java.util.Set;
import java.util.TreeSet;

/** * ClassName: TestSets * Description: Tree自然排序 * date: 2019-11-20 17:27 * * @author chengluchao * @since JDK 1.8 */
public class TestSets {
    public static void main(String[] args) {
        Set set = new TreeSet();
        set.add(new Err());
        set.add(new Err());
        set.add(new Err());
        System.out.println(set);
    }
}

class Err {
}

運行程序會拋出如下異常

Exception in thread "main" java.lang.ClassCastException: clc.Err cannot be cast to java.lang.Comparable
	at java.util.TreeMap.compare(TreeMap.java:1294)
	at java.util.TreeMap.put(TreeMap.java:538)
	at java.util.TreeSet.add(TreeSet.java:255)
	at clc.TestSets.main(TestSets.java:17)

將上面的Err類實現Comparable接口之后程序就能正常運行了

class Err implements Comparable{
    @Override
    public int compareTo(Object o) {
        return 0;
    }
}

還有個重要問題是,因為TreeSet會調用元素的compareTo方法,這就要求所有元素的類型都相同,否則也會發生異常。也就是說,TreeSet只允許存入同一類的元素。例如下面這個例子就會拋出類型轉換異常

    public static void main(String[] args) {
        Set set = new TreeSet();
        set.add(1);
        set.add("2");
        System.out.println(set);
    }

運行結果

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.util.Date
	at java.util.Date.compareTo(Date.java:131)
	at java.util.TreeMap.put(TreeMap.java:568)
	at java.util.TreeSet.add(TreeSet.java:255)
	at clc.TestSets.main(TestSets.java:19)
定制排序(在集合中寫排序規則)

TreeSet還有一種排序就是定制排序,定制排序時候,需要關聯一個Comparator對象,由Comparator提供排序邏輯。下面就是一個使用Lambda表達式代替Comparator對象來提供定制排序的例子。下面是一個定制排序的列子


import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

/** * ClassName: TestSets * Description: Tree自然排序 * date: 2019-11-20 17:27 * * @author chengluchao * @since JDK 1.8 */
public class TestSets {
    public static void main(String[] args) {
        Set set = new TreeSet(new MyCommpare());
        set.add(new M(5));
        set.add(new M(3));
        set.add(new M(9));
        System.out.println(set);
    }
    
}

class M {
    int age;

    public M(int age) {
        this.age = age;
    }
}

class MyCommpare implements Comparator {
    @Override public int compare(Object o1, Object o2) {
        M m1 = (M) o1;
        M m2 = (M) o2;
        return m1.age > m2.age ? 1 : m1.age < m2.age ? -1 : 0;
    }
}

當然將Comparator直接寫入TreeSet初始化中也可以。如下。


import org.junit.Test;

import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;

/** * ClassName: TestSets * Description: Tree自然排序 * date: 2019-11-20 17:27 * * @author chengluchao * @since JDK 1.8 */
public class TestSets {
    public static void main(String[] args) {
        Set set = new TreeSet(new MyCommpare());
        set.add(new M(5));
        set.add(new M(3));
        set.add(new M(9));
        System.out.println(set);
    }

    /** * Description: Comparator直接寫入TreeSet初始化中1 * * @method: testTreeSet * @author: chengluchao * @date: 2019-11-21 09:41 * @param: * @return: */
    @Test
    public void testTreeSet() {
        Set set = new TreeSet(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                M m1 = (M) o1;
                M m2 = (M) o2;
                return m1.age > m2.age ? 1 : m1.age < m2.age ? -1 : 0;
            }
        });
        set.add(new M(5));
        set.add(new M(3));
        set.add(new M(9));
        System.out.println(set);
    }

    /** * Description: Comparator直接寫入TreeSet初始化中 * * @method: testTreeSetLam * @author: chengluchao * @date: 2019-11-21 09:41 * @param: * @return: */
    @Test
    public void testTreeSetLam() {
        Set set = new TreeSet((o1, o2) -> {
            M m1 = (M) o1;
            M m2 = (M) o2;
            return m1.age > m2.age ? 1 : m1.age < m2.age ? -1 : 0;
        });
        set.add(new M(5));
        set.add(new M(3));
        set.add(new M(9));
        System.out.println(set);
    }

}

class M {
    int age;

    public M(int age) {
        this.age = age;
    }
}

class MyCommpare implements Comparator {
    @Override
    public int compare(Object o1, Object o2) {
        M m1 = (M) o1;
        M m2 = (M) o2;
        return m1.age > m2.age ? 1 : m1.age < m2.age ? -1 : 0;
    }
}

class Err implements Comparable {
    @Override
    public int compareTo(Object o) {
        return 0;
    }

TreeSet是依靠TreeMap來實現的。
TreeSet是一個有序集合,TreeSet中元素將按照升序排列,缺省是按照自然順序進行排列,意味着TreeSet中元素要實現Comparable接口。
我們可以在構造TreeSet對象時,傳遞實現了Comparator接口的比較器對象。

Comparable和Comparator
Comparable 接口以提供自然排序順序。
對於那些沒有自然順序的類、或者當您想要一個不同於自然順序的順序時,您可以實現Comparator 接口來定義您自己的排序函數。可以將Comparator傳遞給Collections.sort或Arrays.sort。

Comparator接口
當一個類並未實現Comparable,或者不喜歡缺省的Comaparable行為。可以實現Comparator接口
直接實現Comparator的compare接口完成自定義比較類。
例:Arrays.sort(results, new Comparator<RepDataQueryResultVO>() 數組排序 RepDataQueryExecutor
例:Collections.sort(lst,new Comparator<TaskPrintSchemeVO>()

EnumSet特征
EnumSet顧名思義就是專為枚舉類型設計的集合,因此集合元素必須是枚舉類型,否則會拋出異常。 EnumSet集合也是有序的,其順序就是Enum類內元素定義的順序。 EnumSet存取的速度非常快,批量操作的速度也很快。EnumSet主要提供以下方法,allOf, complementOf, copyOf, noneOf, of, range等。注意到EnumSet並沒有提供任何構造函數,要創建一個EnumSet集合對象,只需要調用allOf等方法,下面是一個EnumSet的例子。

import java.util.EnumSet;

/** * ClassName: EnumSets * Description: * date: 2019-11-21 09:59 * * @author chengluchao * @since JDK 1.8 */
public class EnumSets {
    public static void main(String[] args) {
        EnumSet es1 = EnumSet.allOf(Season.class);
        System.out.println(es1);
        EnumSet es2 = EnumSet.noneOf(Season.class);
        es2.add(Season.WINTER);
        es2.add(Season.SUMER);
        System.out.println(es2);
        EnumSet es3 = EnumSet.of(Season.WINTER, Season.SUMER);
        System.out.println(es3);
        EnumSet es4 = EnumSet.range(Season.SUMER,Season.WINTER );
        System.out.println(es4);
        EnumSet es5 = EnumSet.complementOf(es4);
        System.out.println(es5);
    }
}

enum Season {
    SPRING, SUMER, FALL, WINTER
}

執行結果

[SPRING, SUMER, FALL, WINTER]
[SUMER, WINTER]
[SUMER, WINTER]
[SUMER, FALL, WINTER]
[SPRING]

幾種Set的比較:
HashSet外部無序地遍歷成員。
成員可為任意Object子類的對象,但如果覆蓋了equals方法,同時注意修改hashCode方法。
TreeSet外部有序地遍歷成員;
附加實現了SortedSet, 支持子集等要求順序的操作
成員要求實現Comparable接口,或者使用Comparator構造TreeSet。成員一般為同一類型。
LinkedHashSet外部按成員的插入順序遍歷成員
成員與HashSet成員類似
HashSet是基於Hash算法實現的,其性能通常都優於TreeSet。我們通常都應該使用HashSet,在我們需要排序的功能時,我們才使用TreeSet。

HashSet的元素存放順序和我們添加進去時候的順序沒有任何關系,而LinkedHashSet 則保持元素的添加順序。TreeSet則是對我們的Set中的元素進行排序存放。

一般來說,當您要從集合中以有序的方式抽取元素時,TreeSet實現就會有用處。為了能順利進行,添加到 TreeSet 的元素必須是可排序的。 而您同樣需要對添加到TreeSet中的類對象實現 Comparable 接口的支持。一般說來,先把元素添加到 HashSet,再把集合轉換為 TreeSet 來進行有序遍歷會更快。

各種Set集合性能分析

  • HashSet和TreeSet是Set集合中用得最多的I集合。HashSet總是比TreeSet集合性能好,因為HashSet不需要額維護元素的順序。
  • LinkedHashSet需要用額外的鏈表維護元素的插入順序,因此在插入時性能比HashSet低,但在迭代訪問(遍歷)時性能更高。因為插入的時候即要計算hashCode又要維護鏈表,而遍歷的時候只需要按鏈表來訪問元素。
  • EnumSet元素是所有Set元素中性能最好的,但是它只能保存Enum類型的元素

Map

集合框架的第二類接口樹。
它提供了一組鍵值的映射。其中存儲的每個對象都有一個相應的關鍵字(key),關鍵字決定了對象在Map中的存儲位置。
關鍵字應該是唯一的,每個key 只能映射一個value。

實現類:
HashMap、TreeMap、LinkedHashMap、Hashtable等

  • HashMap:鍵值對,key不能重復,但是value可以重復;key的實現就是HashSet;value對應着放;允許null的鍵或值;
  • Hashtable:線程安全的,不允許null的鍵或值;
  • Properties::key和value都是String類型,用來讀配置文件;
  • TreeMap:對key排好序的Map; key 就是TreeSet, value對應每個key; key要實現Comparable接口或TreeMap有自己的構造器;
  • LinkedHashMap: 此實現與HashMap的不同之處在於,后者維護着一個運行於所有條目的雙重鏈接列表。存儲的數據是有序的。
HashMap:
  • Map 主要用於存儲鍵(key)值(value)對,根據鍵得到值,因此鍵不允許重復,但允許值重復。
  • HashMap 是一個最常用的Map,它根據鍵的HashCode值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度。
  • HashMap最多只允許一條記錄的鍵為Null;允許多條記錄的值為 Null;
  • HashMap不支持線程的同步,即任一時刻可以有多個線程同時寫HashMap;可能會導致數據的不一致。如果需要同步,可以用 Collections的synchronizedMap方法使HashMap具有同步的能力。
HashMap實現原理---散列

Hash哈希算法的意義在於提供了一種快速存取數據的方法,它用一種算法建立鍵值與真實值之間的對應關系。散列表又稱為哈希表。散列表算法的基本思想是:以結點的關鍵字為自變量,通過一定的函數關系(散列函數)計算出對應的函數值,以這個值作為該結點存儲在散列表中地址。
當散列表中的元素存放太滿,就必須進行再散列,將產生一個新的散列表,所有元素存放到新的散列表中,原先的散列表將被刪除。在Java語言中,通過負載因子(load factor)來決定何時對散列表進行再散列。例如:如果負載因子0.75,當散列表中已經有75%位置已經放滿,那么將進行再散列。
負載因子越高(越接近1.0),內存的使用效率越高,元素的尋找時間越長。負載因子越低(越接近0.0),元素的尋找時間越短,內存浪費越多。

何時需重寫equals?

當一個類有自己特有的“邏輯相等”概念(不同於對象身份的概念);
Object類僅僅提供了一個對引用的比較,如果兩個引用不是同一個那就返回false,這是無法滿足大多數對象比較的需要的,所以要覆蓋;
使用==操作符檢查實參是否為指向對象的引用”
使用instanceof操作符檢查實參是否為正確的類型
把實參轉換到正確的類型;
對於該類中每一個“關鍵”域,檢查實參中的域與當前對象中對應的域值是否匹配。對於既不是float也不是double類型的基本類型的域,可以使用==操作符進行比較;對於對象引用類型的域,可以遞歸地調用所引用的對象的equals方法,對於float和double類型的域,先轉換成int或long類型的值,然后使用==操作符比較;
當你編寫完成了equals方法之后,應該問自己三個問題:它是否是對稱的、傳 遞的、一致的? 如果答案是否定的,那么請找到 這些特性未能滿足的原因,再修改equals方法的代碼

equals()和hashCode()同時覆寫

尤其強調當一個對象被當作鍵值(或索引)來使用的時候要重寫這兩個方法;
覆寫equals后,兩個不同實例可能在邏輯上相等,但是根據Object.hashCode方法卻產生不同的散列碼,違反“相等的對象必須具有相等的散列碼”。
導致,當你用其中的一個作為鍵保存到hashMap、hasoTable或hashSet中,再以“相等的”找另 一個作為鍵值去查找他們的時候,則根本找不到

不同類型的hashCode取值

  • 如果該域是布爾型的,計算(f?0:1)
  • 如果是char,short,byte或int,計算(int)f
  • 如果是long類型,計算(int)(f^(f>>>32))
  • 如果是float類型,計算Float.floatToIntBits(f)
  • 如果是double類型,計算Dobule.doubleToLongBits(f)
  • 如果該域是一個對象引用,遞歸調用hashCode
  • 如果該域是一個數組,則把每個元素當做單獨的域來處理,對每個重要的元素計算一個散列碼

Map集合比較:

  • HashMap的存入順序和輸出順序無關。
  • LinkedHashMap 則保留了鍵值對的存入順序。
  • TreeMap則是對Map中的元素進行排序。
  • 因為HashMap和LinkedHashMap 存儲數據的速度比直接使用TreeMap 要快,存取效率要高。
  • 當完成了所有的元素的存放后,我們再對整個的Map中的元素進行排序。這樣可以提高整個程序的運行的效率,縮短執行時間。

注意:TreeMap中是根據鍵(Key)進行排序的。而如果我們要使用TreeMap來進行正常的排序的話,Key 中存放的對象必須實現Comparable 接口。

Map常用方法:

  • Object put(Object key,Object value):用來存放一個鍵-值對Map中
  • Object remove(Object key):根據key(鍵),移除鍵-值對,並將值返回
  • void putAll(Map mapping) :將另外一個Map中的元素存入當前的Map中
  • void clear() :清空當前Map中的元素
  • Object get(Object key) :根據key(鍵)取得對應的值
  • boolean containsKey(Object key) :判斷Map中是否存在某鍵(key)
  • boolean containsValue(Object value):判斷Map中是否存在某值(value)
  • public Set keySet() :返回所有的鍵(key),並使用Set容器存放
  • public Collection values() :返回所有的值(Value),並使用Collection存放
  • public Set entrySet() :返回一個實現 Map.Entry 接口的元素 Set

集合遍歷

  1. 增強for循環 for(Obj o:c){syso(o)}
  2. 使用iterator , Iterator it=c.iterator;
    while(it.hasNext()){Object o = it.next()}
  3. 普通循環:for(Iterator it=c.iterator();it.hasNext();){it.next() }

總結

 

 

  1. ArrayList: 元素單個,效率高,多用於查詢
  2. Vector: 元素單個,線程安全,多用於查詢
  3. LinkedList:元素單個,多用於插入和刪除
  4. HashMap: 元素成對,元素可為空
  5. shTable: 元素成對,線程安全,元素不可為空
HashMap和Hashtable的區別:

HashMap和Hashtable都是java的集合類,都可以用來存放java對象,這是他們的相同點
以下是他們的區別:

  1. 歷史原因:
    Hashtable是基於陳舊的Dictionary類的,HashMap是java 1.2引進的Map接口的一個現實。
  2. 同步性:
    hashtable是同步的,這個類中的一些方法保證了Hashtable中的對象是線程安全的,而HashMap則是異步的,因此HashMap中的對象並不是線程安全的,因為同步的要求會影響執行的效率,所以如果你不需要線程安全的結合那么使用HashMap是一個很好的選擇,這樣可以避免由於同步帶來的不必要的性能開銷,從而提高效率,我們一般所編寫的程序都是異步的,但如果是服務器端的代碼除外。
  3. 值:
    HashMap可以讓你將空值作為一個表的條目的key或value
    Hashtable是不能放入空值(null)的

ArrayList和Vector的區別
ArrayList與Vector都是java的集合類,都是用來存放java對象,這是他們的相同點,
區別:

  1. 同步性: Vector是同步的,這個類的一些方法保證了Vector中的對象的線程安全的,而ArrayList則是異步的,因此ArrayList中的對象並不 是線程安全的,因為同步要求會影響執行的效率,所以你不需要線程安全的集合那么使用ArrayList是一個很好的選擇,這樣可以避免由於同步帶來的不必 要的性能開銷。
  2. 數據增長: 從內部實現的機制來講,ArrayList和Vector都是使用數組(Array)來控制集合中的對象,當你向兩種類型中增加元素的時候,如果元素的數目超過了內部數組目前的長度他們都需要擴展內部數組的長度,Vector缺省情況下自動增長原來一倍的數組長度,ArrayList是原來的50%,所以最后你獲得的這個集合所占的空間總是比你實際需要的要大,所以如果你要在集合中保存大量的數據,那么使用Vector有一些優勢,因為你可以通過設置集合的初始大小來避免不必要的資源開銷。

總結:

  1. 如果要求線程安全,使用Vector,Hashtable
  2. 如果不要求線程安全,使用ArrayList,LinkedList,HashMap
  3. 如果要求鍵值對,則使用HashMap,Hashtable
  4. 如果數據量很大,又要求線程安全考慮Vector

arraylist和linkedlist聯系與區別

  1. ArrayList是實現了基於動態數組的數據結構,LinkedList基於鏈表的數據結構。
  2. 對於隨機訪問get和set,ArrayList覺得優於LinkedList,因為LinkedList要移動指針。
  3. 對於新增和刪除操作add和remove,LinedList比較占優勢,因為ArrayList要移動數據。 這一點要看實際情況的。若只對單條數據插入或刪除,ArrayList的速度反而優於LinkedList。但若是批量隨機的插入刪除數據,LinkedList的速度大大優於ArrayList. 因為ArrayList每插入一條數據,要移動插入點及之后的所有數據。

HashMap與TreeMap聯系與區別

  1. HashMap通過hashcode對其內容進行快速查找,而TreeMap中所有的元素都保持着某種固定的順序,如果你需要得到一個有序的結果你就應該使用TreeMap(HashMap中元素的排列順序是不固定的)。
  2. 在Map 中插入、刪除和定位元素,HashMap是最好的選擇。但如果您要按自然順序或自定義順序遍歷鍵,那么TreeMap會更好。使用HashMap要求添加的鍵類明確定義了hashCode()和 equals()的實現。

兩個map中的元素一樣,但順序不一樣,導致hashCode()不一樣。
同樣做測試:
在HashMap中,同樣的值的map,順序不同,equals時,false;
而在treeMap中,同樣的值的map,順序不同,equals時,true,說明,treeMap在equals()時是整理了順序了的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM