整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          來篇Java集合框架綜述,助你吃透它(建議收藏常看)!

          者:平凡希

          鏈接:cnblogs.com/xiaoxi/p/6089984.html

          一、集合框架圖

          簡化圖:

          說明:對于以上的框架圖有如下幾點說明

          1、所有集合類都位于java.util包下。Java的集合類主要由兩個接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,這兩個接口又包含了一些子接口或實現類。

          2、集合接口:6個接口(短虛線表示),表示不同集合類型,是集合框架的基礎。

          3、抽象類:5個抽象類(長虛線表示),對集合接口的部分實現。可擴展為自定義集合類。

          4、實現類:8個實現類(實線表示),對接口的具體實現。

          5、Collection 接口是一組允許重復的對象。

          6、Set 接口繼承 Collection,集合元素不重復。

          7、List 接口繼承 Collection,允許重復,維護元素插入順序。

          8、Map接口是鍵-值對象,與Collection接口沒有什么關系。

          9、Set、List和Map可以看做集合的三大類:

          • List集合是有序集合,集合中的元素可以重復,訪問集合中的元素可以根據元素的索引來訪問。
          • Set集合是無序集合,集合中的元素不可以重復,訪問集合中的元素只能根據元素本身來訪問(也是集合里元素不允許重復的原因)。
          • Map集合中保存Key-value對形式的元素,訪問時只能根據每項元素的key來訪問其value。

          二、總體分析

          大致說明:

          看上面的框架圖,先抓住它的主干,即Collection和Map。

          1、Collection是一個接口,是高度抽象出來的集合,它包含了集合的基本操作和屬性。Collection包含了List和Set兩大分支。

          • List是一個有序的隊列,每一個元素都有它的索引。第一個元素的索引值是0。List的實現類有LinkedList, ArrayList, Vector, Stack。
          • Set是一個不允許有重復元素的集合。Set的實現類有HastSet和TreeSet。HashSet依賴于HashMap,它實際上是通過HashMap實現的;TreeSet依賴于TreeMap,它實際上是通過TreeMap實現的。

          2、Map是一個映射接口,即key-value鍵值對。Map中的每一個元素包含“一個key”和“key對應的value”。AbstractMap是個抽象類,它實現了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是繼承于AbstractMap。Hashtable雖然繼承于Dictionary,但它實現了Map接口。

          3、接下來,再看Iterator。它是遍歷集合的工具,即我們通常通過Iterator迭代器來遍歷集合。我們說Collection依賴于Iterator,是因為Collection的實現類都要實現iterator()函數,返回一個Iterator對象。ListIterator是專門為遍歷List而存在的。

          4、再看Enumeration,它是JDK 1.0引入的抽象類。作用和Iterator一樣,也是遍歷集合;但是Enumeration的功能要比Iterator少。在上面的框圖中,Enumeration只能在Hashtable, Vector, Stack中使用。

          5、最后,看Arrays和Collections。它們是操作數組、集合的兩個工具類。

          有了上面的整體框架之后,我們接下來對每個類分別進行分析。


          三、Collection接口

          Collection接口是處理對象集合的根接口,其中定義了很多對元素進行操作的方法。Collection接口有兩個主要的子接口List和Set,注意Map不是Collection的子接口,這個要牢記。

          Collection接口中的方法如下:

          其中,有幾個比較常用的方法,比如方法add()添加一個元素到集合中,addAll()將指定集合中的所有元素添加到集合中,contains()方法檢測集合中是否包含指定的元素,toArray()方法返回一個表示集合的數組。

          另外,Collection中有一個iterator()函數,它的作用是返回一個Iterator接口。通常,我們通過Iterator迭代器來遍歷集合。ListIterator是List接口所特有的,在List接口中,通過ListIterator()返回一個ListIterator對象。

          Collection接口有兩個常用的子接口,下面詳細介紹。

          1.List接口

          List集合代表一個有序集合,集合中每個元素都有其對應的順序索引。List集合允許使用重復元素,可以通過索引來訪問指定位置的集合元素。

          List接口繼承于Collection接口,它可以定義一個允許重復的有序集合。因為List中的元素是有序的,所以我們可以通過使用索引(元素在List中的位置,類似于數組下標)來訪問List中的元素,這類似于Java的數組。

          List接口為Collection直接接口。List所代表的是有序的Collection,即它用某種特定的插入順序來維護元素順序。用戶可以對列表中每個元素的插入位置進行精確地控制,同時可以根據元素的整數索引(在列表中的位置)訪問元素,并搜索列表中的元素。實現List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

          (1)ArrayList

          ArrayList是一個動態數組,也是我們最常用的集合。它允許任何符合規則的元素插入甚至包括null。每一個ArrayList都有一個初始容量(10),該容量代表了數組的大小。隨著容器中的元素不斷增加,容器的大小也會隨著增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操作。所以如果我們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操作而浪費時間、效率。

          size、isEmpty、get、set、iterator和 listIterator 操作都以固定時間運行。add 操作以分攤的固定時間運行,也就是說,添加 n 個元素需要 O(n) 時間(由于要考慮到擴容,所以這不只是添加元素會帶來分攤固定時間開銷那樣簡單)。

          ArrayList擅長于隨機訪問。同時ArrayList是非同步的。

          (2)LinkedList

          同樣實現List接口的LinkedList與ArrayList不同,ArrayList是一個動態數組,而LinkedList是一個雙向鏈表。所以它除了有ArrayList的基本操作方法外還額外提供了get,remove,insert方法在LinkedList的首部或尾部。

          由于實現的方式不同,LinkedList不能隨機訪問,它所有的操作都是要按照雙重鏈表的需要執行。在列表中索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價在List中進行插入和刪除操作。

          與ArrayList一樣,LinkedList也是非同步的。如果多個線程同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在創建List時構造一個同步的List:

          List list = Collections.synchronizedList(new LinkedList(...));

          (3)Vector

          與ArrayList相似,但是Vector是同步的。所以說Vector是線程安全的動態數組。它的操作與ArrayList幾乎一樣。

          (4)Stack

          Stack繼承自Vector,實現一個后進先出的堆棧。Stack提供5個額外的方法使得Vector得以被當作堆棧使用。基本的push和pop 方法,還有peek方法得到棧頂的元素,empty方法測試堆棧是否為空,search方法檢測一個元素在堆棧中的位置。Stack剛創建后是空棧。

          2.Set接口

          Set是一種不包括重復元素的Collection。它維持它自己的內部排序,所以隨機訪問沒有任何意義。與List一樣,它同樣允許null的存在但是僅有一個。由于Set接口的特殊性,所有傳入Set集合中的元素都必須不同,同時要注意任何可變對象,如果在對集合中元素進行操作時,導致e1.equals(e2)==true,則必定會產生某些問題。Set接口有三個具體實現類,分別是散列集HashSet、鏈式散列集LinkedHashSet和樹形集TreeSet。

          Set是一種不包含重復的元素的Collection,無序,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個null元素。

          需要注意的是:雖然Set中元素沒有順序,但是元素在set中的位置是由該元素的HashCode決定的,其具體位置其實是固定的。

          此外需要說明一點,在set接口中的不重復是有特殊要求的。

          舉一個例子:對象A和對象B,本來是不同的兩個對象,正常情況下它們是能夠放入到Set里面的,但是如果對象A和B的都重寫了hashcode和equals方法,并且重寫后的hashcode和equals方法是相同的話。那么A和B是不能同時放入到Set集合中去的,也就是Set集合中的去重和hashcode與equals方法直接相關。

          為了更好地理解,請看下面的例子:

          public class Test{ 
          public static void main(String[] args) { 
               Set<String> set=new HashSet<String>(); 
               set.add("Hello"); 
               set.add("world"); 
               set.add("Hello"); 
               System.out.println("集合的尺寸為:"+set.size()); 
               System.out.println("集合中的元素為:"+set.toString()); 
            } 
          }

          運行結果:

          集合的尺寸為:2集合中的元素為:[world, Hello]

          分析:由于String類中重寫了hashcode和equals方法,用來比較指向的字符串對象所存儲的字符串是否相等。所以這里的第二個Hello是加不進去的。

          再看一個例子:

          public class TestSet {
          
              public static void main(String[] args){
          
                  Set<String> books = new HashSet<String>();
                  //添加一個字符串對象
                  books.add(new String("Struts2權威指南"));
          
                  //再次添加一個字符串對象,
                  //因為兩個字符串對象通過equals方法比較相等,所以添加失敗,返回false
                  boolean result = books.add(new String("Struts2權威指南"));
          
                  System.out.println(result);
          
                  //下面輸出看到集合只有一個元素
                  System.out.println(books);    
          
              }
          }

          運行結果:

          false[Struts2權威指南]

          說明:程序中,book集合兩次添加的字符串對象明顯不是一個對象(程序通過new關鍵字來創建字符串對象),當使用==運算符判斷返回false,使用equals方法比較返回true,所以不能添加到Set集合中,最后只能輸出一個元素。

          (1)HashSet

          HashSet 是一個沒有重復元素的集合。它是由HashMap實現的,不保證元素的順序(這里所說的沒有順序是指:元素插入的順序與輸出的順序不一致),而且HashSet允許使用null 元素。HashSet是非同步的,如果多個線程同時訪問一個哈希set,而其中至少一個線程修改了該set,那么它必須保持外部同步。 HashSet按Hash算法來存儲集合的元素,因此具有很好的存取和查找性能。

          HashSet的實現方式大致如下,通過一個HashMap存儲元素,元素是存放在HashMap的Key中,而Value統一使用一個Object對象。

          HashSet使用和理解中容易出現的誤區:

          a.HashSet中存放null值。HashSet中是允許存入null值的,但是在HashSet中僅僅能夠存入一個null值。

          b.HashSet中存儲元素的位置是固定的。HashSet中存儲的元素的是無序的,這個沒什么好說的,但是由于HashSet底層是基于Hash算法實現的,使用了hashcode,所以HashSet中相應的元素的位置是固定的。

          c.必須小心操作可變對象(Mutable Object)。如果一個Set中的可變元素改變了自身狀態導致Object.equals(Object)=true將導致一些問題。

          (2)LinkedHashSet

          LinkedHashSet繼承自HashSet,其底層是基于LinkedHashMap來實現的,有序,非同步。LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。

          (3)TreeSet

          TreeSet是一個有序集合,其底層是基于TreeMap實現的,非線程安全。TreeSet可以確保集合元素處于排序狀態。TreeSet支持兩種排序方式,自然排序和定制排序,其中自然排序為默認的排序方式。當我們構造TreeSet時,若使用不帶參數的構造函數,則TreeSet的使用自然比較器;若用戶需要使用自定義的比較器,則需要使用帶比較器的參數。

          注意:TreeSet集合不是通過hashcode和equals函數來比較元素的.它是通過compare或者comparaeTo函數來判斷元素是否相等.compare函數通過判斷兩個對象的id,相同的id判斷為重復元素,不會被加入到集合中。


          四、Map接口

          Map與List、Set接口不同,它是由一系列鍵值對組成的集合,提供了key到Value的映射。同時它也沒有繼承Collection。在Map中它保證了key與value之間的一一對應關系。也就是說一個key對應一個value,所以它不能存在相同的key值,當然value值可以相同

          1.HashMap

          以哈希表數據結構實現,查找對象時通過哈希函數計算其位置,它是為快速查詢而設計的,其內部定義了一個hash表數組(Entry[] table),元素會通過哈希轉換函數將元素的哈希地址轉換成數組中存放的索引,如果有沖突,則使用散列鏈表的形式將所有相同哈希地址的元素串起來,可能通過查看HashMap.Entry的源碼它是一個單鏈表結構。

          2.LinkedHashMap

          LinkedHashMap是HashMap的一個子類,它保留插入的順序,如果需要輸出的順序和輸入時的相同,那么就選用LinkedHashMap。

          LinkedHashMap是Map接口的哈希表和鏈接列表實現,具有可預知的迭代順序。此實現提供所有可選的映射操作,并允許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。

          LinkedHashMap實現與HashMap的不同之處在于,后者維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。

          根據鏈表中元素的順序可以分為:按插入順序的鏈表,和按訪問順序(調用get方法)的鏈表。默認是按插入順序排序,如果指定按訪問順序排序,那么調用get方法后,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。

          注意,此實現不是同步的。如果多個線程同時訪問鏈接的哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須保持外部同步。由于LinkedHashMap需要維護元素的插入順序,因此性能略低于HashMap的性能,但在迭代訪問Map里的全部元素時將有很好的性能,因為它以鏈表來維護內部順序。

          3.TreeMap

          TreeMap 是一個有序的key-value集合,非同步,基于紅黑樹(Red-Black tree)實現,每一個key-value節點作為紅黑樹的一個節點。TreeMap存儲時會進行排序的,會根據key來對key-value鍵值對進行排序,其中排序方式也是分為兩種,一種是自然排序,一種是定制排序,具體取決于使用的構造方法。

          自然排序:TreeMap中所有的key必須實現Comparable接口,并且所有的key都應該是同一個類的對象,否則會報ClassCastException異常。

          定制排序:定義TreeMap時,創建一個comparator對象,該對象對所有的treeMap中所有的key值進行排序,采用定制排序的時候不需要TreeMap中所有的key必須實現Comparable接口。

          TreeMap判斷兩個元素相等的標準:兩個key通過compareTo()方法返回0,則認為這兩個key相等。

          如果使用自定義的類來作為TreeMap中的key值,且想讓TreeMap能夠良好的工作,則必須重寫自定義類中的equals()方法,TreeMap中判斷相等的標準是:兩個key通過equals()方法返回為true,并且通過compareTo()方法比較應該返回為0。


          五、Iterator 與 ListIterator詳解

          1.Iterator

          Iterator的定義如下:

          public interface Iterator<E> {}

          Iterator是一個接口,它是集合的迭代器。集合可以通過Iterator去遍歷集合中的元素。

          Iterator提供的API接口如下:

          • boolean hasNext():判斷集合里是否存在下一個元素。如果有,hasNext()方法返回 true。
          • Object next():返回集合里下一個元素。
          • void remove():刪除集合里上一次next方法返回的元素。

          使用示例:

          public class IteratorExample {
              public static void main(String[] args) {
                  ArrayList<String> a = new ArrayList<String>();
                  a.add("aaa");
                  a.add("bbb");
                  a.add("ccc");
                  System.out.println("Before iterate : " + a);
                  Iterator<String> it = a.iterator();
                  while (it.hasNext()) {
                      String t = it.next();
                      if ("bbb".equals(t)) {
                          it.remove();
                      }
                  }
                  System.out.println("After iterate : " + a);
              }
          }

          輸出結果如下:

          Before iterate : [aaa, bbb, ccc]After iterate : [aaa, ccc]

          注意:

          • Iterator只能單向移動。
          • Iterator.remove()是唯一安全的方式來在迭代過程中修改集合;如果在迭代過程中以任何其它的方式修改了基本集合將會產生未知的行為。而且每調用一次next()方法,remove()方法只能被調用一次,如果違反這個規則將拋出一個異常。

          2.ListIterator

          ListIterator是一個功能更加強大的迭代器, 它繼承于Iterator接口,只能用于各種List類型的訪問。可以通過調用listIterator()方法產生一個指向List開始處的ListIterator, 還可以調用listIterator(n)方法創建一個一開始就指向列表索引為n的元素處的ListIterator。

          ListIterator接口定義如下:

          public interface ListIterator<E> extends Iterator<E> {
              boolean hasNext();
          
              E next();
          
              boolean hasPrevious();
          
              E previous();
          
              int nextIndex();
          
              int previousIndex();
          
              void remove();
          
              void set(E e);
          
              void add(E e);
          
          }

          由以上定義我們可以推出ListIterator可以:

          • 雙向移動(向前/向后遍歷).
          • 產生相對于迭代器在列表中指向的當前位置的前一個和后一個元素的索引.
          • 可以使用set()方法替換它訪問過的最后一個元素.
          • 可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一個元素.

          使用示例:

          public class ListIteratorExample {
          
              public static void main(String[] args) {
                  ArrayList<String> a = new ArrayList<String>();
                  a.add("aaa");
                  a.add("bbb");
                  a.add("ccc");
                  System.out.println("Before iterate : " + a);
                  ListIterator<String> it = a.listIterator();
                  while (it.hasNext()) {
                      System.out.println(it.next() + ", " + it.previousIndex() + ", " + it.nextIndex());
                  }
                  while (it.hasPrevious()) {
                      System.out.print(it.previous() + " ");
                  }
                  System.out.println();
                  it = a.listIterator(1);
                  while (it.hasNext()) {
                      String t = it.next();
                      System.out.println(t);
                      if ("ccc".equals(t)) {
                          it.set("nnn");
                      } else {
                          it.add("kkk");
                      }
                  }
                  System.out.println("After iterate : " + a);
              }
          }

          輸出結果如下:

          Before iterate : [aaa, bbb, ccc]
          aaa, 0, 1
          bbb, 1, 2
          ccc, 2, 3
          ccc bbb aaa 
          bbb
          ccc
          After iterate : [aaa, bbb, kkk, nnn]

          六、異同點

          1.ArrayList和LinkedList

          • ArrayList是實現了基于動態數組的數據結構,LinkedList基于鏈表的數據結構。
          • 對于隨機訪問get和set,ArrayList絕對優于LinkedList,因為LinkedList要移動指針。
          • 對于新增和刪除操作add和remove,LinedList比較占優勢,因為ArrayList要移動數據。

          這一點要看實際情況的。若只對單條數據插入或刪除,ArrayList的速度反而優于LinkedList。但若是批量隨機的插入刪除數據,LinkedList的速度大大優于ArrayList. 因為ArrayList每插入一條數據,要移動插入點及之后的所有數據。

          2.HashTable與HashMap

          相同點:

          • 都實現了Map、Cloneable、java.io.Serializable接口。
          • 都是存儲"鍵值對(key-value)"的散列表,而且都是采用拉鏈法實現的。

          不同點:

          (1)歷史原因:HashTable是基于陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現 。

          (2)同步性:HashTable是線程安全的,也就是說是同步的,而HashMap是線程序不安全的,不是同步的 。

          (3)對null值的處理:HashMap的key、value都可為null,HashTable的key、value都不可為null 。

          (4)基類不同:HashMap繼承于AbstractMap,而Hashtable繼承于Dictionary。

          • Dictionary是一個抽象類,它直接繼承于Object類,沒有實現任何接口。Dictionary類是JDK 1.0的引入的。雖然Dictionary也支持“添加key-value鍵值對”、“獲取value”、“獲取大小”等基本操作,但它的API函數比Map少;而且Dictionary一般是通過Enumeration(枚舉類)去遍歷,Map則是通過Iterator(迭代M器)去遍歷。 然而由于Hashtable也實現了Map接口,所以,它即支持Enumeration遍歷,也支持Iterator遍歷。
          • AbstractMap是一個抽象類,它實現了Map接口的絕大部分API函數;為Map的具體實現類提供了極大的便利。它是JDK 1.2新增的類。

          (5)支持的遍歷種類不同:HashMap只支持Iterator(迭代器)遍歷。而Hashtable支持Iterator(迭代器)和Enumeration(枚舉器)兩種方式遍歷。

          3.HashMap、Hashtable、LinkedHashMap和TreeMap比較

          Hashmap 是一個最常用的Map,它根據鍵的HashCode 值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度。遍歷時,取得數據的順序是完全隨機的。HashMap最多只允許一條記錄的鍵為Null;允許多條記錄的值為Null;HashMap不支持線程的同步,即任一時刻可以有多個線程同時寫HashMap;可能會導致數據的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。

          Hashtable 與 HashMap類似,不同的是:它不允許記錄的鍵或者值為空;它支持線程的同步,即任一時刻只有一個線程能寫Hashtable,因此也導致了Hashtale在寫入時會比較慢。

          LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,也可以在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比LinkedHashMap慢,因為LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。

          如果需要輸出的順序和輸入的相同,那么用LinkedHashMap可以實現,它還可以按讀取順序來排列,像連接池中可以應用。LinkedHashMap實現與HashMap的不同之處在于,后者維護著一個運行于所有條目的雙重鏈表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。對于LinkedHashMap而言,它繼承與HashMap、底層使用哈希表與雙向鏈表來保存所有元素。其基本操作與父類HashMap相似,它通過重寫父類相關的方法,來實現自己的鏈接列表特性。

          TreeMap實現SortMap接口,內部實現是紅黑樹。能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。TreeMap不允許key的值為null。非同步的。

          一般情況下,我們用的最多的是HashMap,HashMap里面存入的鍵值對在取出的時候是隨機的,它根據鍵的HashCode值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度。在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。

          TreeMap取出來的是排序后的鍵值對。但如果您要按自然順序或自定義順序遍歷鍵,那么TreeMap會更好。

          LinkedHashMap 是HashMap的一個子類,如果需要輸出的順序和輸入的相同,那么用LinkedHashMap可以實現,它還可以按讀取順序來排列,像連接池中可以應用。

          import java.util.HashMap;
          import java.util.Iterator;
          import java.util.LinkedHashMap;
          import java.util.TreeMap;
          
          public class MapTest {
          
              public static void main(String[] args) {
          
                  //HashMap
                  HashMap<String,String> hashMap = new HashMap();
                  hashMap.put("4", "d");
                  hashMap.put("3", "c");
                  hashMap.put("2", "b");
                  hashMap.put("1", "a");
          
                  Iterator<String> iteratorHashMap = hashMap.keySet().iterator();
          
                  System.out.println("HashMap-->");
          
                  while (iteratorHashMap.hasNext()){
          
                      Object key1 = iteratorHashMap.next();
                      System.out.println(key1 + "--" + hashMap.get(key1));
                  }
          
                  //LinkedHashMap
                  LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap();
                  linkedHashMap.put("4", "d");
                  linkedHashMap.put("3", "c");
                  linkedHashMap.put("2", "b");
                  linkedHashMap.put("1", "a");
          
                  Iterator<String> iteratorLinkedHashMap = linkedHashMap.keySet().iterator();
          
                  System.out.println("LinkedHashMap-->");
          
                  while (iteratorLinkedHashMap.hasNext()){
          
                      Object key2 = iteratorLinkedHashMap.next();
                      System.out.println(key2 + "--" + linkedHashMap.get(key2));
                  }
          
                  //TreeMap
                  TreeMap<String,String> treeMap = new TreeMap();
                  treeMap.put("4", "d");
                  treeMap.put("3", "c");
                  treeMap.put("2", "b");
                  treeMap.put("1", "a");
          
                  Iterator<String> iteratorTreeMap = treeMap.keySet().iterator();
          
                  System.out.println("TreeMap-->");
          
                  while (iteratorTreeMap.hasNext()){
          
                      Object key3 = iteratorTreeMap.next();
                      System.out.println(key3 + "--" + treeMap.get(key3));
                  }
          
              }
          
          }

          輸出結果:

          HashMap-->
          3--c
          2--b
          1--a
          4--d
          LinkedHashMap-->
          4--d
          3--c
          2--b
          1--a
          TreeMap-->
          1--a
          2--b
          3--c
          4--d

          4.HashSet、LinkedHashSet、TreeSet比較

          Set接口

          Set不允許包含相同的元素,如果試圖把兩個相同元素加入同一個集合中,add方法返回false。

          Set判斷兩個對象相同不是使用==運算符,而是根據equals方法。也就是說,只要兩個對象用equals方法比較返回true,Set就不會接受這兩個對象。

          HashSet

          HashSet有以下特點:

          • 不能保證元素的排列順序,順序有可能發生變化。
          • 不是同步的。
          • 集合元素可以是null,但只能放入一個null。

          當向HashSet結合中存入一個元素時,HashSet會調用該對象的hashCode()方法來得到該對象的hashCode值,然后根據 hashCode值來決定該對象在HashSet中存儲位置。簡單的說,HashSet集合判斷兩個元素相等的標準是兩個對象通過equals方法比較相等,并且兩個對象的hashCode()方法返回值也相等。

          注意,如果要把一個對象放入HashSet中,重寫該對象對應類的equals方法,也應該重寫其hashCode()方法。其規則是如果兩個對象通過equals方法比較返回true時,其hashCode也應該相同。另外,對象中用作equals比較標準的屬性,都應該用來計算 hashCode的值。

          LinkedHashSet

          LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。

          LinkedHashSet在迭代訪問Set中的全部元素時,性能比HashSet好,但是插入時性能稍微遜色于HashSet。

          TreeSet類

          TreeSet是SortedSet接口的唯一實現類,TreeSet可以確保集合元素處于排序狀態。TreeSet支持兩種排序方式,自然排序和定制排序,其中自然排序為默認的排序方式。向TreeSet中加入的應該是同一個類的對象。

          TreeSet判斷兩個對象不相等的方式是兩個對象通過equals方法返回false,或者通過CompareTo方法比較沒有返回0。

          自然排序

          自然排序使用要排序元素的CompareTo(Object obj)方法來比較元素之間大小關系,然后將元素按照升序排列。

          Java提供了一個Comparable接口,該接口里定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現了該接口的對象就可以比較大小。obj1.compareTo(obj2)方法如果返回0,則說明被比較的兩個對象相等,如果返回一個正數,則表明obj1大于obj2,如果是負數,則表明obj1小于obj2。如果我們將兩個對象的equals方法總是返回true,則這兩個對象的compareTo方法返回應該返回0。

          定制排序

          自然排序是根據集合元素的大小,以升序排列,如果要定制排序,應該使用Comparator接口,實現 int compare(T o1,T o2)方法。

          package com.test;  
          
          import java.util.HashSet;  
          import java.util.LinkedHashSet;  
          import java.util.TreeSet;  
          
          /**  
           * @description 幾個set的比較  
           *    HashSet:哈希表是通過使用稱為散列法的機制來存儲信息的,元素并沒有以某種特定順序來存放;  
           *    LinkedHashSet:以元素插入的順序來維護集合的鏈接表,允許以插入的順序在集合中迭代;  
           *    TreeSet:提供一個使用樹結構存儲Set接口的實現,對象以升序順序存儲,訪問和遍歷的時間很快。  
           * @author Zhou-Jingxian  
           *  
           */  
          public class SetDemo {  
          
              public static void main(String[] args) {  
          
                  HashSet<String> hs = new HashSet<String>();  
                  hs.add("B");  
                  hs.add("A");  
                  hs.add("D");  
                  hs.add("E");  
                  hs.add("C");  
                  hs.add("F");  
                  System.out.println("HashSet 順序:\n"+hs);  
          
                  LinkedHashSet<String> lhs = new LinkedHashSet<String>();  
                  lhs.add("B");  
                  lhs.add("A");  
                  lhs.add("D");  
                  lhs.add("E");  
                  lhs.add("C");  
                  lhs.add("F");  
                  System.out.println("LinkedHashSet 順序:\n"+lhs);  
          
                  TreeSet<String> ts = new TreeSet<String>();  
                  ts.add("B");  
                  ts.add("A");  
                  ts.add("D");  
                  ts.add("E");  
                  ts.add("C");  
                  ts.add("F");  
                  System.out.println("TreeSet 順序:\n"+ts);  
              }  
          }

          輸出結果:

          HashSet 順序:[D, E, F, A, B, C]
          LinkedHashSet 順序:[B, A, D, E, C, F]
          TreeSet 順序:[A, B, C, D, E, F]

          5、Iterator和ListIterator區別

          我們在使用List,Set的時候,為了實現對其數據的遍歷,我們經常使用到了Iterator(迭代器)。使用迭代器,你不需要干涉其遍歷的過程,只需要每次取出一個你想要的數據進行處理就可以了。但是在使用的時候也是有不同的。

          List和Set都有iterator()來取得其迭代器。對List來說,你也可以通過listIterator()取得其迭代器,兩種迭代器在有些時候是不能通用的,Iterator和ListIterator主要區別在以下方面:

          • ListIterator有add()方法,可以向List中添加對象,而Iterator不能
          • ListIterator和Iterator都有hasNext()和next()方法,可以實現順序向后遍歷,但是ListIterator有hasPrevious()和previous()方法,可以實現逆向(順序向前)遍歷。Iterator就不可以。
          • ListIterator可以定位當前的索引位置,nextIndex()和previousIndex()可以實現。Iterator沒有此功能。
          • 都可實現刪除對象,但是ListIterator可以實現對象的修改,set()方法可以實現。Iierator僅能遍歷,不能修改。

          因為ListIterator的這些功能,可以實現對LinkedList等List數據結構的操作。其實,數組對象也可以用迭代器來實現。

          6、Collection 和 Collections區別

          (1)java.util.Collection 是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操作的通用接口方法。Collection接口在Java 類庫中有很多具體的實現。Collection接口的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承接口有List與Set。

           Collection   
          ├List   
          │├LinkedList   
          │├ArrayList   
          │└Vector   
          │ └Stack   
          └Set 

          (2)java.util.Collections 是一個包裝類(工具類/幫助類)。它包含有各種有關集合操作的靜態多態方法。此類不能實例化,就像一個工具類,用于對集合中元素進行排序、搜索以及線程安全等各種操作,服務于Java的Collection框架。

          代碼示例:

          全的集合講解,很不錯的一篇文章,認真看完,也算是回顧吧!


          作者:平凡希

          來源:cnblogs.com/xiaoxi/p/6089984.html

          一、集合框架圖


          簡化圖:

          說明:對于以上的框架圖有如下幾點說明

          1、所有集合類都位于java.util包下。Java 的集合類主要由兩個接口派生而出:Collection 和 Map,Collection 和 Map 是 Java 集合框架的根接口,這兩個接口又包含了一些子接口或實現類。

          2、集合接口:6 個接口(短虛線表示),表示不同集合類型,是集合框架的基礎。

          3、抽象類:5 個抽象類(長虛線表示),對集合接口的部分實現。可擴展為自定義集合類。

          4、實現類:8 個實現類(實線表示),對接口的具體實現。

          5、Collection 接口是一組允許重復的對象。

          6、Set 接口繼承 Collection,集合元素不重復。

          7、List 接口繼承 Collection,允許重復,維護元素插入順序。

          8、Map 接口是鍵-值對象,與 Collection 接口沒有什么關系。

          9、Set、List 和 Map 可以看做集合的三大類:

          • List 集合是有序集合,集合中的元素可以重復,訪問集合中的元素可以根據元素的索引來訪問。
          • Set 集合是無序集合,集合中的元素不可以重復,訪問集合中的元素只能根據元素本身來訪問(也是集合里元素不允許重復的原因)。
          • Map 集合中保存 Key-value 對形式的元素,訪問時只能根據每項元素的 key 來訪問其 value。

          二、總體分析

          大致說明:

          看上面的框架圖,先抓住它的主干,即 Collection 和 Map。

          1、Collection 是一個接口,是高度抽象出來的集合,它包含了集合的基本操作和屬性。Collection 包含了 List 和 Set 兩大分支。

          • List 是一個有序的隊列,每一個元素都有它的索引。第一個元素的索引值是 0。List 的實現類有 LinkedList, ArrayList, Vector, Stack。
          • Set 是一個不允許有重復元素的集合。Set 的實現類有 HastSet 和 TreeSet。HashSet 依賴于 HashMap,它實際上是通過 HashMap 實現的;TreeSet 依賴于 TreeMap,它實際上是通過 TreeMap 實現的。

          2、Map 是一個映射接口,即 key-value 鍵值對。Map 中的每一個元素包含 “一個 key” 和“key 對應的 value”。AbstractMap 是個抽象類,它實現了 Map 接口中的大部分 API。而 HashMap,TreeMap,WeakHashMap 都是繼承于 AbstractMap。Hashtable 雖然繼承于 Dictionary,但它實現了 Map 接口。

          3、接下來,再看 Iterator。它是遍歷集合的工具,即我們通常通過 Iterator 迭代器來遍歷集合。我們說 Collection 依賴于 Iterator,是因為 Collection 的實現類都要實現 iterator() 函數,返回一個 Iterator 對象。ListIterator 是專門為遍歷 List 而存在的。

          4、再看 Enumeration,它是 JDK 1.0 引入的抽象類。作用和 Iterator 一樣,也是遍歷集合;但是 Enumeration 的功能要比 Iterator 少。在上面的框圖中,Enumeration 只能在 Hashtable, Vector, Stack 中使用。

          5、最后,看 Arrays 和 Collections。它們是操作數組、集合的兩個工具類。

          搜索公縱號:MarkerHub,關注回復[ vue ]獲取前后端入門教程!

          有了上面的整體框架之后,我們接下來對每個類分別進行分析。

          三、Collection 接口

          Collection 接口是處理對象集合的根接口,其中定義了很多對元素進行操作的方法。Collection 接口有兩個主要的子接口 List 和 Set,注意 Map 不是 Collection 的子接口,這個要牢記。

          Collection 接口中的方法如下:

          其中,有幾個比較常用的方法,比如方法 add() 添加一個元素到集合中,addAll() 將指定集合中的所有元素添加到集合中,contains()方法檢測集合中是否包含指定的元素,toArray() 方法返回一個表示集合的數組。

          另外,Collection 中有一個iterator()函數,它的作用是返回一個 Iterator 接口。通常,我們通過 Iterator 迭代器來遍歷集合。ListIterator 是 List 接口所特有的,在 List 接口中,通過ListIterator()返回一個 ListIterator 對象。

          Collection 接口有兩個常用的子接口,下面詳細介紹。

          1.List 接口

          List 集合代表一個有序集合,集合中每個元素都有其對應的順序索引。List 集合允許使用重復元素,可以通過索引來訪問指定位置的集合元素。

          List 接口繼承于 Collection 接口,它可以定義一個允許重復的有序集合。因為 List 中的元素是有序的,所以我們可以通過使用索引(元素在 List 中的位置,類似于數組下標)來訪問 List 中的元素,這類似于 Java 的數組。

          List 接口為 Collection 直接接口。List 所代表的是有序的 Collection,即它用某種特定的插入順序來維護元素順序。用戶可以對列表中每個元素的插入位置進行精確地控制,同時可以根據元素的整數索引(在列表中的位置)訪問元素,并搜索列表中的元素。實現 List 接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

          (1)ArrayList

          ArrayList 是一個動態數組,也是我們最常用的集合。它允許任何符合規則的元素插入甚至包括 null。每一個 ArrayList 都有一個初始容量(10),該容量代表了數組的大小。隨著容器中的元素不斷增加,容器的大小也會隨著增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操作。所以如果我們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操作而浪費時間、效率。

          size、isEmpty、get、set、iterator和 listIterator 操作都以固定時間運行。add 操作以分攤的固定時間運行,也就是說,添加 n 個元素需要 O(n) 時間(由于要考慮到擴容,所以這不只是添加元素會帶來分攤固定時間開銷那樣簡單)。

          ArrayList 擅長于隨機訪問。同時 ArrayList 是非同步的。

          (2)LinkedList

          同樣實現 List 接口的 LinkedList 與 ArrayList 不同,ArrayList 是一個動態數組,而 LinkedList 是一個雙向鏈表。所以它除了有 ArrayList 的基本操作方法外還額外提供了get,remove,insert方法在 LinkedList 的首部或尾部。

          由于實現的方式不同,LinkedList 不能隨機訪問,它所有的操作都是要按照雙重鏈表的需要執行。在列表中索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價在 List 中進行插入和刪除操作。

          與 ArrayList 一樣,LinkedList 也是非同步的。如果多個線程同時訪問一個 List,則必須自己實現訪問同步。一種解決方法是在創建 List 時構造一個同步的 List:

          List list = Collections.synchronizedList(new LinkedList(...));
          
          

          (3)Vector

          與 ArrayList 相似,但是 Vector 是同步的。所以說 Vector 是線程安全的動態數組。它的操作與 ArrayList 幾乎一樣。

          (4)Stack

          Stack 繼承自 Vector,實現一個后進先出的堆棧。Stack 提供 5 個額外的方法使得 Vector 得以被當作堆棧使用。基本的 push 和 pop 方法,還有 peek 方法得到棧頂的元素,empty 方法測試堆棧是否為空,search 方法檢測一個元素在堆棧中的位置。Stack 剛創建后是空棧。

          2.Set 接口

          Set 是一種不包括重復元素的 Collection。它維持它自己的內部排序,所以隨機訪問沒有任何意義。與 List 一樣,它同樣允許 null 的存在但是僅有一個。由于 Set 接口的特殊性,所有傳入 Set 集合中的元素都必須不同,同時要注意任何可變對象,如果在對集合中元素進行操作時,導致e1.equals(e2)==true,則必定會產生某些問題。Set 接口有三個具體實現類,分別是散列集 HashSet、鏈式散列集 LinkedHashSet 和樹形集 TreeSet。

          搜索公縱號:MarkerHub,關注回復[ vue ]獲取前后端入門教程!

          Set 是一種不包含重復的元素的 Collection,無序,即任意的兩個元素 e1 和 e2 都有 e1.equals(e2)=false,Set 最多有一個 null 元素。

          需要注意的是:雖然 Set 中元素沒有順序,但是元素在 set 中的位置是由該元素的 HashCode 決定的,其具體位置其實是固定的。

          此外需要說明一點,在 set 接口中的不重復是有特殊要求的。

          舉一個例子:對象 A 和對象 B,本來是不同的兩個對象,正常情況下它們是能夠放入到 Set 里面的,但是如果對象 A 和 B 的都重寫了 hashcode 和 equals 方法,并且重寫后的 hashcode 和 equals 方法是相同的話。那么 A 和 B 是不能同時放入到 Set 集合中去的,也就是 Set 集合中的去重和 hashcode 與 equals 方法直接相關。

          為了更好地理解,請看下面的例子:

          public class Test{ 
          public static void main(String[] args) { 
               Set<String> set=new HashSet<String>(); 
               set.add("Hello"); 
               set.add("world"); 
               set.add("Hello"); 
               System.out.println("集合的尺寸為:"+set.size()); 
               System.out.println("集合中的元素為:"+set.toString()); 
            } 
          }
          
          

          運行結果:

          集合的尺寸為: 2集合中的元素為:[world, Hello]

          分析:由于 String 類中重寫了 hashcode 和 equals 方法,用來比較指向的字符串對象所存儲的字符串是否相等。所以這里的第二個 Hello 是加不進去的。

          再看一個例子:

          public class TestSet {
          
              public static void main(String[] args){
          
                  Set<String> books = new HashSet<String>();
                  //添加一個字符串對象
                  books.add(new String("Struts2權威指南"));
          
                  //再次添加一個字符串對象,
                  //因為兩個字符串對象通過equals方法比較相等,所以添加失敗,返回false
                  boolean result = books.add(new String("Struts2權威指南"));
          
                  System.out.println(result);
          
                  //下面輸出看到集合只有一個元素
                  System.out.println(books);    
          
              }
          }

          運行結果:

          false[Struts2 權威指南]

          說明:程序中,book 集合兩次添加的字符串對象明顯不是一個對象(程序通過 new 關鍵字來創建字符串對象),當使用 == 運算符判斷返回 false,使用 equals 方法比較返回 true,所以不能添加到 Set 集合中,最后只能輸出一個元素。

          (1)HashSet

          HashSet 是一個沒有重復元素的集合。它是由 HashMap 實現的,不保證元素的順序 (這里所說的沒有順序是指:元素插入的順序與輸出的順序不一致),而且 HashSet 允許使用 null 元素。HashSet 是非同步的,如果多個線程同時訪問一個哈希 set,而其中至少一個線程修改了該 set,那么它必須保持外部同步。 HashSet 按 Hash 算法來存儲集合的元素,因此具有很好的存取和查找性能。

          HashSet 的實現方式大致如下,通過一個 HashMap 存儲元素,元素是存放在 HashMap 的 Key 中,而 Value 統一使用一個 Object 對象。

          HashSet 使用和理解中容易出現的誤區:

          **a.HashSet 中存放 null 值。**HashSet 中是允許存入 null 值的,但是在 HashSet 中僅僅能夠存入一個 null 值。

          **b.HashSet 中存儲元素的位置是固定的。**HashSet 中存儲的元素的是無序的,這個沒什么好說的,但是由于 HashSet 底層是基于 Hash 算法實現的,使用了 hashcode,所以 HashSet 中相應的元素的位置是固定的。

          c. 必須小心操作可變對象(Mutable Object)。如果一個 Set 中的可變元素改變了自身狀態導致Object.equals(Object)=true將導致一些問題。

          (2)LinkedHashSet

          LinkedHashSet 繼承自 HashSet,其底層是基于 LinkedHashMap 來實現的,有序,非同步。LinkedHashSet 集合同樣是根據元素的 hashCode 值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet 將會以元素的添加順序訪問集合的元素。

          (3)TreeSet

          TreeSet 是一個有序集合,其底層是基于 TreeMap 實現的,非線程安全。TreeSet 可以確保集合元素處于排序狀態。**TreeSet 支持兩種排序方式,自然排序和定制排序,其中自然排序為默認的排序方式。**當我們構造 TreeSet 時,若使用不帶參數的構造函數,則 TreeSet 的使用自然比較器;若用戶需要使用自定義的比較器,則需要使用帶比較器的參數。

          注意:TreeSet 集合不是通過 hashcode 和 equals 函數來比較元素的. 它是通過 compare 或者 comparaeTo 函數來判斷元素是否相等. compare 函數通過判斷兩個對象的 id,相同的 id 判斷為重復元素,不會被加入到集合中。

          四、Map 接口

          Map 與 List、Set 接口不同,它是由一系列鍵值對組成的集合,提供了 key 到 Value 的映射。同時它也沒有繼承 Collection。在 Map 中它保證了 key 與 value 之間的一一對應關系。也就是說一個 key 對應一個 value,所以它不能存在相同的 key 值,當然 value 值可以相同

          1.HashMap

          以哈希表數據結構實現,查找對象時通過哈希函數計算其位置,它是為快速查詢而設計的,其內部定義了一個 hash 表數組(Entry[] table),元素會通過哈希轉換函數將元素的哈希地址轉換成數組中存放的索引,如果有沖突,則使用散列鏈表的形式將所有相同哈希地址的元素串起來,可能通過查看 HashMap.Entry 的源碼它是一個單鏈表結構。

          2.LinkedHashMap

          LinkedHashMap 是 HashMap 的一個子類,它保留插入的順序,如果需要輸出的順序和輸入時的相同,那么就選用 LinkedHashMap。

          **LinkedHashMap 是 Map 接口的哈希表和鏈接列表實現,具有可預知的迭代順序。**此實現提供所有可選的映射操作,并允許使用 null 值和 null 鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。

          LinkedHashMap 實現與 HashMap 的不同之處在于,后者維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。

          根據鏈表中元素的順序可以分為:按插入順序的鏈表,和按訪問順序 (調用 get 方法) 的鏈表。默認是按插入順序排序,如果指定按訪問順序排序,那么調用 get 方法后,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。

          注意,此實現不是同步的。如果多個線程同時訪問鏈接的哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須保持外部同步。由于 LinkedHashMap 需要維護元素的插入順序,因此性能略低于 HashMap 的性能,但在迭代訪問 Map 里的全部元素時將有很好的性能,因為它以鏈表來維護內部順序。

          3.TreeMap

          **TreeMap 是一個有序的 key-value 集合,非同步,基于紅黑樹(Red-Black tree)實現,每一個 key-value 節點作為紅黑樹的一個節點。**TreeMap 存儲時會進行排序的,會根據 key 來對 key-value 鍵值對進行排序,其中排序方式也是分為兩種,一種是自然排序,一種是定制排序,具體取決于使用的構造方法。

          **自然排序:**TreeMap 中所有的 key 必須實現 Comparable 接口,并且所有的 key 都應該是同一個類的對象,否則會報 ClassCastException 異常。

          **定制排序:**定義 TreeMap 時,創建一個 comparator 對象,該對象對所有的 treeMap 中所有的 key 值進行排序,采用定制排序的時候不需要 TreeMap 中所有的 key 必須實現 Comparable 接口。

          TreeMap 判斷兩個元素相等的標準:兩個 key 通過compareTo()方法返回 0,則認為這兩個 key 相等。

          如果使用自定義的類來作為 TreeMap 中的 key 值,且想讓 TreeMap 能夠良好的工作,則必須重寫自定義類中的equals()方法,TreeMap 中判斷相等的標準是:兩個 key 通過equals()方法返回為 true,并且通過compareTo()方法比較應該返回為 0。

          五、Iterator 與 ListIterator 詳解

          1.Iterator

          Iterator 的定義如下:

          public interface Iterator<E> {}

          Iterator 是一個接口,它是集合的迭代器。集合可以通過 Iterator 去遍歷集合中的元素。

          Iterator 提供的 API 接口如下:

          • boolean hasNext():判斷集合里是否存在下一個元素。如果有,hasNext() 方法返回 true。
          • Object next():返回集合里下一個元素。
          • void remove():刪除集合里上一次 next 方法返回的元素。

          使用示例:

          public class IteratorExample {
              public static void main(String[] args) {
                  ArrayList<String> a = new ArrayList<String>();
                  a.add("aaa");
                  a.add("bbb");
                  a.add("ccc");
                  System.out.println("Before iterate : " + a);
                  Iterator<String> it = a.iterator();
                  while (it.hasNext()) {
                      String t = it.next();
                      if ("bbb".equals(t)) {
                          it.remove();
                      }
                  }
                  System.out.println("After iterate : " + a);
              }
          }

          輸出結果如下:

          Before iterate : [aaa, bbb, ccc]After iterate : [aaa, ccc]

          注意:

          • Iterator 只能單向移動。
          • Iterator.remove() 是唯一安全的方式來在迭代過程中修改集合;如果在迭代過程中以任何其它的方式修改了基本集合將會產生未知的行為。而且每調用一次next()方法,remove()方法只能被調用一次,如果違反這個規則將拋出一個異常。

          2.ListIterator

          **ListIterator 是一個功能更加強大的迭代器, 它繼承于 Iterator 接口,**只能用于各種 List 類型的訪問。可以通過調用listIterator()方法產生一個指向 List 開始處的 ListIterator, 還可以調用listIterator(n)方法創建一個一開始就指向列表索引為 n 的元素處的 ListIterator。

          ListIterator 接口定義如下:

          public interface ListIterator<E> extends Iterator<E> {
              boolean hasNext();
          
              E next();
          
              boolean hasPrevious();
          
              E previous();
          
              int nextIndex();
          
              int previousIndex();
          
              void remove();
          
              void set(E e);
          
              void add(E e);
          
          }

          由以上定義我們可以推出 ListIterator 可以:

          • 雙向移動(向前 / 向后遍歷).
          • 產生相對于迭代器在列表中指向的當前位置的前一個和后一個元素的索引.
          • 可以使用set()方法替換它訪問過的最后一個元素.
          • 可以使用add()方法在next()方法返回的元素之前或previous()方法返回的元素之后插入一個元素.

          使用示例:

          public class ListIteratorExample {
          
              public static void main(String[] args) {
                  ArrayList<String> a = new ArrayList<String>();
                  a.add("aaa");
                  a.add("bbb");
                  a.add("ccc");
                  System.out.println("Before iterate : " + a);
                  ListIterator<String> it = a.listIterator();
                  while (it.hasNext()) {
                      System.out.println(it.next() + ", " + it.previousIndex() + ", " + it.nextIndex());
                  }
                  while (it.hasPrevious()) {
                      System.out.print(it.previous() + " ");
                  }
                  System.out.println();
                  it = a.listIterator(1);
                  while (it.hasNext()) {
                      String t = it.next();
                      System.out.println(t);
                      if ("ccc".equals(t)) {
                          it.set("nnn");
                      } else {
                          it.add("kkk");
                      }
                  }
                  System.out.println("After iterate : " + a);
              }
          }

          輸出結果如下:

          Before iterate : [aaa, bbb, ccc]
          aaa, 0, 1
          bbb, 1, 2
          ccc, 2, 3
          ccc bbb aaa 
          bbb
          ccc
          After iterate : [aaa, bbb, kkk, nnn]

          六、異同點

          1.ArrayList 和 LinkedList

          • ArrayList 是實現了基于動態數組的數據結構,LinkedList 基于鏈表的數據結構。
          • 對于隨機訪問 get 和 set,ArrayList 絕對優于 LinkedList,因為 LinkedList 要移動指針。
          • 對于新增和刪除操作 add 和 remove,LinedList 比較占優勢,因為 ArrayList 要移動數據。

          這一點要看實際情況的。**若只對單條數據插入或刪除,ArrayList 的速度反而優于 LinkedList。**但若是批量隨機的插入刪除數據,LinkedList 的速度大大優于 ArrayList. 因為 ArrayList 每插入一條數據,要移動插入點及之后的所有數據。

          2.HashTable 與 HashMap

          相同點:

          • 都實現了Map、Cloneable、java.io.Serializable接口。
          • 都是存儲 "鍵值對 (key-value)" 的散列表,而且都是采用拉鏈法實現的。

          不同點:

          **(1)歷史原因:**HashTable 是基于陳舊的 Dictionary 類的,HashMap 是 Java 1.2 引進的 Map 接口的一個實現 。

          **(2)同步性:**HashTable 是線程安全的,也就是說是同步的,而 HashMap 是線程序不安全的,不是同步的 。

          **(3)對 null 值的處理:**HashMap 的 key、value 都可為 null,HashTable 的 key、value 都不可為 null 。

          **(4)基類不同:**HashMap 繼承于 AbstractMap,而 Hashtable 繼承于 Dictionary。

          • Dictionary 是一個抽象類,它直接繼承于 Object 類,沒有實現任何接口。Dictionary 類是 JDK 1.0 的引入的。雖然 Dictionary 也支持 “添加 key-value 鍵值對”、“獲取 value”、“獲取大小” 等基本操作,但它的 API 函數比 Map 少;而且 Dictionary 一般是通過 Enumeration(枚舉類)去遍歷,Map 則是通過 Iterator(迭代 M 器)去遍歷。然而由于 Hashtable 也實現了 Map 接口,所以,它即支持 Enumeration 遍歷,也支持 Iterator 遍歷。
          • AbstractMap 是一個抽象類,它實現了 Map 接口的絕大部分 API 函數;為 Map 的具體實現類提供了極大的便利。它是 JDK 1.2 新增的類。

          **(5)支持的遍歷種類不同:**HashMap 只支持 Iterator(迭代器) 遍歷。而 Hashtable 支持 Iterator(迭代器) 和 Enumeration(枚舉器) 兩種方式遍歷。

          3.HashMap、Hashtable、LinkedHashMap 和 TreeMap 比較

          Hashmap 是一個最常用的 Map,它根據鍵的 HashCode 值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度。遍歷時,取得數據的順序是完全隨機的。**HashMap 最多只允許一條記錄的鍵為 Null;允許多條記錄的值為 Null;HashMap 不支持線程的同步,即任一時刻可以有多個線程同時寫 HashMap;可能會導致數據的不一致。**如果需要同步,可以用 Collections 的 synchronizedMap 方法使 HashMap 具有同步的能力。

          Hashtable 與 HashMap 類似,不同的是:它不允許記錄的鍵或者值為空;它支持線程的同步,即任一時刻只有一個線程能寫 Hashtable,因此也導致了 Hashtale 在寫入時會比較慢。

          LinkedHashMap 保存了記錄的插入順序,在用 Iterator 遍歷 LinkedHashMap 時,先得到的記錄肯定是先插入的,也可以在構造時用帶參數,按照應用次數排序。在遍歷的時候會比 HashMap 慢,不過有種情況例外,當 HashMap 容量很大,實際數據較少時,遍歷起來可能會比 LinkedHashMap 慢,因為 LinkedHashMap 的遍歷速度只和實際數據有關,和容量無關,而 HashMap 的遍歷速度和他的容量有關。

          如果需要輸出的順序和輸入的相同,那么用 LinkedHashMap 可以實現,它還可以按讀取順序來排列,像連接池中可以應用。LinkedHashMap 實現與 HashMap 的不同之處在于,后者維護著一個運行于所有條目的雙重鏈表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。對于 LinkedHashMap 而言,它繼承與 HashMap、底層使用哈希表與雙向鏈表來保存所有元素。其基本操作與父類 HashMap 相似,它通過重寫父類相關的方法,來實現自己的鏈接列表特性。

          **TreeMap 實現 SortMap 接口,內部實現是紅黑樹。**能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用 Iterator 遍歷 TreeMap 時,得到的記錄是排過序的。TreeMap 不允許 key 的值為 null。非同步的。

          一般情況下,我們用的最多的是 HashMap,HashMap 里面存入的鍵值對在取出的時候是隨機的,它根據鍵的 HashCode 值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度。在 Map 中插入、刪除和定位元素,HashMap 是最好的選擇。

          TreeMap 取出來的是排序后的鍵值對。但如果您要按自然順序或自定義順序遍歷鍵,那么 TreeMap 會更好。

          LinkedHashMap 是 HashMap 的一個子類,如果需要輸出的順序和輸入的相同,那么用 LinkedHashMap 可以實現,它還可以按讀取順序來排列,像連接池中可以應用。

          import java.util.HashMap;
          import java.util.Iterator;
          import java.util.LinkedHashMap;
          import java.util.TreeMap;
          
          public class MapTest {
          
              public static void main(String[] args) {
          
                  //HashMap
                  HashMap<String,String> hashMap = new HashMap();
                  hashMap.put("4", "d");
                  hashMap.put("3", "c");
                  hashMap.put("2", "b");
                  hashMap.put("1", "a");
          
                  Iterator<String> iteratorHashMap = hashMap.keySet().iterator();
          
                  System.out.println("HashMap-->");
          
                  while (iteratorHashMap.hasNext()){
          
                      Object key1 = iteratorHashMap.next();
                      System.out.println(key1 + "--" + hashMap.get(key1));
                  }
          
                  //LinkedHashMap
                  LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap();
                  linkedHashMap.put("4", "d");
                  linkedHashMap.put("3", "c");
                  linkedHashMap.put("2", "b");
                  linkedHashMap.put("1", "a");
          
                  Iterator<String> iteratorLinkedHashMap = linkedHashMap.keySet().iterator();
          
                  System.out.println("LinkedHashMap-->");
          
                  while (iteratorLinkedHashMap.hasNext()){
          
                      Object key2 = iteratorLinkedHashMap.next();
                      System.out.println(key2 + "--" + linkedHashMap.get(key2));
                  }
          
                  //TreeMap
                  TreeMap<String,String> treeMap = new TreeMap();
                  treeMap.put("4", "d");
                  treeMap.put("3", "c");
                  treeMap.put("2", "b");
                  treeMap.put("1", "a");
          
                  Iterator<String> iteratorTreeMap = treeMap.keySet().iterator();
          
                  System.out.println("TreeMap-->");
          
                  while (iteratorTreeMap.hasNext()){
          
                      Object key3 = iteratorTreeMap.next();
                      System.out.println(key3 + "--" + treeMap.get(key3));
                  }
          
              }
          
          }

          輸出結果:

          HashMap-->
          3--c
          2--b
          1--a
          4--d
          LinkedHashMap-->
          4--d
          3--c
          2--b
          1--a
          TreeMap-->
          1--a
          2--b
          3--c
          4--d

          4.HashSet、LinkedHashSet、TreeSet 比較

          Set 接口

          Set 不允許包含相同的元素,如果試圖把兩個相同元素加入同一個集合中,add 方法返回 false。

          **Set 判斷兩個對象相同不是使用 == 運算符,而是根據 equals 方法。**也就是說,只要兩個對象用 equals 方法比較返回 true,Set 就不會接受這兩個對象。

          HashSet

          HashSet 有以下特點:

          • 不能保證元素的排列順序,順序有可能發生變化。
          • 不是同步的。
          • 集合元素可以是 null,但只能放入一個 null。

          當向 HashSet 結合中存入一個元素時,HashSet 會調用該對象的 hashCode() 方法來得到該對象的 hashCode 值,然后根據 hashCode 值來決定該對象在 HashSet 中存儲位置。簡單的說,HashSet 集合判斷兩個元素相等的標準是兩個對象通過 equals 方法比較相等,并且兩個對象的 hashCode() 方法返回值也相等。

          注意,如果要把一個對象放入 HashSet 中,重寫該對象對應類的 equals 方法,也應該重寫其 hashCode() 方法。其規則是如果兩個對象通過 equals 方法比較返回 true 時,其 hashCode 也應該相同。另外,對象中用作 equals 比較標準的屬性,都應該用來計算 hashCode 的值。

          LinkedHashSet

          LinkedHashSet 集合同樣是根據元素的 hashCode 值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet 將會以元素的添加順序訪問集合的元素。

          LinkedHashSet 在迭代訪問 Set 中的全部元素時,性能比 HashSet 好,但是插入時性能稍微遜色于 HashSet。

          TreeSet 類

          TreeSet 是 SortedSet 接口的唯一實現類,TreeSet 可以確保集合元素處于排序狀態。TreeSet 支持兩種排序方式,自然排序和定制排序,其中自然排序為默認的排序方式。向 TreeSet 中加入的應該是同一個類的對象。

          TreeSet 判斷兩個對象不相等的方式是兩個對象通過 equals 方法返回 false,或者通過 CompareTo 方法比較沒有返回 0。

          自然排序

          自然排序使用要排序元素的CompareTo(Object obj)方法來比較元素之間大小關系,然后將元素按照升序排列。

          Java 提供了一個 Comparable 接口,該接口里定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現了該接口的對象就可以比較大小。obj1.compareTo(obj2)方法如果返回 0,則說明被比較的兩個對象相等,如果返回一個正數,則表明 obj1 大于 obj2,如果是負數,則表明 obj1 小于 obj2。如果我們將兩個對象的 equals 方法總是返回 true,則這兩個對象的 compareTo 方法返回應該返回 0。

          定制排序

          自然排序是根據集合元素的大小,以升序排列,如果要定制排序,應該使用 Comparator 接口,實現 int compare(T o1,T o2)方法。

          package com.test;  
          
          import java.util.HashSet;  
          import java.util.LinkedHashSet;  
          import java.util.TreeSet;  
          
          /**  
           * @description 幾個set的比較  
           *    HashSet:哈希表是通過使用稱為散列法的機制來存儲信息的,元素并沒有以某種特定順序來存放;  
           *    LinkedHashSet:以元素插入的順序來維護集合的鏈接表,允許以插入的順序在集合中迭代;  
           *    TreeSet:提供一個使用樹結構存儲Set接口的實現,對象以升序順序存儲,訪問和遍歷的時間很快。  
           * @author Zhou-Jingxian  
           *  
           */  
          public class SetDemo {  
          
              public static void main(String[] args) {  
          
                  HashSet<String> hs = new HashSet<String>();  
                  hs.add("B");  
                  hs.add("A");  
                  hs.add("D");  
                  hs.add("E");  
                  hs.add("C");  
                  hs.add("F");  
                  System.out.println("HashSet 順序:\n"+hs);  
          
                  LinkedHashSet<String> lhs = new LinkedHashSet<String>();  
                  lhs.add("B");  
                  lhs.add("A");  
                  lhs.add("D");  
                  lhs.add("E");  
                  lhs.add("C");  
                  lhs.add("F");  
                  System.out.println("LinkedHashSet 順序:\n"+lhs);  
          
                  TreeSet<String> ts = new TreeSet<String>();  
                  ts.add("B");  
                  ts.add("A");  
                  ts.add("D");  
                  ts.add("E");  
                  ts.add("C");  
                  ts.add("F");  
                  System.out.println("TreeSet 順序:\n"+ts);  
              }  
          }

          輸出結果:

          HashSet 順序:[D, E, F, A, B, C]
          LinkedHashSet 順序:[B, A, D, E, C, F]
          TreeSet 順序:[A, B, C, D, E, F]

          5、Iterator 和 ListIterator 區別

          我們在使用 List,Set 的時候,為了實現對其數據的遍歷,我們經常使用到了 Iterator(迭代器)。使用迭代器,你不需要干涉其遍歷的過程,只需要每次取出一個你想要的數據進行處理就可以了。但是在使用的時候也是有不同的。

          List 和 Set 都有iterator()來取得其迭代器。對 List 來說,你也可以通過 listIterator() 取得其迭代器,兩種迭代器在有些時候是不能通用的,Iterator 和 ListIterator 主要區別在以下方面:

          • ListIterator 有add()方法,可以向 List 中添加對象,而 Iterator 不能
          • ListIterator 和 Iterator 都有hasNext()和next()方法,可以實現順序向后遍歷,但是 ListIterator 有hasPrevious()和previous()方法,可以實現逆向(順序向前)遍歷。Iterator 就不可以。
          • ListIterator 可以定位當前的索引位置,nextIndex()和previousIndex()可以實現。Iterator 沒有此功能。
          • 都可實現刪除對象,但是 ListIterator 可以實現對象的修改,set()方法可以實現。Iierator 僅能遍歷,不能修改。

          因為 ListIterator 的這些功能,可以實現對 LinkedList 等 List 數據結構的操作。其實,數組對象也可以用迭代器來實現。

          6、Collection 和 Collections 區別

          **(1)java.util.Collection 是一個集合接口(集合類的一個頂級接口)。**它提供了對集合對象進行基本操作的通用接口方法。Collection 接口在 Java 類庫中有很多具體的實現。Collection 接口的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承接口有 List 與 Set。

           Collection   
          ├List   
          │├LinkedList   
          │├ArrayList   
          │└Vector   
          │ └Stack   
          └Set 

          **(2)java.util.Collections 是一個包裝類(工具類 / 幫助類)。**它包含有各種有關集合操作的靜態多態方法。此類不能實例化,就像一個工具類,用于對集合中元素進行排序、搜索以及線程安全等各種操作,服務于 Java 的 Collection 框架。

          代碼示例:

          import java.util.ArrayList; 
          import java.util.Collections; 
          import java.util.List; 
          
          public class TestCollections { 
          
              public static void main(String args[]) { 
                  //注意List是實現Collection接口的 
                  List list = new ArrayList(); 
                  double array[] = { 112, 111, 23, 456, 231 }; 
                  for (int i = 0; i < array.length; i++) { 
                      list.add(new Double(array[i])); 
                  } 
                  Collections.sort(list); 
                  for (int i = 0; i < array.length; i++) { 
                      System.out.println(list.get(i)); 
                  } 
                  // 結果:23.0 111.0 112.0 231.0 456.0 
              } 
          }

          -END-

          質文章,及時送達

          作者:平凡希

          cnblogs.com/xiaoxi/p/6089984.html

          一、集合框架圖

          簡化圖:

          說明:對于以上的框架圖有如下幾點說明

          1、所有集合類都位于java.util包下。Java的集合類主要由兩個接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口,這兩個接口又包含了一些子接口或實現類。

          2、集合接口:6個接口(短虛線表示),表示不同集合類型,是集合框架的基礎。

          3、抽象類:5個抽象類(長虛線表示),對集合接口的部分實現。可擴展為自定義集合類。

          4、實現類:8個實現類(實線表示),對接口的具體實現。

          5、Collection 接口是一組允許重復的對象。

          6、Set 接口繼承 Collection,集合元素不重復。

          7、List 接口繼承 Collection,允許重復,維護元素插入順序。

          8、Map接口是鍵-值對象,與Collection接口沒有什么關系。

          9、Set、List和Map可以看做集合的三大類:

          • List集合是有序集合,集合中的元素可以重復,訪問集合中的元素可以根據元素的索引來訪問。

          • Set集合是無序集合,集合中的元素不可以重復,訪問集合中的元素只能根據元素本身來訪問(也是集合里元素不允許重復的原因)。

          • Map集合中保存Key-value對形式的元素,訪問時只能根據每項元素的key來訪問其value。

          二、總體分析

          大致說明:

          看上面的框架圖,先抓住它的主干,即Collection和Map。

          1、Collection是一個接口,是高度抽象出來的集合,它包含了集合的基本操作和屬性。Collection包含了List和Set兩大分支。

          • List是一個有序的隊列,每一個元素都有它的索引。第一個元素的索引值是0。List的實現類有LinkedList, ArrayList, Vector, Stack。
          • Set是一個不允許有重復元素的集合。Set的實現類有HastSet和TreeSet。HashSet依賴于HashMap,它實際上是通過HashMap實現的;TreeSet依賴于TreeMap,它實際上是通過TreeMap實現的。

          2、Map是一個映射接口,即key-value鍵值對。Map中的每一個元素包含“一個key”和“key對應的value”。AbstractMap是個抽象類,它實現了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是繼承于AbstractMap。Hashtable雖然繼承于Dictionary,但它實現了Map接口。3、接下來,再看Iterator。它是遍歷集合的工具,即我們通常通過Iterator迭代器來遍歷集合。我們說Collection依賴于Iterator,是因為Collection的實現類都要實現iterator函數,返回一個Iterator對象。ListIterator是專門為遍歷List而存在的。4、再看Enumeration,它是JDK 1.0引入的抽象類。作用和Iterator一樣,也是遍歷集合;但是Enumeration的功能要比Iterator少。在上面的框圖中,Enumeration只能在Hashtable, Vector, Stack中使用。

          5、最后,看Arrays和Collections。它們是操作數組、集合的兩個工具類。

          有了上面的整體框架之后,我們接下來對每個類分別進行分析。

          三、Collection接口

          Collection接口是處理對象集合的根接口,其中定義了很多對元素進行操作的方法。Collection接口有兩個主要的子接口List和Set,注意Map不是Collection的子接口,這個要牢記。

          Collection接口中的方法如下:

          其中,有幾個比較常用的方法,比如方法add添加一個元素到集合中,addAll將指定集合中的所有元素添加到集合中,contains方法檢測集合中是否包含指定的元素,toArray方法返回一個表示集合的數組。另外,Collection中有一個iterator函數,它的作用是返回一個Iterator接口。通常,我們通過Iterator迭代器來遍歷集合。ListIterator是List接口所特有的,在List接口中,通過ListIterator返回一個ListIterator對象。

          Collection接口有兩個常用的子接口,下面詳細介紹。

          1.List接口

          List集合代表一個有序集合,集合中每個元素都有其對應的順序索引。List集合允許使用重復元素,可以通過索引來訪問指定位置的集合元素。

          List接口繼承于Collection接口,它可以定義一個允許重復的有序集合。因為List中的元素是有序的,所以我們可以通過使用索引(元素在List中的位置,類似于數組下標)來訪問List中的元素,這類似于Java的數組。

          List接口為Collection直接接口。List所代表的是有序的Collection,即它用某種特定的插入順序來維護元素順序。用戶可以對列表中每個元素的插入位置進行精確地控制,同時可以根據元素的整數索引(在列表中的位置)訪問元素,并搜索列表中的元素。實現List接口的集合主要有:ArrayList、LinkedList、Vector、Stack

          (1)ArrayList

          ArrayList是一個動態數組,也是我們最常用的集合。它允許任何符合規則的元素插入甚至包括。每一個ArrayList都有一個初始容量(10),該容量代表了數組的大小。隨著容器中的元素不斷增加,容器的大小也會隨著增加。在每次向容器中增加元素的同時都會進行容量檢查,當快溢出時,就會進行擴容操作。所以如果我們明確所插入元素的多少,最好指定一個初始容量值,避免過多的進行擴容操作而浪費時間、效率。size、isEmpty、get、set、iterator和 listIterator 操作都以固定時間運行。add 操作以分攤的固定時間運行,也就是說,添加 n 個元素需要 O(n) 時間(由于要考慮到擴容,所以這不只是添加元素會帶來分攤固定時間開銷那樣簡單)。ArrayList擅長于隨機訪問。同時ArrayList是非同步的。

          (2)LinkedList

          同樣實現List接口的LinkedList與ArrayList不同,ArrayList是一個動態數組,而LinkedList是一個雙向鏈表。所以它除了有ArrayList的基本操作方法外還額外提供了get,remove,insert方法在LinkedList的首部或尾部。由于實現的方式不同,LinkedList不能隨機訪問,它所有的操作都是要按照雙重鏈表的需要執行。在列表中索引的操作將從開頭或結尾遍歷列表(從靠近指定索引的一端)。這樣做的好處就是可以通過較低的代價在List中進行插入和刪除操作。與ArrayList一樣,LinkedList也是非同步的。如果多個線程同時訪問一個List,則必須自己實現訪問同步。一種解決方法是在創建List時構造一個同步的List:
          List list = Collections.synchronizedList(new LinkedList(...));

          (3)Vector

          與ArrayList相似,但是Vector是同步的。所以說Vector是線程安全的動態數組。它的操作與ArrayList幾乎一樣。

          (4)Stack

          Stack繼承自Vector,實現一個后進先出的堆棧。Stack提供5個額外的方法使得Vector得以被當作堆棧使用。基本的push和pop 方法,還有peek方法得到棧頂的元素,empty方法測試堆棧是否為空,search方法檢測一個元素在堆棧中的位置。Stack剛創建后是空棧。

          2.Set接口

          Set是一種不包括重復元素的Collection。它維持它自己的內部排序,所以隨機訪問沒有任何意義。與List一樣,它同樣允許的存在但是僅有一個。由于Set接口的特殊性,所有傳入Set集合中的元素都必須不同,同時要注意任何可變對象,如果在對集合中元素進行操作時,導致e1.equals(e2)==true,則必定會產生某些問題。Set接口有三個具體實現類,分別是散列集HashSet、鏈式散列集LinkedHashSet和樹形集TreeSet。Set是一種不包含重復的元素的Collection,無序,即任意的兩個元素e1和e2都有e1.equals(e2)=false,Set最多有一個元素。

          需要注意的是:雖然Set中元素沒有順序,但是元素在set中的位置是由該元素的HashCode決定的,其具體位置其實是固定的。

          此外需要說明一點,在set接口中的不重復是有特殊要求的。

          舉一個例子:對象A和對象B,本來是不同的兩個對象,正常情況下它們是能夠放入到Set里面的,但是如果對象A和B的都重寫了hashcode和equals方法,并且重寫后的hashcode和equals方法是相同的話。那么A和B是不能同時放入到Set集合中去的,也就是Set集合中的去重和hashcode與equals方法直接相關。

          為了更好地理解,請看下面的例子:

          public class Test{ 
          public static void main(String[] args) {
          Set<String> set=new HashSet<String>;
          set.add("Hello");
          set.add("world");
          set.add("Hello");
          System.out.println("集合的尺寸為:"+set.size);
          System.out.println("集合中的元素為:"+set.toString);
          }
          }

          運行結果:

          集合的尺寸為:2
          集合中的元素為:[world, Hello]

          分析:由于String類中重寫了hashcode和equals方法,用來比較指向的字符串對象所存儲的字符串是否相等。所以這里的第二個Hello是加不進去的。

          再看一個例子:

          public class TestSet {

          public static void main(String[] args){

          Set<String> books = new HashSet<String>;
          //添加一個字符串對象
          books.add(new String("Struts2權威指南"));

          //再次添加一個字符串對象,
          //因為兩個字符串對象通過equals方法比較相等,所以添加失敗,返回false
          boolean result = books.add(new String("Struts2權威指南"));

          System.out.println(result);

          //下面輸出看到集合只有一個元素
          System.out.println(books);

          }
          }

          運行結果:

          false
          [Struts2權威指南]

          說明:程序中,book集合兩次添加的字符串對象明顯不是一個對象(程序通過new關鍵字來創建字符串對象),當使用==運算符判斷返回false,使用equals方法比較返回true,所以不能添加到Set集合中,最后只能輸出一個元素。

          (1)HashSet

          HashSet 是一個沒有重復元素的集合。它是由HashMap實現的,不保證元素的順序(這里所說的沒有順序是指:元素插入的順序與輸出的順序不一致),而且HashSet允許使用 元素。HashSet是非同步的,如果多個線程同時訪問一個哈希set,而其中至少一個線程修改了該set,那么它必須保持外部同步。HashSet按Hash算法來存儲集合的元素,因此具有很好的存取和查找性能。

          HashSet的實現方式大致如下,通過一個HashMap存儲元素,元素是存放在HashMap的Key中,而Value統一使用一個Object對象。

          HashSet使用和理解中容易出現的誤區:a.HashSet中存放值。HashSet中是允許存入值的,但是在HashSet中僅僅能夠存入一個值。b.HashSet中存儲元素的位置是固定的。HashSet中存儲的元素的是無序的,這個沒什么好說的,但是由于HashSet底層是基于Hash算法實現的,使用了hashcode,所以HashSet中相應的元素的位置是固定的。c.必須小心操作可變對象Mutable Object)。如果一個Set中的可變元素改變了自身狀態導致Object.equals(Object)=true將導致一些問題。

          (2)LinkedHashSet

          LinkedHashSet繼承自HashSet,其底層是基于LinkedHashMap來實現的,有序,非同步。LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。

          (3)TreeSet

          TreeSet是一個有序集合,其底層是基于TreeMap實現的,非線程安全。TreeSet可以確保集合元素處于排序狀態。TreeSet支持兩種排序方式,自然排序和定制排序,其中自然排序為默認的排序方式。當我們構造TreeSet時,若使用不帶參數的構造函數,則TreeSet的使用自然比較器;若用戶需要使用自定義的比較器,則需要使用帶比較器的參數。

          注意:TreeSet集合不是通過hashcode和equals函數來比較元素的.它是通過compare或者comparaeTo函數來判斷元素是否相等.compare函數通過判斷兩個對象的id,相同的id判斷為重復元素,不會被加入到集合中。

          四、Map接口

          Map與List、Set接口不同,它是由一系列鍵值對組成的集合,提供了key到Value的映射。同時它也沒有繼承Collection。在Map中它保證了key與value之間的一一對應關系。也就是說一個key對應一個value,所以它不能存在相同的key值,當然value值可以相同

          1.HashMap

          以哈希表數據結構實現,查找對象時通過哈希函數計算其位置,它是為快速查詢而設計的,其內部定義了一個hash表數組(Entry[] table),元素會通過哈希轉換函數將元素的哈希地址轉換成數組中存放的索引,如果有沖突,則使用散列鏈表的形式將所有相同哈希地址的元素串起來,可能通過查看HashMap.Entry的源碼它是一個單鏈表結構。

          2.LinkedHashMap

          LinkedHashMap是HashMap的一個子類,它保留插入的順序,如果需要輸出的順序和輸入時的相同,那么就選用LinkedHashMap。

          LinkedHashMap是Map接口的哈希表和鏈接列表實現,具有可預知的迭代順序。此實現提供所有可選的映射操作,并允許使用值和鍵。此類不保證映射的順序,特別是它不保證該順序恒久不變。

          LinkedHashMap實現與HashMap的不同之處在于,后者維護著一個運行于所有條目的雙重鏈接列表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。

          根據鏈表中元素的順序可以分為:按插入順序的鏈表,和按訪問順序(調用get方法)的鏈表。默認是按插入順序排序,如果指定按訪問順序排序,那么調用get方法后,會將這次訪問的元素移至鏈表尾部,不斷訪問可以形成按訪問順序排序的鏈表。

          注意,此實現不是同步的。如果多個線程同時訪問鏈接的哈希映射,而其中至少一個線程從結構上修改了該映射,則它必須保持外部同步。由于LinkedHashMap需要維護元素的插入順序,因此性能略低于HashMap的性能,但在迭代訪問Map里的全部元素時將有很好的性能,因為它以鏈表來維護內部順序。

          3.TreeMap

          TreeMap 是一個有序的key-value集合,非同步,基于紅黑樹(Red-Black tree)實現,每一個key-value節點作為紅黑樹的一個節點。TreeMap存儲時會進行排序的,會根據key來對key-value鍵值對進行排序,其中排序方式也是分為兩種,一種是自然排序,一種是定制排序,具體取決于使用的構造方法。自然排序:TreeMap中所有的key必須實現Comparable接口,并且所有的key都應該是同一個類的對象,否則會報ClassCastException異常。定制排序:定義TreeMap時,創建一個comparator對象,該對象對所有的treeMap中所有的key值進行排序,采用定制排序的時候不需要TreeMap中所有的key必須實現Comparable接口。TreeMap判斷兩個元素相等的標準:兩個key通過compareTo方法返回0,則認為這兩個key相等。如果使用自定義的類來作為TreeMap中的key值,且想讓TreeMap能夠良好的工作,則必須重寫自定義類中的equals方法,TreeMap中判斷相等的標準是:兩個key通過equals方法返回為true,并且通過compareTo方法比較應該返回為0。

          五、Iterator 與 ListIterator詳解

          1.Iterator

          Iterator的定義如下:

          public interface Iterator<E> {}

          Iterator是一個接口,它是集合的迭代器。集合可以通過Iterator去遍歷集合中的元素。

          Iterator提供的API接口如下:

          • boolean hasNext:判斷集合里是否存在下一個元素。如果有,hasNext方法返回 true。

          • Object next:返回集合里下一個元素。

          • void remove:刪除集合里上一次next方法返回的元素。

          使用示例:

          public class IteratorExample {
          public static void main(String[] args) {
          ArrayList<String> a = new ArrayList<String>;
          a.add("aaa");
          a.add("bbb");
          a.add("ccc");
          System.out.println("Before iterate : " + a);
          Iterator<String> it = a.iterator;
          while (it.hasNext) {
          String t = it.next;
          if ("bbb".equals(t)) {
          it.remove;
          }
          }
          System.out.println("After iterate : " + a);
          }
          }

          輸出結果如下:

          Before iterate : [aaa, bbb, ccc]
          After iterate : [aaa, ccc]

          注意:

          • Iterator只能單向移動。

          • Iterator.remove是唯一安全的方式來在迭代過程中修改集合;如果在迭代過程中以任何其它的方式修改了基本集合將會產生未知的行為。而且每調用一次next方法,remove方法只能被調用一次,如果違反這個規則將拋出一個異常。

          2.ListIterator

          ListIterator是一個功能更加強大的迭代器, 它繼承于Iterator接口,只能用于各種List類型的訪問。可以通過調用listIterator方法產生一個指向List開始處的ListIterator, 還可以調用listIterator(n)方法創建一個一開始就指向列表索引為n的元素處的ListIterator。

          ListIterator接口定義如下:

          public interface ListIterator<E> extends Iterator<E> {
          boolean hasNext;

          E next;

          boolean hasPrevious;

          E previous;

          int nextIndex;

          int previousIndex;

          void remove;

          void set(E e);

          void add(E e);

          }

          由以上定義我們可以推出ListIterator可以:

          • 雙向移動(向前/向后遍歷).

          • 產生相對于迭代器在列表中指向的當前位置的前一個和后一個元素的索引.

          • 可以使用set方法替換它訪問過的最后一個元素.
          • 可以使用add方法在next方法返回的元素之前或previous方法返回的元素之后插入一個元素.

          使用示例:

          public class ListIteratorExample {

          public static void main(String[] args) {
          ArrayList<String> a = new ArrayList<String>;
          a.add("aaa");
          a.add("bbb");
          a.add("ccc");
          System.out.println("Before iterate : " + a);
          ListIterator<String> it = a.listIterator;
          while (it.hasNext) {
          System.out.println(it.next + ", " + it.previousIndex + ", " + it.nextIndex);
          }
          while (it.hasPrevious) {
          System.out.print(it.previous + " ");
          }
          System.out.println;
          it = a.listIterator(1);
          while (it.hasNext) {
          String t = it.next;
          System.out.println(t);
          if ("ccc".equals(t)) {
          it.set("nnn");
          } else {
          it.add("kkk");
          }
          }
          System.out.println("After iterate : " + a);
          }
          }

          輸出結果如下:

          Before iterate : [aaa, bbb, ccc]
          aaa, 0, 1
          bbb, 1, 2
          ccc, 2, 3
          ccc bbb aaa
          bbb
          ccc
          After iterate : [aaa, bbb, kkk, nnn]

          六、異同點

          1.ArrayList和LinkedList

          • ArrayList是實現了基于動態數組的數據結構,LinkedList基于鏈表的數據結構。

          • 對于隨機訪問get和set,ArrayList絕對優于LinkedList,因為LinkedList要移動指針。

          • 對于新增和刪除操作add和remove,LinedList比較占優勢,因為ArrayList要移動數據。

          這一點要看實際情況的。若只對單條數據插入或刪除,ArrayList的速度反而優于LinkedList。但若是批量隨機的插入刪除數據,LinkedList的速度大大優于ArrayList. 因為ArrayList每插入一條數據,要移動插入點及之后的所有數據。

          2.HashTable與HashMap

          相同點:

          • 都實現了Map、Cloneable、java.io.Serializable接口。
          • 都是存儲"鍵值對(key-value)"的散列表,而且都是采用拉鏈法實現的。

          不同點:

          (1)歷史原因:HashTable是基于陳舊的Dictionary類的,HashMap是Java 1.2引進的Map接口的一個實現 。(2)同步性:HashTable是線程安全的,也就是說是同步的,而HashMap是線程序不安全的,不是同步的 。(3)對值的處理:HashMap的key、value都可為,HashTable的key、value都不可為 。(4)基類不同:HashMap繼承于AbstractMap,而Hashtable繼承于Dictionary。

          • Dictionary是一個抽象類,它直接繼承于Object類,沒有實現任何接口。Dictionary類是JDK 1.0的引入的。雖然Dictionary也支持“添加key-value鍵值對”、“獲取value”、“獲取大小”等基本操作,但它的API函數比Map少;而且Dictionary一般是通過Enumeration(枚舉類)去遍歷,Map則是通過Iterator(迭代M器)去遍歷。 然而由于Hashtable也實現了Map接口,所以,它即支持Enumeration遍歷,也支持Iterator遍歷。

          • AbstractMap是一個抽象類,它實現了Map接口的絕大部分API函數;為Map的具體實現類提供了極大的便利。它是JDK 1.2新增的類。

          (5)支持的遍歷種類不同:HashMap只支持Iterator(迭代器)遍歷。而Hashtable支持Iterator(迭代器)和Enumeration(枚舉器)兩種方式遍歷。

          3.HashMap、Hashtable、LinkedHashMap和TreeMap比較

          Hashmap 是一個最常用的Map,它根據鍵的HashCode 值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度。遍歷時,取得數據的順序是完全隨機的。HashMap最多只允許一條記錄的鍵為;允許多條記錄的值為;HashMap不支持線程的同步,即任一時刻可以有多個線程同時寫HashMap;可能會導致數據的不一致。如果需要同步,可以用Collections的synchronizedMap方法使HashMap具有同步的能力。Hashtable 與 HashMap類似,不同的是:它不允許記錄的鍵或者值為空;它支持線程的同步,即任一時刻只有一個線程能寫Hashtable,因此也導致了Hashtale在寫入時會比較慢。LinkedHashMap保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,也可以在構造時用帶參數,按照應用次數排序。在遍歷的時候會比HashMap慢,不過有種情況例外,當HashMap容量很大,實際數據較少時,遍歷起來可能會比LinkedHashMap慢,因為LinkedHashMap的遍歷速度只和實際數據有關,和容量無關,而HashMap的遍歷速度和他的容量有關。如果需要輸出的順序和輸入的相同,那么用LinkedHashMap可以實現,它還可以按讀取順序來排列,像連接池中可以應用。LinkedHashMap實現與HashMap的不同之處在于,后者維護著一個運行于所有條目的雙重鏈表。此鏈接列表定義了迭代順序,該迭代順序可以是插入順序或者是訪問順序。對于LinkedHashMap而言,它繼承與HashMap、底層使用哈希表與雙向鏈表來保存所有元素。其基本操作與父類HashMap相似,它通過重寫父類相關的方法,來實現自己的鏈接列表特性。TreeMap實現SortMap接口,內部實現是紅黑樹。能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator 遍歷TreeMap時,得到的記錄是排過序的。TreeMap不允許key的值為。非同步的。

          一般情況下,我們用的最多的是HashMap,HashMap里面存入的鍵值對在取出的時候是隨機的,它根據鍵的HashCode值存儲數據,根據鍵可以直接獲取它的值,具有很快的訪問速度。在Map 中插入、刪除和定位元素,HashMap 是最好的選擇。

          TreeMap取出來的是排序后的鍵值對。但如果您要按自然順序或自定義順序遍歷鍵,那么TreeMap會更好。

          LinkedHashMap 是HashMap的一個子類,如果需要輸出的順序和輸入的相同,那么用LinkedHashMap可以實現,它還可以按讀取順序來排列,像連接池中可以應用。

          import java.util.HashMap;
          import java.util.Iterator;
          import java.util.LinkedHashMap;
          import java.util.TreeMap;

          public class MapTest {

          public static void main(String[] args) {

          //HashMap
          HashMap<String,String> hashMap = new HashMap;
          hashMap.put("4", "d");
          hashMap.put("3", "c");
          hashMap.put("2", "b");
          hashMap.put("1", "a");

          Iterator<String> iteratorHashMap = hashMap.keySet.iterator;

          System.out.println("HashMap-->");

          while (iteratorHashMap.hasNext){

          Object key1 = iteratorHashMap.next;
          System.out.println(key1 + "--" + hashMap.get(key1));
          }

          //LinkedHashMap
          LinkedHashMap<String,String> linkedHashMap = new LinkedHashMap;
          linkedHashMap.put("4", "d");
          linkedHashMap.put("3", "c");
          linkedHashMap.put("2", "b");
          linkedHashMap.put("1", "a");

          Iterator<String> iteratorLinkedHashMap = linkedHashMap.keySet.iterator;

          System.out.println("LinkedHashMap-->");

          while (iteratorLinkedHashMap.hasNext){

          Object key2 = iteratorLinkedHashMap.next;
          System.out.println(key2 + "--" + linkedHashMap.get(key2));
          }

          //TreeMap
          TreeMap<String,String> treeMap = new TreeMap;
          treeMap.put("4", "d");
          treeMap.put("3", "c");
          treeMap.put("2", "b");
          treeMap.put("1", "a");

          Iterator<String> iteratorTreeMap = treeMap.keySet.iterator;

          System.out.println("TreeMap-->");

          while (iteratorTreeMap.hasNext){

          Object key3 = iteratorTreeMap.next;
          System.out.println(key3 + "--" + treeMap.get(key3));
          }

          }

          }

          輸出結果:

          HashMap-->
          3--c
          2--b
          1--a
          4--d
          LinkedHashMap-->
          4--d
          3--c
          2--b
          1--a
          TreeMap-->
          1--a
          2--b
          3--c
          4--d

          4.HashSet、LinkedHashSet、TreeSet比較

          Set接口

          Set不允許包含相同的元素,如果試圖把兩個相同元素加入同一個集合中,add方法返回false。Set判斷兩個對象相同不是使用==運算符,而是根據equals方法。也就是說,只要兩個對象用equals方法比較返回true,Set就不會接受這兩個對象。

          HashSet

          HashSet有以下特點:

          • 不能保證元素的排列順序,順序有可能發生變化。

          • 不是同步的。

          • 集合元素可以是,但只能放入一個。

          當向HashSet結合中存入一個元素時,HashSet會調用該對象的hashCode方法來得到該對象的hashCode值,然后根據 hashCode值來決定該對象在HashSet中存儲位置。簡單的說,HashSet集合判斷兩個元素相等的標準是兩個對象通過equals方法比較相等,并且兩個對象的hashCode方法返回值也相等。

          注意,如果要把一個對象放入HashSet中,重寫該對象對應類的equals方法,也應該重寫其hashCode方法。其規則是如果兩個對象通過equals方法比較返回true時,其hashCode也應該相同。另外,對象中用作equals比較標準的屬性,都應該用來計算 hashCode的值。

          LinkedHashSet

          LinkedHashSet集合同樣是根據元素的hashCode值來決定元素的存儲位置,但是它同時使用鏈表維護元素的次序。這樣使得元素看起來像是以插入順序保存的,也就是說,當遍歷該集合時候,LinkedHashSet將會以元素的添加順序訪問集合的元素。

          LinkedHashSet在迭代訪問Set中的全部元素時,性能比HashSet好,但是插入時性能稍微遜色于HashSet。

          TreeSet類

          TreeSet是SortedSet接口的唯一實現類,TreeSet可以確保集合元素處于排序狀態。TreeSet支持兩種排序方式,自然排序和定制排序,其中自然排序為默認的排序方式。向TreeSet中加入的應該是同一個類的對象。

          TreeSet判斷兩個對象不相等的方式是兩個對象通過equals方法返回false,或者通過CompareTo方法比較沒有返回0。自然排序自然排序使用要排序元素的CompareTo(Object obj)方法來比較元素之間大小關系,然后將元素按照升序排列。

          Java提供了一個Comparable接口,該接口里定義了一個compareTo(Object obj)方法,該方法返回一個整數值,實現了該接口的對象就可以比較大小。obj1.compareTo(obj2)方法如果返回0,則說明被比較的兩個對象相等,如果返回一個正數,則表明obj1大于obj2,如果是負數,則表明obj1小于obj2。如果我們將兩個對象的equals方法總是返回true,則這兩個對象的compareTo方法返回應該返回0。

          定制排序自然排序是根據集合元素的大小,以升序排列,如果要定制排序,應該使用Comparator接口,實現int compare(T o1,T o2)方法。

          package com.test; 

          import java.util.HashSet;
          import java.util.LinkedHashSet;
          import java.util.TreeSet;

          /**
          * @description 幾個set的比較
          * HashSet:哈希表是通過使用稱為散列法的機制來存儲信息的,元素并沒有以某種特定順序來存放;
          * LinkedHashSet:以元素插入的順序來維護集合的鏈接表,允許以插入的順序在集合中迭代;
          * TreeSet:提供一個使用樹結構存儲Set接口的實現,對象以升序順序存儲,訪問和遍歷的時間很快。
          * @author Zhou-Jingxian
          *
          */
          public class SetDemo {

          public static void main(String[] args) {

          HashSet<String> hs = new HashSet<String>;
          hs.add("B");
          hs.add("A");
          hs.add("D");
          hs.add("E");
          hs.add("C");
          hs.add("F");
          System.out.println("HashSet 順序:\n"+hs);

          LinkedHashSet<String> lhs = new LinkedHashSet<String>;
          lhs.add("B");
          lhs.add("A");
          lhs.add("D");
          lhs.add("E");
          lhs.add("C");
          lhs.add("F");
          System.out.println("LinkedHashSet 順序:\n"+lhs);

          TreeSet<String> ts = new TreeSet<String>;
          ts.add("B");
          ts.add("A");
          ts.add("D");
          ts.add("E");
          ts.add("C");
          ts.add("F");
          System.out.println("TreeSet 順序:\n"+ts);
          }
          }

          輸出結果:

          HashSet 順序:[D, E, F, A, B, C]
          LinkedHashSet 順序:[B, A, D, E, C, F]
          TreeSet 順序:[A, B, C, D, E, F]

          5、Iterator和ListIterator區別

          我們在使用List,Set的時候,為了實現對其數據的遍歷,我們經常使用到了Iterator(迭代器)。使用迭代器,你不需要干涉其遍歷的過程,只需要每次取出一個你想要的數據進行處理就可以了。但是在使用的時候也是有不同的。

          List和Set都有iterator來取得其迭代器。對List來說,你也可以通過listIterator取得其迭代器,兩種迭代器在有些時候是不能通用的,Iterator和ListIterator主要區別在以下方面:

          • ListIterator有add方法,可以向List中添加對象,而Iterator不能
          • ListIterator和Iterator都有hasNextnext方法,可以實現順序向后遍歷,但是ListIterator有hasPreviousprevious方法,可以實現逆向(順序向前)遍歷。Iterator就不可以。
          • ListIterator可以定位當前的索引位置,nextIndexpreviousIndex可以實現。Iterator沒有此功能。
          • 都可實現刪除對象,但是ListIterator可以實現對象的修改,set方法可以實現。Iierator僅能遍歷,不能修改。

          因為ListIterator的這些功能,可以實現對LinkedList等List數據結構的操作。其實,數組對象也可以用迭代器來實現。

          6、Collection 和 Collections區別

          (1)java.util.Collection是一個集合接口(集合類的一個頂級接口)。它提供了對集合對象進行基本操作的通用接口方法。Collection接口在Java 類庫中有很多具體的實現。Collection接口的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承接口有List與Set。
          Collection 
          ├List
          │├LinkedList
          │├ArrayList
          │└Vector
          │ └Stack
          └Set

          (2)java.util.Collections 是一個包裝類(工具類/幫助類)。它包含有各種有關集合操作的靜態多態方法。此類不能實例化,就像一個工具類,用于對集合中元素進行排序、搜索以及線程安全等各種操作,服務于Java的Collection框架。

          代碼示例:

          import java.util.ArrayList; 
          import java.util.Collections;
          import java.util.List;

          public class TestCollections {

          public static void main(String args[]) {
          //注意List是實現Collection接口的
          List list = new ArrayList;
          double array = { 112, 111, 23, 456, 231 };
          for (int i = 0; i < array.length; i++) {
          list.add(new Double(array[i]));
          }
          Collections.sort(list);
          for (int i = 0; i < array.length; i++) {
          System.out.println(list.get(i));
          }
          // 結果:23.0 111.0 112.0 231.0 456.0
          }
          }

          -END-

          如果看到這里,說明你喜歡這篇文章,請轉發。同時標星(置頂)本公眾號可以第一時間接受到博文推送。1. 太全了!互聯網大廠的薪資和職級一覽Redis + Tomcat + Nginx 集群實現 Session 共享3.雷軍 1994 年寫的代碼,不服不行!干掉 try catch !


          主站蜘蛛池模板: 久久蜜桃精品一区二区三区| 无码国产精品一区二区高潮| 日韩精品无码人妻一区二区三区| 日韩精品视频一区二区三区 | 亚洲熟妇av一区二区三区漫画| 精品一区二区久久久久久久网精| 精品视频在线观看你懂的一区| 国产精品无码亚洲一区二区三区| 中文字幕一区二区三区在线不卡| 亚洲一区二区三区乱码在线欧洲| 一区二区在线观看视频| 免费在线视频一区| 蜜臀AV无码一区二区三区| 亚洲色精品VR一区区三区| 亚洲AV永久无码精品一区二区国产| 尤物精品视频一区二区三区 | 视频精品一区二区三区| 三上悠亚日韩精品一区在线| 日韩一区二区视频在线观看| 国产精品无码一区二区在线观一 | 亚洲日本久久一区二区va| 日韩毛片一区视频免费| 中文字幕精品一区影音先锋| 国产裸体舞一区二区三区| 免费无码一区二区三区蜜桃 | 一区二区三区在线| 亚洲欧洲日韩国产一区二区三区| 日韩av片无码一区二区不卡电影| 国产美女口爆吞精一区二区| 亚洲一区在线观看视频| 中文字幕无码免费久久9一区9| 大帝AV在线一区二区三区| 精品亚洲综合在线第一区| 高清精品一区二区三区一区| 国产精品第一区第27页| 少妇一晚三次一区二区三区| 国产激情一区二区三区 | 日本无卡码免费一区二区三区| 国产精品免费综合一区视频| 一区二区亚洲精品精华液| 夜色福利一区二区三区|