日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

Java集合类原理详解

發布時間:2023/12/3 java 44 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Java集合类原理详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 1 集合框架
    • 1.1 集合框架概述
      • 1.1.1 容器簡介
      • 1.1.1 容器的分類
    • 1.2 Collection
      • 1.2.1 常用方法
      • 1.2.2 迭代器
    • 1.3 List
      • 1.3.1 概述
      • 1.3.2 常用方法
      • 1.3.3 實現原理
    • 1.4 Map
      • 1.4.1 概述
      • 1.4.2 常用方法
      • 1.4.3 Comparable 接口
      • 1.4.4 實現原理
      • 1.4.5 覆寫 hashCode()
    • 1.5 Set
      • 1.5.1 概述
      • 1.5.2 常用方法
      • 1.5.3 實現原理
    • 1.6 總結:集合框架中常用類比較
  • 2 練習
  • 3 附錄:排序

1 集合框架

1.1 集合框架概述

1.1.1 容器簡介

到目前為止,我們已經學習了如何創建多個不同的對象,定義了這些對象以后,我們就可以利用它們來做一些有意義的事情。

舉例來說,假設要存儲許多雇員,不同的雇員的區別僅在于雇員的身份證號。我們可以通過身份證號來順序存儲每個雇員,但是在內存中實現呢?是不是要準備足夠的內存來存儲 1000個雇員,然后再將這些雇員逐一插入?如果已經插入了 500條記錄,這時需要插入一 個身份證號較低的新雇員,該怎么辦呢?是在內存中將 500 條記錄全部下移后,再從開頭插 入新的記錄? 還是創建一個映射來記住每個對象的位置?當決定如何存儲對象的集合時,必須考慮如下問題。

對于對象集合,必須執行的操作主要以下三種:
?添加新的對象
?刪除對象
?查找對象

我們必須確定如何將新的對象添加到集合中??梢詫ο筇砑拥郊系哪┪?、開頭或者中間的某個邏輯位置。

從集合中刪除一個對象后,對象集合中現有對象會有什么影響呢?可能必須將內存移來 移去,或者就在現有對象所駐留的內存位置下一個“洞”?

在內存中建立對象集合后,必須確定如何定位特定對象??山⒁环N機制,利用該機制 可根據某些搜索條件(例如身份證號)直接定位到目標對象;否則,便需要遍歷集合中的每 個對象,直到找到要查找的對象為止。

前面大家已經學習過了數組。數組的作用是可以存取一組數據。但是它卻存在一些缺點, 使得無法使用它來比較方便快捷的完成上述應用場景的要求。

  • 首先,在很多數情況下面,我們需要能夠存儲一組數據的容器,這一點雖然數組可 以實現,但是如果我們需要存儲的數據的個數多少并不確定。比如說,我們需要在 容器里面存儲某個應用系統的當前的所有的在線用戶信息,而當前的在線用戶信息是時刻都可能在變化的。 也就是說,我們需要一種存儲數據的容器,它能夠自動的改變這個容器的所能存放的數據數量的大小。這一點上,如果使用數組來存儲的話,就顯得十分的笨拙。

  • 我們再假設這樣一種場景:假定一個購物網站,經過一段時間的運行,我們已經存儲了一系列的購物清單了,購物清單中有商品信息。如果我們想要知道這段時間里 面有多少種商品被銷售出去了。那么我們就需要一個容器能夠自動的過濾掉購物清 單中的關于商品的重復信息。如果使用數組,這也是很難實現的。

  • 最后再想想,我們經常會遇到這種情況,我知道某個人的帳號名稱,希望能夠進一 步了解這個人的其他的一些信息。也就是說,我們在一個地方存放一些用戶信息, 我們希望能夠通過用戶的帳號來查找到對應的該用戶的其他的一些信息。再舉個查字典例子:假設我們希望使用一個容器來存放單詞以及對于這個單詞的解釋,而當 我們想要查找某個單詞的意思的時候,能夠根據提供的單詞在這個容器中找到對應的單詞的解釋。如果使用數組來實現的話,就更加的困難了。

  • 為解決這些問題,Java里面就設計了容器集合,不同的容器集合以不同的格式保存對象。

    數學背景

    在常見用法中,集合(collection)和數學上直觀的集(set)的概念是相同的。集是 一個唯一項組,也就是說組中沒有重復項。實際上, “集合框架”包含了一個 Set 接口和許 多具體的 Set 類。但正式的集概念卻比 Java 技術提前了一個世紀,那時英國數學家 George Boole 按邏輯正式的定義了集的概念。大部分人在小學時通過我們熟悉的維恩圖 引入的“集的交”和“集的并”學到過一些集的理論。

    集的基本屬性如下:

    ?集內只包含每項的一個實例
    ?集可以是有限的,也可以是無限的
    ?可以定義抽象概念

    集不僅是邏輯學、數學和計算機科學的基礎,對于商業和系統的日常應用來說,它也很實用。
    “連接池”這一概念就是數據庫服務器的一個開放連接集。 Web 服務器必須管理客戶機和連 接集。文件描述符提供了操作系統中另一個集的示例。

    映射是一種特別的集。它是一種對(pair)集,每個對表示一個元素到另一元素的單向映射。 一些映射示例有:
    ? IP地址到域名(DNS)的映射
    ? 關鍵字到數據庫記錄的映射
    ? 字典(詞到含義的映射)
    ? 2 進制到 10 進制轉換的映射

    就像集一樣,映射背后的思想比 Java 編程語言早的多,甚至比計算機科學還早。而 Java 中的 Map 就是映射的一種表現形式。

    1.1.1 容器的分類

    既然您已經具備了一些集的理論,您應該能夠更輕松的理解“集合框架”。 “集合框架” 由一組用來操作對象的接口組成。不同接口描述不同類型的組。在很大程度上,一旦您理解 了接口,您就理解了框架。雖然您總要創建接口特定的實現,但訪問實際集合的方法應該限制在接口方法的使用上;因此,允許您更改基本的數據結構而不必改變其它代碼??蚣芙涌?層次結構如下圖所示。

    Java 容器類類庫的用途是“保存對象”,并將其劃分為兩個不同的概念:
    1) Collection 。 一組對立的元素,通常這些元素都服從某種規則。List必須保持元素特定 的順序,而 Set 不能有重復元素。

    2) Map 。 一組 成對的“鍵值對”對象。初看起來這似乎應該是一個 Collection ,其元素 是成對的對象,但是這樣的設計實現起來太笨拙了,于是我們將Map明確的提取出來形 成一個獨立的概念。另一方面,如果使用Collection表示Map的部分內容,會便于查看 此部分內容。因此Map 一樣容易擴展成多維Map,無需增加新的概念,只要讓Map 中的鍵值對的每個"值”也是一個Map即可。

    Collection 和 Map 的區別在于容器中每個位置保存的元素個數。 Collection 每個位置只能保 存一個元素(對象)。此類容器包括:List,它以特定的順序保存一組元素;Set則是元素 不能重復。

    Map保存的是“鍵值對”,就像一個小型數據庫。我們可以通過“鍵”找到該鍵對應的“值”。

    ? Collection -對象之間沒有指定的順序,允許重復元素。
    ? Set - 對象之間沒有指定的順序,不允許重復元素
    ? List- 對象之間有指定的順序,允許重復元素,并引入位置下標。
    ? Map - 接口用于保存關鍵字(Key)和數值(Value)的集合,集合中的每個對象 加入時都提供數值和關鍵字。 Map 接口既不繼承 Set 也不繼承 Collection。

    List、Set、Map共同的實現基礎是Object數組。

    除了四個歷史集合類外, Java2 框架還引入了六個集合實現,如下表所示。


    這里沒有 Collection 接口的實現,接下來我們再來看一下下面的這張關于集合框架的大 圖:

    這張圖看起來有點嚇人,熟悉之后就會發現其實只有三種容器:Map, List和Set,它 們各自有兩個三個實現版本。

    1.2 Collection

    1.2.1 常用方法

    Collection 接口用于表示任何對象或元素組。想要盡可能以常規方式處理一組元素時, 就使用這一接口。Collection在前面的大圖也可以看出,它是List和Set的父類。并且它本 身也是一個接口。它定義了作為集合所應該擁有的一些方法。如下:

    注意: 集合必須只有對象,集合中的元素不能是基本數據類型。

    Collection 接口支持如添加和除去等基本操作。設法除去一個元素時,如果這個元素存在, 除去的僅僅是集合中此元素的一個實例。

    ? boolean add(Object element)
    ? boolean remove(Object element)

    Collection 接口還支持查詢操作:

    ? int size()
    ? boolean isEmpty()
    ? boolean contains(Object element)
    ? Iterator iterator()

    組操作 :Collection 接口支持的其它操作,要么是作用于元素組的任務,要么是同時作用 于整個集合的任務。

    ? boolean containsAll(Collection collection)
    ? boolean addAll(Collection collection)
    ? void clear()
    ? void removeAll(Collection collection)
    ? void retainAll(Collection collection)

    containsAll() 方法允許您查找當前集合是否包含了另一個集合的所有元素,即另一個 集合是否是當前集合的子集。其余方法是可選的,因為特定的集合可能不支持集合更改。 addAll() 方法確保另一個集合中的所有元素都被添加到當前的集合中,通常稱為并。 clear() 方法從當前集合中除去所有元素。 removeAll() 方法類似于 clear() ,但 只除去了元素的一個子集。 retainAll() 方法類似于 removeAll() 方法,不過可能 感到它所做的與前面正好相反:它從當前集合中除去不屬于另一個集合的元素,即交。

    我們看一個簡單的例子,來了解一下集合類的基本方法的使用:

    import java.util.*; public class CollectionToArray { public static void main(String[] args) { Collection collection1 = new ArrayList();//倉U建一個集合對象 collection1.add("000"); //添加對象到Collection 集合中 collection1.add("111"); collection1.add("222"); System.out.println("集合collection1的大小:"+collection1.size ()); System.out.println("集合collection1的內容: "+collection1); collection1.remove ("000") ;//從集合collection1中移除掉"000"這個 對象 System.out.println("集合collection1移除 000 后的內容:"+collection1); System.out.println("集合collection1中是否包含000 :"+collection1.contains("000")); System.out.println("集合collection中是否包含111:"+collection1.contains("111")); Collection collection2 = new ArrayList(); collection2.addAll(collection1);//將collection1集合中的元素全部添加到collection2 中 System.out.println("集合collection2的內容:"+collection2); collection2.clear();//清空集合 collection1 中的元素 System.out.println("集合collection2是否為空 :"+collection2.isEmpty()); //將集合collection1轉化為數組 Object s[] = collection1.toArray(); for(int i=0;i<s.length;i++){ System.out.println(s[i]); } } }

    運行結果為:
    集合collection1的大小:3
    集合 collection1 的內容:[000, 111, 222]
    集合collection1移除000后的內容:[111, 222]
    集合collection1中是否包含000 : false
    集合collection1中是否包含111 : true
    集合 collection2 的內容:[111, 222]
    集合collection2是否為空:true
    111
    222

    這里需要注意的是, Collection 它僅僅只是一個接口,而我們真正使用的時候,確是創建該接口的一個實現類。做為集合的接口,它定義了所有屬于集合的類所都應該具有的一些方法。

    而 ArrayList (列表)類是集合類的一種實現方式。

    這里需要一提的是,因為Collection的實現基礎是數組,所以有轉換為Object數組的方法:

    ? Object[] toArray()
    ? Object[] toArray(Object[] a)

    其中第二個方法Object[] toArray(Object[] a)的參數a 應該是集合中所有存放的對象的類的父類。

    1.2.2 迭代器

    任何容器類,都必須有某種方式可以將東西放進去,然后由某種方式將東西取出來。畢竟,存放事物是容器最基本的工作。對于ArrayList,add()是插入對象的方法,而get()是 取出元素的方式之一。ArrayList很靈活,可以隨時選取任意的元素,或使用不同的下標一次選取多個元素。

    如果從更高層的角度思考,會發現這里有一個缺點:要使用容器,必須知道其中元素的確切 類型。初看起來這沒有什么不好的,但是考慮如下情況:如果原本是ArrayList,但是后來 考慮到容器的特點,你想換用 Set ,應該怎么做?或者你打算寫通用的代碼,它們只是使用 容器,不知道或者說不關心容器的類型,那么如何才能不重寫代碼就可以應用于不同類型的容器?

    所以迭代器(Iterator)的概念,也是出于一種設計模式就是為達成此目的而形成的。所以Collection不提供get()方法。如果要遍歷Collectin中的元素就必須用Iterator。

    迭代器(Iterator)本身就是一個對象,它的工作就是遍歷并選擇集合序列中的對象,而 客戶端的程序員不必知道或關心該序列底層的結構。此外,迭代器通常被稱為“輕量級”對 象,創建它的代價小。但是,它也有一些限制,例如,某些迭代器只能單向移動。

    Collection 接口的 iterator() 方法返回一個 Iterator。 Iterator 和您可 能已經熟悉的 Enumeration 接口類似。使用 Iterator 接口方法,您可以從頭至尾遍 歷集合,并安全的從底層Collection中除去元素。

    下面,我們看一個對于迭代器的簡單使用:

    import java.util.ArrayList; import java.util.Collection; import java.util.Iterator;public class IteratorDemo { public static void main(String[] args) { Collection collection = new ArrayList(); collection.add("s1"); collection.add("s2"); collection.add("s3"); Iterator iterator = collection.iterator(); // 得到一個迭代器 while (iterator.hasNext()) { Object element = iterator.next(); System.out.println("iterator = " + element); } if (collection.isEmpty()) System.out.println("collection is Empty!"); else System.out.println("collection is not Empty! size="+collection.size()); Iterator iterator2 = collection.iterator(); while (iterator2. hasNext()) { // 移除元素 Object element = iterator2.next(); System.out.println("remove: " + element); iterator2.remove(); } Iterator iterator3 = collection.iterator(); if (!iterator3.hasNext()) {//察看是否還有元素 System.out.println("還有元素"); } if (collection.isEmpty()) System.out.println("collection is Empty!"); //使用 collection.isEmpty()方法來判斷 } }

    程序的運行結果為:
    iterator = s1
    iterator = s2
    iterator = s3
    collection is not Empty! size=3
    remove: s1
    remove: s2
    remove: s3
    還有元素
    collection is Empty!

    可以看到, Java 的 Collection 的 Iterator 能夠用來:

  • 使用方法 iterator() 要求容器返回一個 Iterator ,第一次調用 Iterator 的 next() 方法 時,它返回集合序列的第一個元素。
  • 使用 next() 獲得集合序列的中的下一個元素。
  • 使用hasNext()檢查序列中是否有元素。
  • 使用remove()將迭代器新返回的元素刪除。
  • 需要注意的是:方法刪除由next方法返回的最后一個元素,在每次調用next時,remove方
    法只能被調用一次 。

    大家看,Java實現的這個迭代器的使用就是如此的簡單。Iterator (迭代器)雖然功能簡 單,但仍然可以幫助我們解決許多問題,同時針對List還有一個更復雜更高級的ListIterator。 您可以在下面的List講解中得到進一步的了解。

    1.3 List

    1.3.1 概述

    前面我們講述的Collection接口實際上并沒有直接的實現類。而List是容器的一種,表示列表的意思。當我們不知道存儲的數據有多少的情況,我們就可以使用List來完成存儲數據的工作。例如前面提到的一種場景。我們想要在保存一個應用系統當前的在線 用戶的信息。我們就可以使用一個List來存儲。因為List的最大的特點就是能夠自動 的根據插入的數據量來動態改變容器的大小。下面我們先看看List接口的一些常用方法。

    1.3.2 常用方法

    List 就是列表的意思,它是 Collection 的一種,即繼承了 Collection 接口,以定義 一個允許重復項的有序集合。該接口不但能夠對列表的一部分進行處理,還添加了面向位置 的操作。 List 是按對象的進入順序進行保存對象,而不做排序或編輯操作。它除了擁有 Collection 接口的所有的方法外還擁有一些其他的方法。

    面向位置的操作包括插入某個元素或 Collection 的功能,還包括獲取、除去或更改元素的功能。在 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 位置上的元素
    ? Object set(int index, Object element) :將 index 位置上的對象替換為 element 并返回 老的元素。

    先看一下下面表格:

    在“集合框架”中有兩種常規的 List 實現: ArrayList 和 LinkedList 。使用兩種List 實現的哪一種取決于您特定的需要。如果要支持隨機訪問,而不必在除尾部的任何位 置插入或除去元素,那么, ArrayList 提供了可選的集合。但如果,您要頻繁的從列表的 中間位置添加和除去元素,而只要順序的訪問列表元素,那么, LinkedList 實現更好。
    我們以ArrayList為例,先看一個簡單的例子:

    public class ListDemo { pulilic static void main(String[] a) { String strMonths[] = { "1","2","3","4","5","6","7","8","9","10","11","12" }; // get count of months int nMonthLen = strMonths.length; // create arrayList object List months = new ArrayList(); // put months value to array list object for (int i = 0; i < nMonthLen; i++) { months.add(strMonths[i]); } // print all element in ArrayList for (int i = months.seze() - 1; i >= 0; i--) { System.out.println(months.get(i)); } } }

    例子中,我們把12個月份存放到ArrayList中,然后用一個循環,并使用get ()方法將列表中的對象都取出來。

    而 LinkedList 添加了一些處理列表兩端元素的方法(下圖只顯示了新方法):

    使用這些新方法,您就可以輕松的把 LinkedList 當作一個堆棧、隊列或其它面向端點的數據結構。
    我們再來看另外一個使用LinkedList來實現一個簡單的隊列的例子:

    import java.util.*; public class ListExample { public static void main(String args[]) { LinkedList queue = new LinkedList(); queue.addFirst("Bernadine"); queue.addFirst("Elizabeth"); queue.addFirst("Gene"); queue.addFirst("Elizabeth"); queue.addFirst("Clara"); System.out.println(queue); queue.removeLast(); queue.removeLast(); System.out.println(queue); } }

    運行程序產生了以下輸出。請注意,與 Set 不同的是 List 允許重復。

    [Clara, Elizabeth, Gene, Elizabeth, Bernadine]
    [Clara, Elizabeth, Gene]

    該程序演示了具體 List 類的使用。第一部分,創建一個由 ArrayList 支持的 List。 填充完列表以后,特定條目就得到了。示例的 LinkedList 部分把 LinkedList 當作一 個隊列,從隊列頭部添加東西,從尾部除去。

    List 接口不但以位置友好的方式遍歷整個列表,還能處理集合的子集:

    ? ListIterator listIterator():返回一個 ListIterator 迭代器,默認開始位置為 0
    ? ListIterator listIterator(int startIndex):返 回一個 ListIterator 迭代器,開始位置為 startIndex
    ? List subList(int fromIndex, int toIndex) : 返回一個子列表 List ,元素存放為從 fromIndex 到 toIndex 之前的一個元素。

    處理 subList() 時,位于 fromIndex 的元素在子列表中,而位于 toIndex 的元素則 不是,提醒這一點很重要。以下 for-loop 測試案例大致反映了這一點:

    for (int i = fromIndex; i < toIndex; i++) { // process element at position i }

    此外,我們還應該提醒的是:對子列表的更改(如 add()、remove() 和 set() 調用) 對底層 List 也有影響。

    ListIterator 接口

    ListIterator 接口繼承 Iterator 接口以支持添加或更改底層集合中的元素,還支持雙向訪問。

    以下源代碼演示了列表中的反向循環。請注意 ListIterator 最初位于列表尾之后
    (list.size()),因為第一個元素的下標是0。

    List list = ...; ListIterator iterator = list.listIterator(list.size()); while (iterator.hasPrevious()) { Object element = iterator.previous(); // Process element }

    正常情況下,不用 ListIterator 改變某次遍歷集合元素的方向 — 向前或者向后。雖然在技術上可能實現時,但在 previous() 后立刻調用 next() ,返回的是同一個元素。 把調用 next() 和 previous() 的順序顛倒一下,結果相同。

    我們看一個 List 的例子:

    import j ava.util.*; public class ListIteratorTest { public static void main(String[] args) { List list = new ArrayList(); list.add("aaa"); list.add("bbb"); list.add("ccc"); list.add("ddd"); System.out.println ("下標0開始: " + list.listIterator(0).next()); // next() System.out.println ("下標1開始:" + list.listIterator(1).next()); System.out.println ("子List 1 -3 : " + list.subList(1,3)) ; // 子列表 Listiterator it = list.listIterator() ;//默認從下標0開始 //隱式光標屬性add操作,插入到當前的下標的前面 it.add("sss"); while (it.hasNext()) { System.out.println("next Index = " + it.nextIndex() + ", Object = " + it.next()); } // set屬性 ListIterator it1 = list.listIterator(); it1.next(); it1.set("ooo"); ListIterator it2 = list.listIterator(list.size());// 下標 while (it2 .hasPrevious()) { System.out.println("previous Index = " + it2.previousIndex() + ", Object = " + it2.previous()); } } }

    程序的執行結果為:
    下標0開始:aaa
    下標1開始:bbb
    子List 1-3:[bbb, ccc]
    next Index = 1, Object = aaa
    next Index = 2, Object = bbb
    next Index = 3, Object = ccc
    next Index = 4, Object = ddd
    previous Index = 4, Object = ddd
    previous Index = 3,Object = ccc
    previous Index = 2, Object = bbb
    previous Index = 1, Object = aaa
    previous Index = 0, Object = ooo

    我們還需要稍微再解釋一下 add() 操作。添加一個元素會導致新元素立刻 被添加到隱式光標的前面。因此,添加元素后調用 previous() 會返回新元素, 而調用 next() 則不起作用,返回添加操作之前的下一個元素。下標的顯示方式,如下圖所示:

    對于List的基本用法我們學會了,下面我們來進一步了解一下List的實現原理,以便加深我們對于集合的理解。

    1.3.3 實現原理

    前面己經提了一下Collection的實現基礎都是基于數組的。下面我們就已ArrayList為例,簡單分析一下 ArrayList 列表的實現方式。首先,先看下它的構造函數。
    下列表格是在SUN提供的API中的描述:

    其中第一個構造函數ArrayList ()和第二構造函數ArrayList (Collection c)是按照 Collection 接口文檔所述,所應該提供兩個構造函數,一個無參數,一個接受另一個 Collection。

    第 3 個構造函數:
    ArrayList(int initialCapacity) 是 ArrayList 實現的比較重要的構造函數,雖然, 我們不常用它,但是默認的構造函數正是調用帶參數: initialCapacity 的構造函數來實現的。其中參數:initialCapacity表示我們構造的這個ArrayList列表的初始化容量是多大。如果調用默認的構造函數,則表示默認調用該參數為initialCapacity =10 的方式,來進行構建一個 ArrayList 列表對象。

    為了更好的理解這個initialCapacity參數的概念,我們先看看ArrayList在Sun提 供的源碼中的實現方式。先看一下它的屬性有哪些:

    public class ArrayList extends AbstractList implements List, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8683452581122892189L; /** * The array buffer into which the elements of the ArrayList are stored? * The capac ity of the ArrayL ist is the length of this array buffer. * 列表的實現核心屬性:數組。我們使用診數組未進行存放集合中的數據。 * 而我們的初始化參數就是該數組構建時候的長度,即訪數組的性就是initialCapacity */ private transient Object elementData[]; /** * The size of the ArrayList (the number of elements it contains). * 列表中真實數據的存放個數 * @serial */ private int size;

    ArrayList 繼承了AbstractList 我們主要看看 ArrayList 中的屬性就可以了。
    ArrayList中主要包含 2 個屬性:

    ? private transient Object elementData[];
    ? private int size;

    其中數組:: elementData[] 是列表的實現核心屬性:數組。 我們使用該數組來進行存 放集合中的數據。而我們的初始化參數就是該數組構建時候的長度,即該數組的 length 屬性就是 initialCapacity 參數。

    Keys: transient表示被修飾的屬性不是對象持久狀態的一部分,不會自動的序列化。

    第2個屬性:size表示列表中真實數據的存放個數。

    我們再來看一下ArrayList的構造函數,加深一下ArrayList是基于數組的理解。

    public ArrayList(int initialCapacity) { super(); if (initialCapacity < 0) throw new IIlegalArgumentException("IIlegal Capacity:" + initialCapacity); // 構建一個初始化長度為initialcapacity的數組對象 this.elementData = new Object[initialcapacity]; /** * Constructs an empty list with an initial capacity of ten. */ public ArrayList() { this(10); }

    從源碼中可以看到默認的構造函數調用的就是帶參數的構造函數:
    public ArrayList(int initialCapacity)
    不過參數 initialCapacity = 10。
    我們主要看 ArrayList(int initialCapacity) 這個構造函數??梢钥吹?#xff1a; this.elementData = new Object[initialCapacity];
    我們就是使用的 initialCapacity 這個參數來創建一個 Object 數組。而我們所有的往該集合對象中存放的數據,就是存放到了這個Object數組中去了。

    我們在看看另外一個構造函數的源碼:

    * 通過另外一個容器對象要構建一個List,構建的數組初始化長度為另外一個容器的size屬性的1.1* @param c the collection whose elements are to be placed into this list. * @throws NullPointerException if the specified collection is null. */ public ArrayList(Collection c) { size = c.size(); // 當前元素的個數為另外一個容器中的元素的個數 // Allow 10% room for growth (擴充1.1倍的容量) elementData = new Object[(int) Math.min((size * 110L) / 100, Integer.MAX_VALUE)]; c.toArray(elementData); } /** * Returns the number of elements in this list. * 返回List中元素的個數 * @returnthe number of elements in this list. */ public int size () { return size; }

    這里,我們先看size()方法的實現形式。它的作用即是返回size屬性值的大小。 然后我們再看另外一個構造函數 public ArrayList(Collection c) ,該構造函數 的作用是把另外一個容器對象中的元素存放到當前的List對象中。

    可以看到,首先,我們是通過調用另外一個容器對象C的方法size()來設置當前的 List 對象的 size 屬性的長度大小。

    接下來,就是對elementData數組進行初始化,初始化的大小為原先容器大小的 1.1倍。最后,就是通過使用容器接口中的 Object[] toArray(Object[] a) 方法來把當前容器 中的對象都存放到新的數組elementData中。這樣就完成了一個ArrayList的建立。

    可能大家會存在一個問題,那就是,我們建立的這個ArrayList是使用數組來實現 的,但是數組的長度一旦被定下來,就不能改變了。而我們在給ArrayList對象中添加元素的時候,卻沒有長度限制。這個時候, ArrayList 中的 elementData 屬性就必須 存在一個需要動態的擴充容量的機制。我們看下面的代碼,它描述了這個擴充機制:

    * 該方法用來判斷當前的數組是否需要擴容,應該擴容多少 * @param minCapacity the desired minimum capacity */public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { // 如果minCapacity大于老的elementData數組的長度,那么就需要擴容 Object oldData[] = elementData; // 新的數組的長度為原來長度1.5倍加1,或者為minCapacity int newCapacity = (oldCapacity * 3) / 2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; elementData = new Object[newCapacity]; System.arraycopy(oldData, 0, elementData, 0, size); } }

    這個方法的作用就是用來判斷當前的數組是否需要擴容,應該擴容多少。其中屬性: modCount是繼承自父類,它表示當前的對象對elementData數組進行了多少次擴容, 清空,移除等操作。該屬性相當于是一個對于當前List對象的一個操作記錄日志號。我們主要看下面的代碼實現:

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

    下面我們看看ensureCapacity方法是如何使用的:

    * Appends the specified element to the end of this list. * @param o element to be appended to this list. * @return <tt>true</tt> (如果添加成功就返回true). */ public boolean add(Object o) { / /調用ensureCapacity方法來確定是否需要擴容 ensureCapacity (size + 1) ; // Increments modCount!! elementData[size++] = o; return true; }* 該方法用來將另外一個容器C中的元素都添加到當前的List* @param c the elements to be inserted into this list. * @return <tt>true</tt> 如果添加的元素個數不為0,就返回true * @throws NullPointerException if the specified collection is null */ public boolean addAll(Collection c) { Object[] a = c.toArray(); int numNew = a.length; // 判斷是否需要擴容 ensureCapacity (size + numNew) ; // Increments modCount!! System.arraycopy(a, 0, elementData, size, numNew); size += numNew; return numNew != 0; }

    上的兩個a d d方法都是往List中添加元素。每次在添加元素的時候,我們就需要判斷 一下,是否需要對于當前的數組進行擴容。

    我們主要看看 public boolean add (Object o)方法,可以發現在添加一個元素到容 器中的時候,首先我們會判斷是否需要擴容。因為只增加一個元素,所以擴容的大小判斷也 就為當前的size + 1來進行判斷。然后,就把新添加的元素放到數組elementData中。

    第二個方法 public boolean addAll(Collection c) 也是同樣的原理。將新的元素放到 elementData 數組之后。同時改變當前 List 對象的 size 屬性。

    類似的 List 中的其他的方法也都是基于數組進行操作的。大家有興趣可以看看源碼中的更多的實現方式。
    最后我們再看看如何判斷在集合中是否已經存在某一個對象的:

    /* * 判斷在當前的List是否包含某個對象 * @param elem element whose presence in this List is to be tested * @return 存在返回<code>true</code>;否則返回false */ public boolean contains (Object elem) { // 調用indexOf方法,如果返回值大于-1則表示存在該elem對象 return indexOf(elem) >= 0; } /* * 將是包含某于對象elem的位置返回,如果不包含該對象則返回-1 * @param elem an object * @return 返回該對象第一次出現的位置,如果環存在則返回-1 * @see Object#equaIs(Object)[通過對象的equals來判斷是否相等] */ public int inclexOf(Object elem) { if (elem == null) { for (int i = 0; i < size; i++) if (elementData[i] == null) return i; } else { // 使用elem象的equals來判斷是否在數組中存在該對象 for (int i = 0; i < size; i++) if (elem.equals(elementData[i])) return i; } return -1; }

    由源碼中我們可以看到, public boolean contains(Object elem) 方法是通過調用 public int indexOf(Object elem) 方法來判斷是否在集合中存在某個對象elem。我們看看indexOf方法的具體實現。

    ? 首先我們判斷一下elem對象是否為null ,如果為null的話,那么遍歷數組 elementData 把第一個出現 null的位置返回。
    ? 如果 elem 不為 null 的話,我們也是遍歷數組 elementData ,并通過調用 elem對象的equals()方法來得到第一個相等的元素的位置。
    這里我們可以發現,ArrayList中用來判斷是否包含一個對象,調用的是各個對象自己實現的 equals() 方法。在前面的高級特性里面,我們可以知道:如果要判斷一個類的一 個實例對象是否等于另外一個對象, 那么我們就需要自己覆寫 Object 類的 public boolean equals(Object obj) 方法。如果不覆寫該方法的話,那么就會調用 Object 的 equals() 方法來進行判斷。這就相當于比較兩個對象的內存應用地址是否相等了。
    在集合框架中,不僅僅是List,所有的集合類,如果需要判斷里面是否存放了的某個對象,都是調用該對象的equals()方法來進行處理的。

    1.4 Map

    1.4.1 概述

    數學中的映射關系在Java中就是通過Map來實現的。它表示,里面存儲的元素是一個對 (pair) ,我們通過一個對象,可以在這個映射關系中找到另外一個和這個對象相關的東西。 前面提到的我們對于根據帳號名得到對應的人員的信息,就屬于這種情況的應用。我們講一 個人員的帳戶名和這人員的信息作了一個映射關系,也就是說,我們把帳戶名和人員信息當 成了一個“鍵值對”,“鍵”就是帳戶名,“值”就是人員信息。下面我們先看看Map接口的常用方法。

    1.4.2 常用方法

    Map 接口不是 Collection 接口的繼承。而是從自己的用于維護鍵-值關聯的接口層次結
    構入手。按定義,該接口描述了從不重復的鍵到值的映射。

    我們可以把這個接口方法分成三組操作:改變、查詢和提供可選視圖。

    改變操作允許您從映射中添加和除去鍵-值對。鍵和值都可以為null。但是,您不能把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)
    ? int size ():返回Map中鍵-值對的個數
    ? boolean isEmpty():判斷當前 Map 是否為空

    最后一組方法允許您把鍵或值的組作為集合來處理:

    ? public Set keySet ():返回所有的鍵(key),并使用Set容器存放
    ? public Collection values ():返回所有的值(Value),并使用 Collection 存放
    ? public Set entrySet() :返回一個實現 Map.Entry 接口的元素 Set, 因為映射中鍵的集合必須是唯一的,就使用 Set 來支持。因為映射中值的集合可能不唯一, 就使用 Collection 來支持。最后一個方法返回一個實現 Map.Entry 接口的元素 Set。
    我們看看 Map 的常用實現類的比較,如下表:

    下面我們看一個簡單的例子:

    import java.util.*; public class MapTest { public static void main(String[] args) { Map map1 = new HashMap(); Map map2 = new HashMap(); map1.put("1","aaa1"); map1.put("2","bbb2"); map2.put("10","aaaa10"); map2.put("11","bbbb11"); // 根據鍵"1"取得值:"aaa1" System.out.println("map1.get(\"1\")="+map1.get("1")); //根據鍵"1"移除鍵值對"1"-"aaa1" System.out.println("map1.remove (\"1\") =" + map1.remove ("1")); System.out.println("map1.get(\"1\")=" + map1.get("1")); map1.putAll(map2); //將map2全部元素放入map1中 map2.clear(); // 清空map2 System.out.println("map1 IsEmpty?=" + map1.isEmpty()); System.out.println("map2 IsEmpty?=" + map2.isEmpty()); System.out.println("map1中的鍵值對的個數size = " + map1.size()); System.out.println("KeySet=" + map1.keySet()); // set System.out.println("values=" + map1.values()); // Collection System.out.println("entrySet=" + map1.entrySet()); System.out.println("map1是否包含鍵:11 = " + map1.containsKey("11")); System.out.println ( "map1是否包含值:aaa1 = " + map1.containsValue("aaa1")); } } 運行輸出結果為: map1.get("1")=aaa1 map1.remove("1")=aaa1 map1.get("1")=null map1 IsEmpty?=false map2 IsEmpty?=true map1中的鍵值對的個數size = 3 KeySet=[10, 2, 11] values=[aaaa10, bbb2, bbbb11] entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11] map1 是否包含鍵:11 = true map1 是否包含值: aaa1 = false

    在該例子中,我們創建一個HashMap,并使用了一下Map接口中的各個方法。
    其中 Map 中的 entrySet() 方法先提一下,該方法返回一個實現 Map.Entry 接口的對象集合。集合中每個對象都是底層 Map 中一個特定的鍵-值對。

    Map.Entry接口是Map接口中的一個內部接口,該內部接口的實現類存放的是鍵值對。 在下面的實現原理中,我們會對這方面再作介紹,現在我們先不管它的具體實現。 我們再看看排序的 Map 是如何使用:

    import java.util.*; public class MapSortExample { public static void main(String args[]) { Map map1 = new HashMap(); Map map2 = new LinkedHashMap(); for (int i = 0; i < 10; i++) { double s = Math.random() * 100; // 產生一個隨機數,并將其放入Map中 map1.put(new Integer((int)s), "第" + i + "個放入的元素:" + s + "\n"); map2.put(new Integer((int)s), "第" + i + "個放入的元素:" + s + "\n"); } System.out.println("未排序前HashMap:" + map1); System.out.println("未排序前LinkedHashMap: " + map2); // 使用TreeMap來對另外的Map進行重構和排序 Map sortedMap = new TreeMap(map1); System.out.println("排序后:" + sortedMap); System.out.println("排序后:" + new TreeMap (map2)); } } 該程序的一次運行結果為: 未排序前HashMap:{ 64=第 1個放入的元素:64.05341725531845, 15=第 9 個放入的元素:15.249165766266382, 2=第 4 個放入的元素:2.66794706854534, 77=第 0 個放入的元素:77.28814965781416, 97=第 5 個放入的元素:97.32893518378948, 99=第 2 個放入的元素:99.99412014935982, 60=第 8 個放入的元素:60.91451419025399, 6=第 3 個放入的元素:6.286974058646977, 1=第 7 個放入的元素:1.8261658496439903, 48=第 6 個放入的元素:48.736039522423106 } 未排序前LinkedHashMap: { 77=第 0 個放入的元素:77.28814965781416, 64=第 1 個放入的元素:64.05341725531845, 99=第 2 個放入的元素:99.99412014935982, 6=第 3 個放入的元素:6.286974058646977, 2=第 4 個放入的元素:2.66794706854534, 97=第 5 個放入的元素:97.32893518378948, 48=第 6 個放入的元素:48.736039522423106, 1=第 7 個放入的元素:1.8261658496439903, 60=第 8 個放入的元素:60.91451419025399, 15=第 9 個放入的元素:15.249165766266382 } 排序后:{ 1=第7個放入的元素:1.8261658496439903, 2=第 4 個放入的元素:2.66794706854534, 6=第 3 個放入的元素:6.286974058646977, 15=第 9 個放入的元素:15.249165766266382, 48=第 6 個放入的元素:48.736039522423106, 60=第 8 個放入的元素:60.91451419025399, 64=第 1 個放入的元素:64.05341725531845, 77=第 0 個放入的元素:77.28814965781416, 97=第 5 個放入的元素:97.32893518378948, 99=第 2 個放入的元素:99.99412014935982 } 排序后:{ 1=第7個放入的元素:1.8261658496439903, 2=第 4 個放入的元素:2.66794706854534, 6=第 3 個放入的元素:6.286974058646977, 15=第 9 個放入的元素:15.249165766266382, 48=第 6 個放入的元素:48.736039522423106, 60=第 8 個放入的元素:60.91451419025399, 64=第 1 個放入的元素:64.05341725531845, 77=第 0 個放入的元素:77.28814965781416, 97=第 5 個放入的元素:97.32893518378948, 99=第 2 個放入的元素:99.99412014935982 }

    從運行結果,我們可以看出,HashMap的存入順序和輸出順序無關。而LinkedHashMap 則保留了鍵值對的存入順序。TreeMap則是對Map中的元素進行排序。在實際的使用中我 們也經常這樣做:使用HashMap或者LinkedHashMap來存放元素,當所有的元素都存放完成后,如果使用是需要一個經過排序的 Map 的話,我們再使用 TreeMap 來重構原來的 Map對象。這樣做的好處是:因為HashMap和LinkedHashMap存儲數據的速度比直接使 用TreeMap 要快,存取效率要高。當完成了所有的元素的存放后,我們再對整個的 Map中的元素進行排序。這樣可以提高整個程序的運行的效率,縮短執行時間。
    這里需要注意的是,TreeMap中是根據鍵(Key)進行排序的。而如果我們要使用TreeMap 來進行正常的排序的話,Key中存放的對象必須實現Comparable接口。

    1.4.3 Comparable 接口

    在 java.lang 包中, Comparable 接口適用于一個類有自然順序的時候。假定對象集合 是同一類型,該接口允許您把集合排序成自然順序。

    它只有一個方法: compareTo() 方法,用來比較當前實例和作為參數傳入的元素。 如果排序過程中當前實例出現在參數前(當前實例比參數大),就返回某個負值。如果當前 實例出現在參數后(當前實例比參數小),則返回正值。否則,返回零。如果這里不要求零返回值表示元素相等。零返回值可以只是表示兩個對象在排序的時候排在同一個位置。

    上面例子中的整形的包裝類: Integer 就實現了該接口。我們可以看一下這個類的源碼:

    public final class Integer extends Number implements Comparable { public int compareTo(Object o) { return compareTo((Integer)o); }public int compareTo(Integer anotherInteger) { int thisVal = this.value; int anotherVal = anotherInteger.value; return (thisVal < anotherVal ? -1 : (thisVal == anotherVal ? 0 : 1)); }

    可以看到 compareTo 方法里面通過判斷當前的 Integer 對象的值是否大于傳入的參數的值來得到返回值的。

    在Java 2 SDK,版本1.2中有十四個類實現Comparable接口。下表展示了它們的自 然排序。雖然一些類共享同一種自然排序,但只有相互可比的類才能排序。


    這里只是簡單的介紹一下排序接口,如果要詳細的了解排序部分內容的話,可以參考文章最 后的附錄部分對于排序的更加詳細的描述。

    我們再回到Map中來,Java提高的API中除了上面介紹的幾種Map比較常用以為還有一些Map,大家可以了解一下:
    ? WeakHashMap: WeakHashMap 是 Map 的一個特殊實現,它只用于存儲對鍵的弱引用。當映射的某個鍵在 WeakHashMap 的外部不再被引用時,就允許垃圾收集器收集映射中相應的鍵值對。使用 WeakHashMap 有益于保持類似注冊表的數據結構,其中條目的鍵不再被任何線程訪問時,此條目就沒用了。
    ? IdentifyHashMap : Map 的一種特性實現, 關鍵屬性的 hash 碼不是由hashCode() 方法計算,而是由 System.identityHashCode 方法計算,使用 == 進行比較而不是 equals() 方法。

    通過簡單的對Map中各個常用實現類的使用,為了更好的理解Map,下面我們再來 了解一下 Map 的實現原理。

    1.4.4 實現原理

    有的人可能會認為Map會繼承Collection。在數學中,映射只是對(pair)的集合。但是,在“集合框架”中,接口 Map 和 Collection 在層次結構沒有任何親緣關系,它們是截然不同的。這種差別的原因與 Set 和 Map 在 Java 庫中使用的方法有關。 Map 的典型應用是訪問按關鍵字存儲的值。它支持一系列集合操作的全部,但操作的是鍵-值對, 而不是單個獨立的元素。因此 Map 需要支持 get() 和 put() 的基本操作,而 Set 不 需要。此外,還有返回 Map 對象的 Set 視圖的方法:

    Set set = aMap.keySet();

    下面我們以HashMap為例,對Map的實現機制作一下更加深入一點的理解。

    因為HashMap里面使用Hash算法,所以在理解HashMap之前,我們需要先了解一下Hash 算法和 Hash 表。

    Hash,一般翻譯做“散列”,也有直接音譯為"哈希"的,就是把任意長度的輸入(又叫做 預映射,pre-image),通過散列算法,變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小于輸入的空間,不同的輸入可能會散列成相同的輸出,而不可能從散列值來唯一的確定輸入值。

    說的通俗一點,Hash算法的意義在于提供了一種快速存取數據的方法,它用一種算法建 立鍵值與真實值之間的對應關系,(每一個真實值只能有一個鍵值,但是一個鍵值可以對應多 個真實值),這樣可以快速在數組等里面存取數據。

    看下圖:

    我們建立一個HashTable (哈希表),該表的長度為N,然后我們分別在該表中的格子中存放 不同的元素。每個格子下面存放的元素又是以鏈表的方式存放元素。
    ? 當添加一個新的元素Entry的時候,首先我們通過一個Hash函數計算出這個Entry元 素的Hash值hashcode。通過該hashcode值,就可以直接定位出我們應該把這個Entry 元素存入到Hash表的哪個格子中,如果該格子中己經存在元素了,那么只要把新的 Entry 元存放到這個鏈表中即可。
    ? 如果要查找一個元素Entry的時候,也同樣的方式,通過Hash函數計算出這個Entry 元素的Hash值hashcode。然后通過該hashcode值,就可以直接找到這個Entry是存放到哪個格子中的。接下來就對該格子存放的鏈表元素進行逐個的比較查找就可以了。
    舉一個比較簡單的例子來說明這個算法的運算方式:
    假定我們有一個長度為8的Hash表(可以理解為一個長度為8的數組)。在這個Hash表中 存放數字:如下表

    假定我們的Hash函數為:
    Hashcode = X%8 ,即對 8 取余數。
    其中X就是我們需要放入Hash表中的數字,而這個函數返回的Hashcode就是Hash碼。
    假定我們有下面10個數字需要依次存入到這個Hash表中:
    11, 23, 44, 9, 6, 32, 12, 45, 57, 89
    通過上面的Hash函數,我們可以得到分別對應的Hash碼:
    11 -- 3; 23 -- 7;44 -- 4;9 -- 1;6 -- 6;32 -- 0;12 -- 4;45 -- 5;57 -- 1;89 -- 1;
    計算出來的Hash碼分別代表該數字應該存放到Hash表中的哪個對應數字的格子中。如果改格子中已經有數字存在了,那么就以鏈表的方式將數字依次存放在該格子中,如下表:

    Hash 表和 Hash 算法的特點就是它的存取速度比數組差一些,但是比起單純的鏈表,在查找和存儲方面卻要好很多。同時數組也不利于數據的重構和排序等方面的要求。
    更具體的說明,讀者可以參考數據結構相關方面的書籍。

    簡單的了解了一下Hash算法后,我們就來看看HashMap的屬性有哪些:

    public class HashMap extends AbstractMap implements Map, Cloneable, Serializable {/* * 哈希表,Entry對象中存放的是犍值對。并且該數組的長度為2的次方 * The table, resized as necessary. Length MUST Always be a power of two. */ transient Entry[] table; /* * 鍵值對的個數 * The number of key-value mappings contained in this identity hash map? */ transient int size; /* * The load factor for the hash table. * 哈希表的負載因子 * @serial * / final float loadFactor;

    里面最重要的3個屬性:

  • transient Entry [] table:用來存放鍵值對的對象Entry數組,也就是Hash 表
  • transient int size:當前Map中存放的鍵值對的個數
  • final float loadFactor:負載因子,用來決定什么情況下應該對Entry進行擴容
  • Entry對象是Map接口中的一個內部接口。即使用它來保存鍵值對。 我們看看這個Entry內部接口在HashMap中的實現:

    static class Entry implements Map.Entry { final Object key; // 鍵,并且不可修改 Object value; // 值 final int hash; // hash碼 Entry next; // 當前鍵值對的下一個鍵值對 Entry (int h, Object k, Object v, Entry n) { value = v; next =n; key = k; hash = h; }

    通過查看源碼,我們可以看到Entry類有點類似一個單向鏈表。其中:
    final Object key 和 Object value存放的就是我們放入Map中的鍵值對。 而屬性Entry next表示當前鍵值對的下一個鍵值對。
    接下來,我們看看 HashMap 的主要的構造函數:

    public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // 默認負載因子為0.75threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);table = new Entry[DEFAULT_INITIAL_CAPACITY]; // 默認初始化hash表長度為16init();} public HashMap(int initialCapacity,float loadFactor) {if (initialCapacity < 0)throw new IllegalArgumentException("Illegal initial capacity:" + initialCapacity);if (initialCapacity > MAXIMUM_CAPACITY)initialCapacity = MAXIMUM_CAPACITY;if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor:" + loadFactor);// Find a power of 2 >= initialCapacity// 找出第一個大于initialCapacity的2次方的數為初始化hash表的長度int capacity = 1;while (capacity < initialCapacity)capacity <<= 1;// 負載因子this.loadFactor = loadFactor;// threshold表示hash表什么長度的時候需要重構threshold = (int)(capacity * loadFactor);table = new Entry[capacity]; // 構建Entry數組作為hash表init();}public HashMap(int initialCapacity) {// 默認負載因子為0.75this(initialCapacity,DEFAULT_LOAD_FACTOR);}

    我們主要看看 public HashMap (int initialcapacity, float loadFactor) 因為,另外兩個構造函數實際也是同樣的方式進行構建一個HashMap的。 該構造函數:

  • 首先是判斷參數int initialCapacity和float loadFactor是否合法
  • 然后確定Hash表的初始化長度。確定的策略是:通過傳進來的參數 initialCapacity來找出第一個大于它的2的次方的數。比如說我們傳了 18 這樣的一個initialCapacity參數,那么真實的table數組的長度為2的5 次方,即32。之所以采用這種策略來構建Hash表的長度,是因為2的次方的運算對于現代的處理器來說,可以通過一些方法得到更加好的執行效率
  • 接下來就是得到重構因子(threshold) 了,這個屬性也是HashMap中的一個比較重要的屬性,它表示,當Hash表中的元素被存放了多少個之后,我們就需要對該 Hash 表進行重構。
  • 最后就是使用得到的初始化參數capacity來構建Hash表:Entry[ ] table。 下面我們看看一個鍵值對是如何添加到 HashMap 中的。
  • public Object put(Object key, Object value) { // 如果key為null則使用一個常量來代替該key值 Object k = maskNull(key); int hash = hash(k); // 計算key值的hash碼 // 通過使用hash碼來定位,我們應該把當前的鍵值對存放到hash表中的哪個格子中 int i = indexFor(hash, table.length); // 計算出為第i個格子 // 遍歷當前的Hash表table[i]格中的鏈表 for (Entry e = table[i]; e != null;e = e.next()) {// 判斷當前的Hash表table[i]格中的鏈表,是否已經存在相同的key的鍵值對if (e.hash == hash && eq(k,e.key)) {// 如果存在一樣的key,那么把新的value覆寫老的value,并把老的value返回Object oldValue = e.value;e.value = value;e.recordAccess(this); // 值(value)覆寫事件,HashMap中該方法沒有做任何處理return oldValue; } }modCount++; // 計數器+1 // 如果遍歷后發現沒有存在相同的鍵(key),那么就增加當前的鍵值對到hash表中 addEntry(hash,k,value,i); return null; }

    該put方法是用來添加一個鍵值對(key-value)到Map中,如果Map中己經存在相同的 鍵的鍵值對的話,那么就把新的值覆蓋老的值,并把老的值返回給方法的調用者。如果不存在一樣的鍵,那么就返回 null 。我們看看方法的具體實現:

  • 首先我們判斷如果key為null則使用一個常量來代替該key值,該行為在方法maskNull()中將key替換為一個非null的對象k。
  • 計算 key 值的 Hash 碼:hash
  • 通過使用Hash碼來定位,我們應該把當前的鍵值對存放到Hash表中的哪個格子中。 indexFor()方法計算出的結果i就是Hash表(table)中的下標。
  • 然后遍歷當前的Hash表中table[i]格中的鏈表。從中判斷是否存在一樣的鍵(Key) 的鍵值對。如果存在一樣的key,那么就用新的value覆寫老的value,并把老的value 返回
  • 如果遍歷后發現不存在同樣的鍵,那么就增加當前鍵值對到Hash表中的第i個格子中的鏈表中。并返回 null。
  • 最后我們看看一個鍵值對是如何添加到各個格子中的鏈表中的:

    void addEntry(int hash, Object key, Object value, int bucketIndex) { // 創建一個Entry對象用來存放鍵值對 // 并把原來的格子中鏈表的第一位置的元素作為當前Entry對象的下一個元素 // 換句話說,就是把當前創建的Entry對象,加到鏈表的第一個位置,其它的掛到它的后面 table[bucketIndex] = new Entry(hash, k, value, table[bucketIndex]); // 如果存放元素的個數大于重構因子threshold,那么就進行重構 if (size++ >= threshold) {resize(2 * table.length); }

    我們先看void addEntry(int hash, Obj ect key, Obj ect value, int bucketIndex)方法,該方法的作用就用來添加一個鍵值對到Hash表的第bucketIndex 個格子中的鏈表中去。這個方法的工作就是:

  • 創建一個 Entry 對象用來存放鍵值對
  • 添加該鍵值對 Entry 對象到鏈表中
  • 最后在size屬性+1,并判斷是否需要對當前的Hash表進行重構。如果需要就在 void resize (int newCapacity)方法中進行重構。
  • 之所以需要重構,也是基于性能考慮。大家可以考慮這樣一種情況,假定我們的Hash 表只有4個格子,那么我們所有的數據都是放到這4個格子中。如果存儲的數據量比較大 的話,例如 100。這個時候,我們就會發現,在這個 Hash 表中的 4 個格子存放的 4 個長長的鏈表。而我們每次查找元素的時候,其實相當于就是遍歷鏈表了。這種情況下,我們用 這個Hash表來存取數據的性能實際上和使用鏈表差不多了。

    但是如果我們對這個 Hash 表進行重構,換為使用 Hash 表長度為 200 的表來存儲這 100個數據,那么平均 2個格子里面才會存放一個數據。這個時候我們查找的數據的速度就會非常的快。因為基本上每個格子中存放的鏈表都不會很長,所以我們遍歷鏈表的次數也 就很少,這樣也就加快了查找速度。但是這個時候又存在了另外的一個問題。我們使用了至 少200個數據的空間來存放100個數據,這樣就造成至少100個數據空間的浪費。 在速 度和空間上面,我們需要找到一個適合自己的中間值。在HashMap中我們通過負載因子 (loadFactor)來決定應該什么時候重構我們的Hash 表,以達到比較好的性能狀態。
    我們再看看重構Hash表的方法:void resize( int newCapacity)是如何實現的:

    // 該方法用來重構Hash表 void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) {// 如果Hash表的長度已經達到最大值,那么就不進行重構了threshold = Integer.MAX_VALUE;return; } // 構建新的Hash表 Entry[] newTable = new Entry[newCapacity]; // 將所有老的Hash表中的元素都轉移存放到新的Hash表newTable中 transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor); }

    它的實現方式比較簡單:

  • 首先判斷如果Hash表的長度己經達到最大值,那么就不進行重構了。因為這個時候Hash表的長度己經達到上限,己經沒有必要重構了。
  • 然后就是構建新的 Hash 表
  • 把老的Hash表中的對象數據全部轉移到新的Hash表newTable中,并設置新的重構因子threshold
  • 對于HashMap中的實現原理,我們就分析到這里。大家可能會發現,HashCode的計算, 是用來定位我們的鍵值對應該放到 Hash 表中哪個格子中的關鍵屬性。 而這個 HashCode的計算方法是調用的各個對象自己的實現的hashCode()方法。而這個方法是 在Object對象中定義的,所以我們自己定義的類如果要在集合中使用的話,就需要正確 的覆寫 hashCode() 方法。下面就介紹一下應該如何正確覆寫 hashCode() 方法。

    1.4.5 覆寫 hashCode()

    在明白了 HashMap具有哪些功能,以及實現原理后,了解如何寫一個hashCode () 方法就更有意義了。當然,在HashMap中存取一個鍵值對涉及到的另外一個方法為equals (),因為該方法的覆寫在高級特性已經講解了。這里就不做過多的描述。
    設計hashCode ()時最重要的因素就是:無論何時,對同一個對象調用hashCode () 都應該生成同樣的值。如果在將一個對象用 put() 方法添加進 HashMap 時產生一個 hashCode ()值,而用get ()取出時卻產生了另外一個hashCode ()值,那么就無法重新 取得該對象了。所以,如果你的hashCode()方法依賴于對象中易變的數據,那用戶就要 小心了,因為此數據發生變化時, hashCode() 就會產生一個不同的 hash 碼,相當于產生 了一個不同的“鍵”。
    此外,也不應該使 hashCode() 依賴于具有唯一性的對象信息,尤其是使用 this 的 值,這只能產生很糟糕的hashCode()。因為這樣做無法生成一個新的“鍵",使之與put() 種原始的"鍵值對”中的“鍵”相同。例如,如果我們不覆寫Object的hashCode()方 法,那么調用該方法的時候,就會調用Object的hashCode ()方法的默認實現。Object 的hashCode ()方法,返回的是當前對象的內存地址。下次如果我們需要取一個一樣的“鍵” 對應的鍵值對的時候,我們就無法得到一樣的 hashCode 值了。因為我們后來創建的“鍵” 對象己經不是存入HashMap中的那個內存地址的對象了。
    我們看一個簡單的例子,就能更加清楚的理解上面的意思。假定我們寫了一個類: Person (人),我們判斷一個對象“人”是否指向同一個人,只要知道這個人的身份證 號一直就可以了。
    先看我們沒有實現 hashCode 的情況:

    package c08.hashEx; import java.util.*; //身份證類 class Code{ final int id;//身份證號碼已經確認,不能改變 Code(int i ) { id=i; } //身份號碼相同,則身份證相同 public boolean equals(Object anObject) { if (anObject instanceof Code) { Code other = (Code)anObject; return this.id == other.id; } return false; }public String toString() { return "身份證:"+id; } }//人員信息類 class Person { Code id; //身份證 String name; // 姓名 public Person(String name, Code id) { this.id = id; this.name = name; } //如果身份證號相同,就表示兩個人是同一個人 public boolean equals(Object anObject) { if (anObject instanceof Person){ Person other=(Person) anObject; return this.id.equals(other.id); } return false; } public String toString() { return "姓名:" + name + "身份證:" + id.id + "\n"; } } public class HashCodeEx { public static void main(String[] args) { HashMap map = new HashMap(); Person p1 = new Person("張三",new Code (123)); map.put(p1.id,p1) ;//我們根據身份證來作為key值存放到Map中 Person p2 = new Person("李四", new Code(456)); map.put(p2.id,p2); Person p3 = new Person("王二"new Code (789)); map.put(p3.id,p3); System. out .println("HashMap中存放的人員信息:\n"+map); //張三改名為:張山 但是還是同一個人。 Person p4 = new Person ("張山",new Code (123)); map.put(p4.id,p4); System.out.println("張三改名后HashMap中存放的人員信息:\n"+map); //查找身份證為:123的人員信息 System.out.println ("查找身份證為:123的人員信息:"+map.get(new Code(123))); } } 運行結果為: HashMap中存放的人員信息: {身份證:456=姓名:李四 身份證:456 ,身份證:123=姓名:張三 身份證:123 ,身份證:789=姓名:王二 身份證:789 } 張三改名后HashMap中存放的人員信息: {身份證:456=姓名:李四 身份證:456 ,身份證:123=姓名:張三 身份證:123 ,身份證:123=姓名:張山 身份證:123 ,身份證:789=姓名:王二 身份證:789 } 查找身份證為:123的人員信息:null

    上面的例子的演示的是,我們在一個HashMap中存放了一些人員的信息。并以這些人 員的身份證最為人員的“鍵”。當有的人員的姓名修改了的情況下,我們需要更新這個 HashMap。同時假如我們知道某個身份證號,想了解這個身份證號對應的人員信息如何, 我們也可以根據這個身份證號在HashMap中得到對應的信息。

    而例子的輸出結果表示,我們所做的更新和查找操作都失敗了。失敗的原因就是我們的 身份證類: Code 沒有覆寫 hashCode() 方法。這個時候,當查找一樣的身份證號碼的鍵 值對的時候,使用的是默認的對象的內存地址來進行定位。這樣,后面的所有的身份證號對 象new Code (123)產生的hashCode ()值都是不一樣的。所以導致操作失敗。
    下面,我們給Code類加上hashCode ()方法,然后再運行一下程序看看:

    //身份證類 class Code{ final int id;//身份證號碼已經確認,不能改變 Code(int i ) { id=i; } //身份號碼相同,則身份證相同 public boolean equals(Obj ect anObject) { if (anObject instanceof Code) { Code other = (Code)anObject; return this.id == other.id; } return false; } public String toString() { return "身份證:"+id; } //覆寫hashCode方法,并使用身份證號作為hash值 public int hashCode(){ return id; } }

    再次執行上面的HashCodeEx的結果就為:

    HashMap中存放的人員信息: {身份證:456=姓名:李四 身份證:456 ,身份證:789=姓名:王二 身份證:789 ,身份證:123=姓名:張三 身份證:123 } 張三改名后HashMap中存放的人員信息: {身份證:456=姓名:李四 身份證:456 ,身份證:789=姓名:王二 身份證:789 ,身份證:123=姓名:張山 身份證:123 } 查找身份證為:123的人員信息:姓名:張山身份證:123

    這個時候,我們發現。我們想要做的更新和查找操作都成功了。

    對于Map部分的使用和實現,主要就是需要注意存放“鍵值對”中的對象的equals () 方法和 hashCode() 方法的覆寫。如果需要使用到排序的話,那么還需要實現 Comparable 接口中的compareTo ()方法。我們需要注意Map中的“鍵”是不能重復的,而是否重復 的判斷,是通過調用“鍵”對象的equals ()方法來決定的。而在HashMap中查找和存 取"鍵值對”是同時使用hashCode ()方法和equals ()方法來決定的。

    1.5 Set

    1.5.1 概述

    Java中的Set和正好和數學上直觀的築(set)的概念是相同的。Set最大的特性就是 不允許在其中存放的元素是重復的。根據這個特點,我們就可以使用Set這個接口來實現 前面提到的關于商品種類的存儲需求。 Set 可以被用來過濾在其他集合中存放的元素,從 而得到一個沒有包含重復新的集合。

    1.5.2 常用方法

    按照定義, Set 接口繼承 Collection 接口,而且它不允許集合中存在重復項。所 有原始方法都是現成的,沒有引入新方法。具體的 Set 實現類依賴添加的對象的 equals() 方法來檢查等同性。

    我們簡單的描述一下各個方法的作用:
    ? public int size():返回set中元素的數目,如果 set 包含的元素數大于 Integer.MAX_VALUE,返回 Integer.MAX_VALUE
    ? public boolean isEmpty() :如果 set 中不含元素,返回 true
    ? 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 booleanremove(Objecto):如果set中存在指定元素,則從set中刪除
    ? public boolean removeAll(Collection c):如果 set 包含指定集合,則從 set 中刪除指 定集合的所有元素
    ? public boolean containsAll(Collection c):如果 set 包含指定集合的所有元素,返回 true。如果指定集合也是一個set,只有是當前set的子集時,方法返回true
    ? public boolean addAll(Collection c):如果 set 中中不存在指定集合的元素,則向 set 中加入所有元素
    ? public boolean retainAll(Collection c):只保留 set 中所含的指定集合的元素(可選操 作)。換言之,從set中刪除所有指定集合不包含的元素。如果指定集合也是一個 set,那么該操作修改set的效果是使它的值為兩個set的交集
    ? public boolean removeAll(Collection c):如果 set 包含指定集合,則從 set 中刪除指 定集合的所有元素
    ? public void clear():從 set 中刪除所有元素

    “集合框架” 支持 Set 接口兩種普通的實現:
    HashSet 和 TreeSet 以及 LinkedHashSet。 下表中是Set的常用實現類的描述:

    在更多情況下,您會使用 HashSet 存儲重復自由的集合。同時 HashSet 中也是采用 了 Hash 算法的方式進行存取對象元素的。所以添加到 HashSet 的對象對應的類也需要 采用恰當方式來實現 hashCode() 方法。雖然大多數系統類覆蓋了 Object 中缺省的 hashCode() 實現, 但創建您自己的要添加到 HashSet 的類時, 別忘了覆蓋 hashCode() 。

    對于Set的使用,我們先以一個簡單的例子來說明:

    import java.util.*; public class HashSetDemo { public static void main(String[] args) { Set set1 = new HashSet(); if (set1.add("a")) {//添加成功 System.out.println("1 add true"); } if (set1.add("a")) {//添加失敗 System.out.println("2 add true"); } set1.add("000") ; //添加對象到Set集合中 set1.add("111"); set1.add("222"); System.out.println("集合set1的大小: " + set1.size()); System.out.println("集合set1 的內容:" + set1); set1.remove("000") ;//從集合set1中移除掉 "000" 這個對象 System.out.println("集合set1 移除 000 后的內容:" + set1); System.out.println("集合set1中是否包含000 :" + set1.contains("000")); System.out.println ("集合set1中是否包含 111 :" + set1.contains("111")); Set set2 = new HashSet(); set2.add("111"); set2.addAll(set1); // 將set1集合中的元素全部都加到set2中 System.out.println("集合set2的內容:"+set2 ); set2.clear(); // 清空集合set1中的元素 System.out.println("集合set2是否為空:" + set2.isEmpty()); Iterator iterator = set1.iterator(); // 得到一個迭代器 while (iterator. hasNext()) { Object element = iterator.next(); System.out.println("iterator = " + element); } // 將集合set1轉化為數組 Obj ect s[]= set].toArray(); for(int i=0;i<s.length;i++){ System.out.println(s[i]); } } }

    程序執行的結果為:

    1 add true 集合set]的大小:4 集合 set]的內容:[222, a, 000, ]]]] 集合set]移除000后的內容:[222, a, ]]]] 集合set]中是否包含000 : false 集合set]中是否包含]]]:true 集合 set2 的內容:[222, a, ]]]] 集合set2是否為空:true iterator = 222 iterator = a iterator =]]] 222 a 111

    從上面的這個簡單的例子中,我們可以發現,Set中的方法與直接使用Collection中的 方法一樣。唯一需要注意的就是Set中存放的元素不能重復。

    我們再看一個例子,來了解一下其它的Set的實現類的特性:

    package c08; import java.util.*; public class SetSortExample { public static void main(String args[]) { Set set1 = new HashSet(); Set set2 = new LinkedHashSet(); for (int i = 0; i < 5; i++) { //產生一個隨機數,并將其放入Set中 int s = (int)(Math.random() * 100); set1.add(new Integer(s)); set2.add(new Integer(s)); System.out.println ("第 " + i + " 次隨機數產生為:" + s); } System.out.println("未排序前HashSet : " + set1); System.out.println ("未排序前LinkedHashSet: " + set2); // 使用TreeSet來對另外的Set進行重構和排序 Set sortedSet = new TreeSet(set1); System.out.println("排序后TreeSet : " + sortedSet); } }

    該程序的一次執行結果為:

    第0次隨機數產生為:96 第1次隨機數產生為:64 第2次隨機數產生為:14 第3次隨機數產生為:95 第4次隨機數產生為:57 未排序前 HashSet:[64, 96, 95, 57, 14] 未排序前LinkedHashSet:[96, 64, 14, 95, 57 ] 排序后TreeSet:[14, 57, 64, 95, 96]

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

    一般來說,當您要從集合中以有序的方式抽取元素時, TreeSet 實現就會有用處。為 了能順利進行,添加到 TreeSet 的元素必須是可排序的。 而您同樣需要對添加到 TreeSet 中的類對象實現 Comparable 接口的支持。對于 Comparable 接口的實現,在 前一小節的Map中己經簡單的介紹了一下。我們暫且假定一棵樹知道如何保持java.lang 包裝程序器類元素的有序狀態。一般說來,先把元素添加到HashSet,再把集合轉換為 TreeSet 來進行有序遍歷會更快。這點和 HashMap 的使用非常的類似。

    其實Set的實現原理是基于Map上面的。通過下面我們對Set的進一步分析大家就能 更加清楚的了解這點了。

    1.5.3 實現原理

    Java中Set的概念和數學中的集合(set)一致,都表示一個集內可以存放的元素是不能重 復的。
    前面我們會發現,Set中很多實現類和Map中的一些實現類的使用上非常的相似。而且 前面再講解Map的時候,我們也提到:Map中的“鍵值對”,其中的“鍵”是不能重復的。 這個和Set中的元素不能重復一致。我們以HashSet為例來分析一下,會發現其實Set利用 的就是Map中“鍵”不能重復的特性來實現的。
    先看看HashSet中有哪些屬性:

    public class HashSet extends AbstractSet implements Set, Cloneable, java.io.Serializable {static final long serialVersionUID = -5024744406713321676L;// 核心屬性:HashMapprivate transient HashMap map;// 常量:present用來所有Map中“鍵值對”的“值”// Dummy value to associate with an Object in the backing Mapprivate static final Object PRESENT = new Object();

    再結合構造函數來看看:

    /* * 構造函數就是對HashMap的構建 * Constructs a new, empty set;the backing <tt>HashMap</tt> * default initial capacity(16) and load factor(0.75). */public HashSet() {map = new HashMap(); }public HashSet(int initialCapacity, float loadFactor) {map = new HashMap(initialCapacity,loadFactor);}public HashSet(int initialCapacity) {map = new HashMap(initialCapacity); }

    通過這些方法,我們可以發現,其實HashSet的實現,全部的操作都是基于HashMap來進行的。我們看看是如何通過HashMap來保證我們的HashSet的元素不重復性的:

    /* * Adds the specified element to this set if it is not already contain the specified element. * */ public boolean add(Object o) {return map.put(o,PRESENT) == null; }

    看到這個操作我們可以發現HashSet的巧妙實現:就是建立一個“鍵值對”,“鍵”就是 我們要存入的對象,“值”則是一個常量。這樣可以確保,我們所需要的存儲的信息之是“鍵”。 而“鍵”在Map中是不能重復的,這就保證了我們存入Set中的所有的元素都不重復。而 判斷是否添加元素成功,則是通過判斷我們向Map中存入的“鍵值對”是否己經存在,如 果存在的話,那么返回值肯定是常量: PRESENT,表示添加失敗。如果不存在,返回值就 為null `表示添加成功。

    我們再看看其他的方法實現:

    /* * Removes the specified element from this set if it is present * 移出操作即是對Map中的鍵值對進行移出,如果返回值為PRESENT常量,則表示移出成功 * @param o object to be removed from this set, if present. * @return <tt>true</tt> if the set contained the specified element. * */public boolean remove(Object o) {return map.remove(o) == PRESENT; }public int size() {return map.size(); }public boolean contains(Object o) {return map.containsKey(o); }public boolean isEmpty() {return map.isEmpty(); }public Iterator iterator() {return map.keySet().iterator(); }

    了解了這些后,我們就不難理解,為什么HashMap中需要注意的地方,在HashSet中 也同樣的需要注意。其他的Set的實現類也是差不多的原理。
    至此對于Set我們就應該能夠比較好的理解了。

    1.6 總結:集合框架中常用類比較

    用“集合框架”設計軟件時,記住該框架四個基本接口的下列層次結構關系會有用處:

    • Collection接口是一組允許重復的對象。
    • Set接口繼承Collection,但不允許重復。
    • List接口繼承Collection,允許重復,并引入位置下標。
    • Map 接口既不繼承 Set 也不繼承 Collection, 存取的是鍵值對

    我們以下面這個圖表來描述一下常用的集合的實現類之間的區別:

    2 練習

  • 撰寫一個Person class,表示一個人員的信息。令該類具備多輛Car的信息,表示一個人可以擁有的車子的數據
  • 屬性/方法:
    Certificate code: 身份證對象
    name: 姓名
    cash: 現金
    List car: 擁有的汽車,其中存放的是Car對象
    boolean buycar(car):買車子
    boolean sellcar(Person p):把自己全部的車子賣給別人
    boolean buyCar(Car car,Person p):自動查找賣車的人p是否有買主想要買的車car,如果有就買,并返回true, 否則返回false
    viod addCar(car):把某輛車送給方法的調用者。
    String toString():得到人的信息

  • 并撰寫第二個 Car class
  • 屬性/方法:
    String ID:ID 車牌號
    cost:價格
    color:顏色
    Person owner:車子的擁有者
    to String():得到汽車的信息
    equals():比較車子是否同一倆汽車,ID相同則認為相同

  • 在另外一個Market類里面,進行車子的買賣。并保留所有交易人員的的信息到一個HashMap中,我們可以通過身份證號來查找到對應的人員的信息。同時所有的車子種類都在市場中進 行注冊,即車子的信息使用一個Set來保存
  • 屬性/方法:
    HashMap people:存放交易人員的信息。Key為身份證號,value為Person對象
    static boolean sellCar(Person p1 ,Car car1, Person p2):p1 將 car1 賣給 p2 。并在該方法中記錄效益人的信息到 people 中。

  • 撰寫類Certificate 表示身份證
  • 屬性/方法:

    Id:號碼
    equals():比較兩個身份證是否同一個,ID相同則認為相同
    hashCode():正確編寫 hashCode 方法

    場景:
    一個叫Bob的人:身份證:310 現金:30000。
    有一輛車子:ID:001,紅色,價格:50000的車子;
    一個叫 Tom 的人:身份證: 210 現金: 70000,
    有一輛車子:顏色:白色,ID:003,價格:25000。
    一個叫 King 的人:身份證: 245 現金: 60000,
    有2輛車子:顏色:白色,ID:005,價格:18000。
    顏色:紅色,ID:045,價格:58000。

    Tom買了 Bob的車子.他就擁有了 2輛汽車
    King 把 ID=005 的車子買給了 Bob
    最后各人的信息如何?

    3 附錄:排序

    為了用’'集合框架"的額外部分把排序支持添加到Java 2 SDK,版本1.2,核心Java庫 作了許多更改。像 String 和 Integer 類如今實現 Comparable 接口以提供自然排序 順序。對于那些沒有自然順序的類、或者當您想要一個不同于自然順序的順序時,您可以實 現 Comparator 接口來定義您自己的。
    為了利用排序功能, '集合框架"提供了兩種使用該功能的接口: SortedSet 和 SortedMap 。

    Comparable 接口
    在 java.lang 包中, Comparable 接口適用于一個類有自然順序的時候。假定對象集合 是同一類型,該接口允許您把集合排序成自然順序。

    compareTo() 方法比較當前實例和作為參數傳入的元素。如果排序過程中當前實例出現在參數前,就返回某個負值。如果當前實例出現在參數后,則返回正值。否則,返回零。這 里不要求零返回值表示元素相等。零返回值只是表示兩個對象排在同一個位置。

    在Java2SDK,版本1.2中有十四個類實現Comparable接口。下表展示了它們的自 然排序。雖然一些類共享同一種自然排序,但只有相互可比的類才能排序。

    創建您自己的類 Comparable 只是個實現 compareTo() 方法的問題。通常就是依賴幾 個數據成員的自然排序。您自己的類也應該覆蓋 equals() 和 hashCode() 以確保兩個 相等的對象返回同一個散列碼。

    Comparator 接口

    若一個類不能用于實現java .lang .Comparable,您可以提供自己的 java.util.Comparator 行為。如果您不喜歡缺省的 Comparable 行為,您照樣可以 提供自己的 Comparator。

    Comparator 的 compare() 方法的返回值和 Comparable 的 compareTo() 方法的 返回值相似。在此情況下,如果排序時第一個元素出現在第二個元素之前,則返回一個負值。 如果第一個元素出現在后,那么返回一個正值。否則,返回零。與 Comparable 相似,零 返回值不表示元素相等。一個零返回值只是表示兩個對象排在同一位置。由 Comparator 用戶決定如何處理。如果兩個不相等的元素比較的結果為零,您首先應該確信那就是您要的結果,然后記錄行為。

    為了演示,您會發現編寫一個新的忽略大小寫的Comparator,代替使用Collator進行語言環境特定、忽略大小寫的比較會更容易。這樣的一種實現如下所示:

    class CaseInsensitiveComparator implements Comparator { public int compare(Object element1, Object element2) { String lowerE1 = ((String)element1).toLowerCase(); String lowerE2 = ((String)element2).toLowerCase(); return lowerE1.compareTo(lowerE2); } }

    因為每個類在某些地方都建立了 Object 子類,所以這不是您實現 equals() 方法的必 要條件。實際上大多數情況下您不會去這樣做。切記該equals() 方法檢查的是 Comparator 實現的等同性,不是處于比較狀態下的對象。

    Collections類有個預定義的 Comparator 用于重用。調用Collections.reverseOrder() 返回一個 Comparator, 它對逆序實現 Comparable 接口的對象進行排序。

    總結

    以上是生活随笔為你收集整理的Java集合类原理详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    国产h在线播放 | 中文字幕免费观看视频 | 国产黄色视 | 国产视频综合在线 | 1024手机在线看 | 手机在线黄色网址 | 日韩av二区 | www.色婷婷| 国产精品免费久久久久 | 久草在线最新免费 | 日韩视频a| 色综合天天 | 午夜精品麻豆 | 国产高清中文字幕 | 国产91大片 | 亚洲另类在线视频 | 男女视频久久久 | 日韩精选在线 | 人人爽人人爽 | 国产一区电影在线观看 | av免费电影在线观看 | 久久久精品国产一区二区三区 | 日韩一级片观看 | 日本狠狠色 | 日躁夜躁狠狠躁2001 | 久久黄色影视 | 日韩色视频在线观看 | 亚洲欧美国产日韩在线观看 | 天堂av色婷婷一区二区三区 | 精品一区精品二区 | 国产精品精品久久久 | av一区二区三区在线播放 | 欧美一二区视频 | 一本一本久久a久久 | 一区二区电影在线观看 | 天海冀一区二区三区 | aaa黄色毛片| 探花视频免费观看 | 黄www在线观看 | 在线观看精品一区 | 国产精品高 | 又黄又爽又湿又无遮挡的在线视频 | 久久草av| 日韩在线观看第一页 | 久久久免费 | 国产精品视频永久免费播放 | 不卡的av中文字幕 | 在线日韩av | 国产成人亚洲在线观看 | 狠狠躁夜夜躁人人爽视频 | 中文字幕五区 | 国产麻豆精品一区二区 | 最新99热| 亚洲一级黄色av | 免费特级黄色片 | 国产码电影 | 狠狠狠操 | 久久国产热 | 人人爽人人爽人人片av免 | 国产精品理论视频 | 国产精品久久一区二区三区不卡 | 日本99精品 | 97超碰人人模人人人爽人人爱 | 亚洲 欧美 国产 va在线影院 | 久久97久久 | 狠狠干夜夜操 | 亚洲精品自拍 | 天天射天天拍 | 日韩av女优视频 | 91成品视频| 亚洲aⅴ久久精品 | 成人中文字幕在线观看 | 国产xxxxx在线观看 | www.日日日.com | 久久精品一区二区三区中文字幕 | 久久免费激情视频 | 一区二区亚洲精品 | 精品人人人 | 国产91精品欧美 | 九九热av | 狠狠狠色丁香婷婷综合久久五月 | 中文字幕亚洲不卡 | 999精品网| 麻豆国产在线视频 | 韩国在线一区二区 | 中文字幕资源网在线观看 | 超薄丝袜一二三区 | 四虎影视成人永久免费观看亚洲欧美 | 欧美了一区在线观看 | 久久视频在线观看免费 | 午夜在线观看 | 久久免费电影网 | 国产精品麻豆视频 | 玖玖在线资源 | 免费黄色在线播放 | 国产美女精品视频 | 亚洲国产一区在线观看 | 五月天婷亚洲天综合网精品偷 | 国产黄色大片免费看 | av经典在线 | 91人人视频在线观看 | 香蕉免费 | av大全在线免费观看 | 六月丁香在线观看 | 一区二区三区韩国免费中文网站 | 精品一区二区在线免费观看 | 日韩免费不卡视频 | 激情综合亚洲精品 | 亚洲成人动漫在线观看 | www.色五月.com | 国产 欧美 在线 | 狠色在线| 人人射人人爱 | 色婷婷综合成人av | 久久国产精品久久国产精品 | 亚洲精品国产精品国自产 | 久久99国产精品二区护士 | 成人在线观看免费视频 | 国产精品一区二区精品视频免费看 | 激情视频网页 | 欧美a级在线播放 | 久久精品一区二区三区四区 | 四虎www.| 亚洲精品国偷自产在线91正片 | 精品国产三级 | 日韩在线 一区二区 | 日韩在线短视频 | 国产精品国产自产拍高清av | 亚洲日日射 | 91在线视频免费 | 在线观看亚洲成人 | 天天搞天天| 一区二区三区四区五区六区 | 婷婷精品国产欧美精品亚洲人人爽 | 9免费视频 | 91精品国产乱码在线观看 | 日韩精品一区二区三区在线视频 | 国产免费三级在线观看 | 成人三级视频 | 91视频免费 | 91成人破解版 | 国产一区二区三区免费视频 | 玖玖国产精品视频 | 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | 500部大龄熟乱视频 欧美日本三级 | 久久久www成人免费毛片 | 欧美日韩aaaa | 色99视频 | 天天干亚洲 | 久久露脸国产精品 | 狠狠狠色丁香婷婷综合久久五月 | 婷婷免费在线视频 | aaa日本高清在线播放免费观看 | sm免费xx网站 | 在线精品亚洲一区二区 | 香蕉视频在线免费 | 久久人人干 | 操操爽| 久久人人爽人人人人片 | 黄色av大片 | 黄色毛片电影 | 热99久久精品 | 最新不卡av| 成年人在线观看视频免费 | 日韩精品一区二区三区三炮视频 | 97超碰人人模人人人爽人人爱 | 久久无码精品一区二区三区 | 黄色av免费看 | 四虎欧美| 九九免费精品视频在线观看 | 日韩av中文在线观看 | 日韩在线观看av | 日韩欧美精选 | 99中文视频在线 | 香蕉精品在线观看 | av免费电影在线观看 | 亚洲色图色 | 美女视频一区 | 免费久久99精品国产婷婷六月 | 成人免费在线视频观看 | av网站在线观看免费 | 狠狠狠色丁香综合久久天下网 | 久久久久久蜜av免费网站 | 伊人久久精品久久亚洲一区 | 久久不射电影网 | 97超碰人 | 国色天香在线 | 日韩黄色免费在线观看 | 在线之家免费在线观看电影 | 4438全国亚洲精品在线观看视频 | 99视频精品 | 精品一区 在线 | 色婷婷六月| 日日夜夜操操操操 | 国产在线精品一区二区 | 一区二区三区在线免费播放 | 久久伊人爱 | 91精品国产乱码在线观看 | 久久国产午夜精品理论片最新版本 | 午夜影院先 | 中文有码在线视频 | 亚洲精品玖玖玖av在线看 | 亚洲精品国产精品国自产在线 | 国产区av在线| 亚洲人xxx | 狠狠色丁婷婷日日 | 日韩精品一卡 | 一级黄色片在线免费观看 | 国产一二三在线视频 | 97免费 | 日日操日日 | 日韩特级毛片 | 91| 精品国产乱码久久久久久浪潮 | 久久久精品网 | 免费国产一区二区视频 | 91免费版成人 | 91理论片午午伦夜理片久久 | 色综合久久天天 | 一区久久久 | 国产精品久久久久久婷婷天堂 | 三级毛片视频 | 国产一区二区在线免费视频 | 夜夜骑日日 | 欧美日韩亚洲在线观看 | 久草免费在线观看视频 | 91视频免费视频 | 免费看片亚洲 | 日韩精品观看 | 亚洲精品456在线播放乱码 | 91成人精品一区在线播放 | 国产在线观看网站 | 99成人精品 | 国产精品18久久久久久久久久久久 | av+在线播放在线播放 | 夜夜躁狠狠燥 | 五月花婷婷 | 亚洲国产电影在线观看 | 精品一区二区在线免费观看 | 女人高潮特级毛片 | www国产亚洲精品久久网站 | 免费视频一二三区 | 亚洲国产美女精品久久久久∴ | 午夜国产影院 | 成人免费电影 | 狠狠色丁香婷婷综合 | 亚洲在线观看av | 黄色aa久久 | 精品久久久免费视频 | 精品毛片久久久久久 | 欧美男同网站 | 激情综合交 | 韩国av电影网 | 天天搞天天干天天色 | 在线免费三级 | japanesefreesex中国少妇 | www.69xx | 国产男女无遮挡猛进猛出在线观看 | 97精品国产91久久久久久久 | 亚洲香蕉视频 | 亚洲人精品午夜 | 97超碰免费在线观看 | 热久久这里只有精品 | 91亚洲综合| 午夜精品久久久久久 | 人人干天天干 | 婷婷成人综合 | 欧美综合久久 | 国产成人综合精品 | 综合天天| 亚洲成人午夜av | 91视频中文字幕 | 在线观看视频黄色 | 免费看三级 | av免费电影在线观看 | 视频在线观看入口黄最新永久免费国产 | 97国产一区| 91成人免费观看视频 | 日批视频| 欧美性春潮 | 在线观看91精品国产网站 | 欧美男男激情videos | 国产成人精品免费在线观看 | 久久视频免费在线观看 | 国产精品久久久久久久久久久久久 | 欧美日韩调教 | av电影在线播放 | 日日夜夜精品免费 | 狠狠色噜噜狠狠狠狠2022 | 91在线最新 | 日韩精品中字 | 麻豆精品在线视频 | 免费视频色 | 欧美日韩亚洲第一 | 91av福利视频 | 69视频在线播放 | 99热国产在线观看 | 精品国产综合区久久久久久 | 欧美精品免费在线观看 | 狠狠色丁香婷综合久久 | 精品久久久久久久久久久久久久久久 | 国产亚洲精品久久久久久电影 | 午夜美女网站 | 久久久精品网 | 在线成人免费电影 | 亚洲小视频在线观看 | 人人玩人人添人人澡超碰 | 国产99中文字幕 | 国产99久久九九精品 | 高清av免费观看 | 日韩久久精品 | 91精品国产乱码 | 视频在线观看日韩 | 精品一区二区在线播放 | 97在线观看 | 免费av在线网站 | wwwav视频 | www.av在线播放| 国产精品永久免费视频 | 国产成人精品一区二区在线观看 | 午夜久久久久久久久久影院 | 在线高清av | 亚洲午夜久久久久久久久久久 | 欧美日韩一区二区在线观看 | 日韩精品影视 | 97精品国产97久久久久久粉红 | 亚洲国产成人精品久久 | 九草视频在线观看 | 国产黄色片免费观看 | 日批网站在线观看 | 91视频免费观看 | 在线视频 国产 日韩 | 欧美嫩草影院 | av线上看 | 2020天天干夜夜爽 | 成人免费电影 | 日韩久久精品一区二区三区 | 999电影免费在线观看 | 国产精品毛片一区 | 日韩av在线资源 | 97精品超碰一区二区三区 | 久久国内精品视频 | 日韩一区二区三区免费视频 | 四虎在线观看精品视频 | 在线观看免费av网站 | 免费在线观看成年人视频 | 91黄色免费网站 | 久久免费精品国产 | 国产精品久久久久久久久久久免费看 | 久久精品国产一区 | 久久99精品久久久久久久久久久久 | 国产69精品久久99不卡的观看体验 | 一区二区中文字幕在线播放 | 色资源二区在线视频 | 免费在线黄网 | 91色偷偷 | 精品99999 | 91精品国产综合久久福利不卡 | 欧美成人精品三级在线观看播放 | 久久久久久片 | 欧美一性一交一乱 | 丁香婷婷综合五月 | av福利在线播放 | 五月天婷婷综合 | 久久久精选 | 久久精品国产一区二区电影 | 久久超碰99 | 91亚洲国产成人久久精品网站 | 亚洲精品国产精品国自产 | 国产精品久久一区二区无卡 | 日韩亚洲国产精品 | 精品在线视频观看 | 欧美专区日韩专区 | 日韩av线观看 | 一级黄色片在线 | 国产五月天婷婷 | 国产精品免费观看久久 | 人人干网 | 免费av观看 | 国产视频18 | 亚洲精品在线电影 | 免费观看高清 | 国产日本亚洲高清 | 精品少妇一区二区三区在线 | 欧美精品久久久久久久久久丰满 | 99视频网站 | 911香蕉视频 | 超碰在线资源 | 欧美一区二区免费在线观看 | 免费在线a | 亚洲精品大全 | 九九九热精品 | 人人澡人人添人人爽一区二区 | 免费看国产一级片 | 亚洲不卡av一区二区三区 | 久久久福利影院 | 国产精品视频地址 | 久久久久久久影院 | 国产精品嫩草影院99网站 | 欧美精品免费在线观看 | 日韩在线电影 | 午夜精品区| 三级av网站 | 超级碰碰碰免费视频 | www.福利视频 | 91污污 | 国产1级毛片 | 2020天天干天天操 | 国产1区2区3区在线 亚洲自拍偷拍色图 | 日韩在线小视频 | 97人人澡人人添人人爽超碰 | 日韩网站在线播放 | 久久九九精品久久 | 在线观影网站 | 最新动作电影 | 精品美女国产在线 | 欧美伦理电影一区二区 | 久草在线视频网站 | 国产不卡精品 | 四虎最新入口 | 国产视频欧美视频 | 人人澡人人添人人爽一区二区 | 中文字幕在线观看2018 | 精品国产不卡 | 日韩视频欧美视频 | 中文字幕 国产精品 | 久久五月婷婷综合 | 国产a精品| 国精产品999国精产品岳 | 国产成人精品久久久久蜜臀 | 中文字幕视频在线播放 | 国产精品99久久久久久宅男 | 久久99精品国产99久久 | 天堂素人在线 | 天天看天天干天天操 | 精品国产一区二区三区不卡 | 久久久久国产精品一区二区 | 亚洲特级片 | 91精品在线播放 | 国产精品久久久久久吹潮天美传媒 | 最近中文字幕视频网 | 国产无遮挡猛进猛出免费软件 | 三三级黄色片之日韩 | 久久福利小视频 | av电影不卡| 国产另类av| 在线视频精品 | 国产伦精品一区二区三区… | 精品久久久精品 | 在线观看一区 | 尤物九九久久国产精品的分类 | av女优中文字幕在线观看 | 99人久久精品视频最新地址 | 高清日韩一区二区 | 亚洲精品高清在线 | 免费v片 | 日韩欧美国产免费播放 | 日韩免费一级a毛片在线播放一级 | 久草在 | 免费看日韩 | 色姑娘综合| 欧美激情视频一区二区三区 | 亚洲va欧洲va国产va不卡 | 国产精品手机看片 | 99久久激情视频 | 中文字幕 91 | 狠狠操狠狠 | 欧美日韩久久久 | 91精品国产高清自在线观看 | 亚洲激情 在线 | 亚洲1区 在线 | 国产伦精品一区二区三区照片91 | 免费观看国产精品视频 | 国产第一二区 | 久久国产香蕉视频 | 欧洲成人免费 | 久久9999久久免费精品国产 | 免费亚洲电影 | 久久免费视频在线观看6 | 国产区精品在线观看 | 成人h视频在线 | 欧美日韩国产一区二 | 亚洲在线视频网站 | 国产精品久久网站 | 国产日韩欧美视频在线观看 | 国产在线免费 | 久久字幕 | 国产在线观看99 | 99视频网站| 中文字幕色婷婷在线视频 | 久久狠狠婷婷 | 免费av大片 | 亚洲综合五月 | 国产无套精品久久久久久 | 夜夜操天天摸 | 国产91精品看黄网站 | av 一区二区三区四区 | 在线视频精品播放 | 一区二区三区福利 | 天天色天天上天天操 | 中文字幕第一页av | 国产精成人品免费观看 | 亚洲一一在线 | 午夜精品999 | 99九九99九九九视频精品 | 日韩中文字幕在线 | 久草久热 | 国产精品高清免费在线观看 | 国产一级二级在线播放 | 成年人免费在线播放 | 欧美精品少妇xxxxx喷水 | 99久免费精品视频在线观看 | 午夜精品福利一区二区三区蜜桃 | 久草国产在线 | 在线三级中文 | 天天综合网在线观看 | 97久久久免费福利网址 | 中文字幕亚洲欧美日韩 | 欧美伦理电影一区二区 | 免费一级片久久 | 精品亚洲视频在线 | 啪啪av在线| 中文字幕中文字幕在线中文字幕三区 | 亚州性色 | 麻花豆传媒一二三产区 | 久久久影视 | 国产老太婆免费交性大片 | 亚洲激情在线播放 | 亚洲精品乱码久久久一二三 | 人人玩人人添人人澡97 | 91麻豆视频网站 | 超碰人人乐 | 91桃色国产在线播放 | 亚欧洲精品视频在线观看 | 欧美精品久久久久久 | 日韩视频在线观看视频 | 在线色亚洲 | 天天干天天插伊人网 | 青青草国产成人99久久 | 久久久精品国产免费观看同学 | 欧美精品乱码久久久久久按摩 | 一级免费片 | 亚洲一级影院 | av片免费播放 | 日批视频在线播放 | 国产精品k频道 | 中文字幕资源网 国产 | 国产一区国产精品 | 麻豆高清免费国产一区 | 国产高清不卡 | 成人亚洲欧美 | 00av视频| 91黄视频在线| 天天插天天操天天干 | 久久在线播放 | h视频在线看 | 国产自产高清不卡 | 久久综合精品国产一区二区三区 | 日本精品视频一区二区 | 六月色丁 | 视频国产一区二区三区 | 激情欧美丁香 | 91成人在线看 | 精品国产一区二区三区久久久 | 又色又爽又黄 | 天天干com| 国产99久久九九精品免费 | 久草在线这里只有精品 | 又黄又爽免费视频 | 伊人天堂网 | 九九九在线观看 | 最近2019中文免费高清视频观看www99 | 欧美精品xx | 在线成人小视频 | 亚洲婷婷综合色高清在线 | 国产成人精品久久亚洲高清不卡 | 国产一区二区久久 | 波多野结衣资源 | 精品国产精品一区二区夜夜嗨 | 国产99在线免费 | 精品主播网红福利资源观看 | 国产精品免费一区二区三区在线观看 | 久久精品国产免费观看 | 午夜精品久久久久久久久久久久 | av在线免费播放 | 婷婷在线看 | 国产在线视频一区二区 | 国产精品字幕 | 国产看片网站 | 久草在线网址 | 国产亚洲在线观看 | 日本大片免费观看在线 | 国产在线日本 | 午夜久久成人 | 久色网 | 香蕉视频久久 | 在线观看日韩免费视频 | 亚洲专区在线视频 | 国产xxxxx在线观看 | 欧美国产日韩一区二区三区 | 精品福利视频在线观看 | 看全黄大色黄大片 | 国产第一页福利影院 | 91视频3p | 国产又粗又猛又色又黄视频 | 96久久久| 国产毛片在线 | 欧美成人播放 | 麻豆91小视频 | 九九电影在线 | 夜夜澡人模人人添人人看 | 精品99在线 | 精品久久久久久久久久久院品网 | www.色婷婷 | 国产女做a爱免费视频 | 精品国产伦一区二区三区免费 | 亚洲精品国产精品国自产在线 | 91免费版成人 | 黄色网在线播放 | 91c网站色版视频 | 高清不卡免费视频 | 狠狠综合久久 | 国产精品手机看片 | 国产视频18 | 亚洲 欧美 另类人妖 | 99久久99精品 | 国产色女人 | av一级久久| 99视屏| 日韩理论在线 | 中文字幕人成一区 | 久久国产精品免费一区二区三区 | 国产不卡一区二区视频 | 国产亚洲人 | 黄网站免费久久 | 欧美精品免费在线观看 | 日韩午夜在线观看 | 免费国产一区二区 | 91免费高清视频 | 成人中文字幕在线 | 97视频入口免费观看 | 不卡av免费在线观看 | 综合在线色 | 91在线在线观看 | 日本黄色大片免费看 | 丁五月婷婷 | 中文字幕视频网 | 手机成人av在线 | 亚洲一级免费观看 | 日韩精品在线视频免费观看 | 精品一区二区久久久久久久网站 | 夜夜操网| 99视频精品 | av片免费播放 | 91av在线电影 | 欧美性色xo影院 | 久久久久亚洲国产精品 | 国产精品女主播一区二区三区 | 1000部国产精品成人观看 | 丝袜足交在线 | 国产黄色片久久 | 久久久久高清毛片一级 | 国产成人在线一区 | 五月婷婷毛片 | 亚洲日本中文字幕在线观看 | 色婷婷午夜 | 狠狠操操操 | 日日日日 | 香蕉视频免费在线播放 | 在线亚州 | 国产精品久久久久久一区二区三区 | 在线91视频 | 精品高清美女精品国产区 | 97超碰成人在线 | 国产日韩欧美视频在线观看 | 激情综合一区 | 国产69精品久久久久9999apgf | 国产伦理一区二区 | 欧美一区在线观看视频 | 国产免费久久 | 97视频免费观看 | 国产精品福利视频 | 中文字幕在线久一本久 | 91一区啪爱嗯打偷拍欧美 | 亚洲精品视频在线观看视频 | 久久久久久久久久久精 | 欧美视频网址 | 中文字幕欧美激情 | 国产精品久久电影观看 | 网站免费黄 | www.亚洲精品 | 91丨九色丨国产丨porny精品 | 久草在线资源免费 | 69亚洲视频| 国产精品一区二区久久精品爱微奶 | 久久理论视频 | 国产精品日韩久久久久 | 香蕉视频18 | 日韩久久久久久久久久 | 日韩精品观看 | 亚洲日本一区二区在线 | 一级黄毛片| 手机av在线不卡 | 久久一区二 | 在线观看黄色av | 久草热视频| 欧美极品少妇xxxxⅹ欧美极品少妇xxxx亚洲精品 | 99热手机在线观看 | 九九九热精品免费视频观看网站 | 国产精品久久久久永久免费观看 | 夜夜夜夜操 | 日日爱网站| 在线成人高清电影 | www日韩在线 | 亚洲精品久久久久久久不卡四虎 | 亚洲欧美观看 | 91精品亚洲影视在线观看 | 永久免费的啪啪网站免费观看浪潮 | 国产精品精 | 日本三级久久久 | 日韩精品一区二区三区不卡 | 中文字幕在线观看第二页 | 亚洲国产mv| 亚洲精品av在线 | v片在线看 | 激情av五月婷婷 | 特片网久久 | 人人射人人爽 | 亚洲午夜小视频 | 成年人电影免费看 | 久久久精品国产免费观看一区二区 | 欧美三级免费 | 亚洲片在线观看 | 岛国精品一区二区 | www.综合网.com | 92av视频 | 91精选| 国产 在线 高清 精品 | 国产日韩欧美在线免费观看 | 久久视频在线观看中文字幕 | www欧美xxxx| 国产不卡在线播放 | 国产成人三级在线播放 | 色多视频在线观看 | 日日爱夜夜爱 | 日韩精品免费在线观看 | av一区二区三区在线观看 | 精品久久久成人 | 亚洲理论在线观看 | 久久在草 | 国产精品毛片网 | 欧美午夜精品久久久久 | 99精品视频99 | 亚洲国产一二三 | 亚洲一二视频 | 国产精品一区免费观看 | 国产v亚洲v | 制服丝袜一区二区 | 午夜精品三区 | 久久国产精品视频 | 国际精品久久 | 亚洲人在线7777777精品 | 成人黄色av网站 | 久草在线免费播放 | 国产xxxx | 色婷婷电影网 | 成人影片免费 | 亚洲1区 在线 | 国产91精品看黄网站 | 天天色宗合| 国产午夜精品一区二区三区欧美 | 一级免费黄色 | 欧美99热 | 91精品国产一区 | 免费在线91| 日韩精品视频网站 | 久草香蕉在线 | www亚洲精品 | 在线观看韩日电影免费 | 国产福利91精品张津瑜 | 国内精品视频一区二区三区八戒 | 五月婷婷中文网 | 热久久免费视频精品 | 久久伊人五月天 | 国产一区免费看 | 波多野结衣久久资源 | 国产精品免费视频一区二区 | 日韩中文字幕网站 | 日韩资源在线观看 | 一级片免费观看 | 国产日韩精品一区二区三区在线 | 亚洲精品视频在线观看免费视频 | 日韩中文字幕视频在线 | 亚州国产精品久久久 | 亚洲 综合 专区 | 日日麻批40分钟视频免费观看 | 激情深爱.com | 色网站在线 | 亚洲天天在线 | wwwwww黄 | 亚洲成av片人久久久 | 久久99国产综合精品 | 成人h动漫在线看 | 亚洲 中文 欧美 日韩vr 在线 | 最新国产精品拍自在线播放 | 激情网色 | 久色小说 | 久草免费在线视频 | 国产黄色免费观看 | 日韩不卡高清视频 | 热99在线视频 | 中文字幕电影在线 | 99这里都是精品 | 丁香六月婷婷激情 | 国产在线专区 | 免费看一及片 | 国产精品久久久久久久久免费看 | 天天操天天射天天添 | 国产精品成人一区二区 | 97人人爽人人 | 97色综合| 免费在线观看午夜视频 | 欧美影院久久 | 国产精品久久久久久久久久久久午夜片 | 特级黄色视频毛片 | 国内免费的中文字幕 | 日批视频在线观看免费 | 99精品欧美一区二区三区 | 成人h视频在线 | 亚洲一区久久久 | 五月婷久 | 九九九在线观看 | 超碰人人乐 | 久久久亚洲网站 | 一区二区三区高清在线 | 亚洲精品综合在线观看 | 日本激情动作片免费看 | 亚洲国产成人在线观看 | 国产精品99久久久久久宅男 | 日日夜日日干 | 精品国产伦一区二区三区观看体验 | 天天做天天爱夜夜爽 | 天天插天天 | 国产成人在线观看免费 | 久久免费的精品国产v∧ | 久久国内免费视频 | 婷婷夜夜| 精品福利在线视频 | 免费 在线 中文 日本 | 四虎永久精品在线 | 探花系列在线 | 免费的国产精品 | 欧美精品久 | 91亚洲精品乱码久久久久久蜜桃 | 久久免费国产视频 | 天天爽夜夜爽人人爽曰av | 国产精品久久久久久久午夜 | 91网在线看 | 国产福利精品一区二区 | 久草国产在线观看 | 最近最新最好看中文视频 | 99免在线观看免费视频高清 | 色婷婷88av视频一二三区 | av一级片 | 国产亚洲精品无 | 国产亚洲精品久久久久久久久久久久 | 在线a视频 | 国产一级片网站 | 婷婷亚洲激情 | 精品久久影院 | 国产一区精品在线观看 | 激情欧美一区二区三区 | 国产在线色视频 | 国产人成看黄久久久久久久久 | 国产精品第54页 | 91高清一区 | 日韩.com | 久久久久久久久影视 | 亚洲成aⅴ人片久久青草影院 | 五月激情久久 | 日本久久久久久久久久久 | 美女黄网久久 | 午夜视频在线观看网站 | 9在线观看免费高清完整版 玖玖爱免费视频 | av一级在线 | 欧美日韩亚洲精品在线 | 在线观看亚洲免费视频 | 国产毛片在线 | 欧美一级视频免费 | 欧美一二三在线 | 玖玖在线播放 | 在线精品视频免费观看 | 99产精品成人啪免费网站 | av大全在线免费观看 | 黄色毛片视频免费观看中文 | 亚洲欧美国产精品va在线观看 | 玖玖视频国产 | 91精品视频在线看 | 国产黄色片网站 | 日韩在线首页 | 超级av在线 | 亚洲国产剧情av | 国产激情免费 | 国产黄色av影视 | 成人性生活大片 | 日韩精品视频免费 | 欧美色888 | 午夜av网站 | 国产在线观看一区 | 人人射人人澡 | 国产精品久久精品国产 | 超碰在线观看av | 国产精彩在线视频 | 激情网站免费观看 | 在线观看免费色 | 国产免费亚洲高清 | 亚洲伦理电影在线 | av在线免费播放 | 午夜精品久久久久久久99婷婷 | 国产高潮久久 | 国产一级二级在线观看 | 中文字幕在线成人 | 亚洲最新av在线网站 | 伊人天堂久久 | 中文字幕色婷婷在线视频 | 久久久久久久久久久久久久免费看 | 91成人精品视频 | av中文字幕在线看 | 五月丁婷婷| aaa日本高清在线播放免费观看 | 久久免费国产精品1 | 在线观看完整版 | 国产精品理论片在线观看 | 精品国产成人 | 久久久网页| 国产香蕉97碰碰久久人人 | 久久99亚洲精品久久 | www.com.黄| av网站在线观看免费 | 亚洲在线不卡 | 精品久久久久久国产偷窥 | 黄污视频大全 | 天天干夜夜干 | 欧美最新另类人妖 | 91九色精品| www.99热精品| 狠狠色香婷婷久久亚洲精品 | 日韩中文久久 | 亚洲免费观看在线视频 | 在线国产视频 | 中文字幕久久精品亚洲乱码 | 99精品视频在线观看视频 | 色综合久久88色综合天天6 | 青青网视频| 日韩在线精品 | 99在线免费视频观看 | 蜜桃av人人夜夜澡人人爽 | 91精品久久香蕉国产线看观看 | 草草草影院 | 国产在线观看国语版免费 | 午夜精品视频一区二区三区在线看 | 美女久久久久久久久久久 | 九七视频在线 | 亚洲电影自拍 | 免费日韩一区二区 | 久久久国产精品人人片99精片欧美一 | 91精品对白一区国产伦 | 精品久久久久久亚洲 | 香蕉在线影院 | 免费在线观看av片 | 三上悠亚一区二区在线观看 | 蜜桃av人人夜夜澡人人爽 | 最新av在线播放 | www五月天婷婷 | 一级淫片在线观看 | 欧美精品天堂 | 97成人在线免费视频 | 视频在线精品 | 国产精品毛片网 | 国产精品国产三级国产aⅴ9色 | 九九九九热精品免费视频点播观看 | 精品一区二区久久久久久久网站 | 国产乱对白刺激视频在线观看女王 | 91成人免费在线视频 | 丁香花在线观看视频在线 | 日韩在线中文字幕视频 | 国产99久久99热这里精品5 | 最近日韩免费视频 | 亚洲第一香蕉视频 | 91亚洲综合 | 国产精品免费久久 | 国产露脸91国语对白 | 在线观看久久 | japanesexxxhd奶水 91在线精品一区二区 | 成人国产精品免费观看 | 99久久精品久久亚洲精品 | 久久资源总站 | 免费在线观看成年人视频 |