1. 程式人生 > >Java集合框架中隱藏的設計套路

Java集合框架中隱藏的設計套路

我們的世界不應該只有“胡蘿蔔”

這裡寫圖片描述

進入正題之前容我先扯點別的。

最近突然想到了一個驢子和胡蘿蔔不得不說的故事。說是一個人坐在驢子背上,用一根長杆綁著一根胡蘿蔔,然後把胡蘿蔔懸到驢子的面前,驢子以為只要向前走一步就可以吃到胡蘿蔔,於是不停地向前走,可是它始終無法吃到這根蘿蔔。

一千個讀者就有一千個哈姆雷特,當然不同的人對這個故事也會有不同的理解。比如我們為了生活拼命地工作,卻永遠達不到財務自由,我們是不是也像一隻忙碌的“驢子”呢?

所以,我們的世界不應該只有“胡蘿蔔”

不識廬山真面目,只緣身在此山中。有時候跳出來,用“上帝視角”來看看這個我們存在的世界,說不定會有不一樣的發現。生活如此,學習也是如此。

資料結構和演算法與Java集合框架

接下來我們再來看一個故事。本故事純屬虛構,如有雷同,純屬你抄我。

時間:2017/01/11

地點:A公司辦公室

人物:小明——一個工作一年的Android小菜鳥;B大大——A公司Android開發負責人,Android高階開發工程師

起因:小明想到A公司工作,由B大大對他進行面試。

經過:B大大說,小明啊,你剛畢業一年,演算法應該還不錯,咱們先來一個簡單的吧。有一個迷宮,由n行m列的單元格組成,每一個單元格要麼是空地要麼是障礙物,障礙物是不能穿過的。任取兩個不是障礙物並且不相同的點p和q,怎麼找到p到q的最短路徑呢?

小明支支吾吾,邊撓頭邊說,那個B大大,我上學的時候貪玩沒有好好學資料結構和演算法,畢業後工作中也基本沒有用到過,所有不怎麼會了。。。

B大大露出一副安慰的表情,說,恩,可以理解。那咱們問點工作中常用的吧。

小明頓時來了精神,一頓小雞啄米似的點頭。

B大大說,那你先說說你工作中接觸到的資料結構吧。

小明張口就來,陣列、ArrayList、Map。

B大大又等了一會,看小明實在沒有了下文,又繼續問道,那你瞭解Java集合框架的設計思路嗎?

小明說,設計思路?為什麼要了解,沒時間啊,老夫寫程式碼就是一把梭!複製、貼上,拿起鍵盤就是幹!效率剛剛的。

B大大心裡一萬隻羊駝飛奔而過,嘴角抖了抖,說,那個小明,咱們今天的面試就先到這吧,有結果了我再讓Hr通知你好吧。

然後,然後就沒有然後了。

雖然這個故事是虛構的,但是不難找出來現實版的小明求職記。那麼從這個故事我們可以反思一些什麼呢?

首先個人認為資料結構和演算法、設計模式這些屬於內功,俗話說練拳不練功,到老一場空,有了這些內功我們才能更好的去使用各種招式,否則只是徒有其形罷了。要知道一個花架子是沒有多少戰鬥力的。

其次,瞭解了原始碼裡的設計思路,用起來才能更得心應手,同時也能提高自己的設計能力。而且就像開篇說的,我們要善於從巨集觀的角度去看一些事情,這樣才能看到更多,收穫更多。當然成長為高階工程師,迎娶白富美,走向人生巔峰不是夢啦。

資料結構和演算法

這裡我不打算再過多的重複資料結構和演算法的定義、演算法的時空複雜度這些問題,只過一下各個資料結構的特性。因為演算法對資料結構的通用操作類似,基本都包括插入、查詢、刪除、遍歷和排序等,所以我們重點關注下這些操作上的效能。

  • 陣列:優點是查詢快,如果知道下標,可以非常快的存取。缺點是插入慢,刪除慢,大小固定。
  • 有序陣列:優點是比無序陣列查詢快。缺點是插入和刪除慢,大小固定。
  • 棧:提供後進先出方式的存取。缺點是存取其他項很慢。
  • 佇列:提供先進先出方式的存取。缺點是存取其他項很慢。
  • 連結串列:優點是插入快,刪除快。缺點是查詢慢。
  • 二叉樹:優點是查詢、插入、刪除都快(如果樹保持平衡)。缺點是刪除演算法複雜。
  • 紅-黑樹:優點是查詢、插入、刪除都快,樹總是平衡的。缺點是演算法複雜。
  • 2-3-4樹:優點是查詢、插入、刪除都快,樹總是平衡的,類似的樹對磁碟儲存有用。缺點是演算法複雜。
  • 雜湊表:優點是如果關鍵字已知,則存取極快,插入快。缺點是刪除慢,如果不知道關鍵字則存取很慢,對儲存空間使用不充分。
  • 堆:優點是插入、刪除快,對最大資料項的存取很快。缺點是對其他資料項存取慢。
  • 圖:對現實世界建模。有些演算法慢且複雜。

Java中集合框架的總體設計

良好的設計總是相似的。它使用一個好用的介面來封裝一個特定的功能,它有效的使用CPU與記憶體,等等。

我們常用的資料結構有線性表、棧、佇列等線性結構,還有集合以及雜湊表,所以我們只討論這幾種結構的設計。

在分析Java集合框架的設計思路之前,我們先來認真思考一個問題,如果讓你去設計Java的集合框架,你會怎麼做?

小明的做法

如果是小明來設計的話,我猜他會選擇一種簡單粗暴的方式,分別去寫幾個類來實現線性表、棧、佇列、集合以及雜湊表。好,那麼問題來了。

  1. 假如使用者想自定義一個用自己方式實現的線性表,那麼他該如何操作?
  2. 假如開始使用者使用了無序的線性表,然後因為某個需求要改成有序的,那麼使用者需不需要進行很大改動呢?
  3. 假如小明要對已有的Api進行升級,要加入無序線性表的另一種更高效能的實現,他需要改動多少東西?

其實把三個問題總結一下無非就是維護和擴充套件成本的問題。

我們的思路

首先我們先來參考一下其他功能的設計思路,比如Android中的View類族Context類族。我們分析一下他們的程式碼的結構層次

View類族的類圖如下

Context類族的類圖如下

我們來分下一下這兩個模組設計上相似的地方

  • 整體的程式碼結構都像一棵樹,有一個唯一的根節點,這個根節點封裝了這個類族的公有特性
  • 有一層抽象類或者類似抽象類作用的類,它們實現了通用的方法。方便使用者擴充套件自己的業務。
  • 有具體的實現,使用者可以直接使用這些具體實現。

這些相似的地方其實可以歸納為三個結構層次

  1. 一個高度抽象的根節點介面,可以再抽象出一組帶有具體操作的介面來實現我們的根節點
  2. 一組抽象類,實現帶有具體操作介面的部分方法,方便使用者快速擴充套件自己的業務。
  3. 具體的實現,方便使用者直接呼叫。

這個套路在Android原始碼中是很常見的,這樣做的好處也顯而易見,比較易於維護和擴充套件

原始碼的實現思路

然後我們來看看是不是像我們說的那樣,Java的集合框架也是這種套路呢?

我們來看下集合框架的類圖

這裡寫圖片描述

這張圖是我從網上找的,不過不影響我們的分析。如果有侵權,請告訴我,我會刪除。

從這張圖我們可以很清晰的看出來,套路一模一樣有沒有。

首先由於Map和Collection的相似點很少,所以這兩部分的根節點是分開的。

我們拿Collection這部分來說,首先定義了一組介面。Collection是List、Set等集合高度抽象出來的介面,它包含了這些集合的基本操作,它主要又分為:List、Set和Queue,分別對應線性表、集合、佇列,實現對應的介面,則有了對應資料結構的特性。這是第一個結構層次。

然後又定義了一組抽象類,我們拿AbstractCollection類來說,先看下注釋

/**
 * This class provides a skeletal implementation of the <tt>Collection</tt>
 * interface, to minimize the effort required to implement this interface. <p>
 */

這個類實現了Collection介面的骨架,使用者繼承這個類可以用最小化的時間來實現一個集合。

其他的抽象類也都是各個資料結構的骨架,使用者可以自定義自己的集合。

最後第三個層次就是具體的實現類了,比如我們常用的ArrayList、LinkedList等等。比如LinkedList實現了List和Queue介面,那麼它既有佇列先進先出的特性,又有List可以通過位置訪問元素的特性。

原始碼的具體實現

Collection部分

Collection是List、Set等集合高度抽象出來的介面,它包含了這些集合的基本操作,它主要又分為兩大部分:List和Set。

Collection介面
/**
 * The root interface in the <i>collection hierarchy</i>.  A collection
 * represents a group of objects, known as its <i>elements</i>.  Some
 * collections allow duplicate elements and others do not.  Some are ordered
 * and others unordered.  The JDK does not provide any <i>direct</i>
 * implementations of this interface: it provides implementations of more
 * specific subinterfaces like <tt>Set</tt> and <tt>List</tt>.  This interface
 * is typically used to pass collections around and manipulate them where
 * maximum generality is desired.
 */

集合層次結構的根。一個集合包含一組元素物件。有一些集合允許重複元素,有一些不允許;有一些集合元素是有序的有一些不是。

定義的方法:

  • int size()
  • boolean isEmpty()
  • boolean contains(Object o)
  • Iterator iterator()
  • Object[] toArray()
  • T[] toArray(T[] a)
  • boolean add(E e)
  • boolean remove(Object o)
  • boolean containsAll(Collection c)
  • boolean addAll(Collection c)
  • boolean removeAll(Collection c)
  • boolean retainAll(Collection c)
  • void clear()
Iterator介面

迭代器介面,用於遍歷集合。

  • boolean hasNext()
  • E next()
  • default void remove()
  • default void forEachRemaining(Consumer action)
AbstractCollection抽象類
/**
 * This class provides a skeletal implementation of the <tt>Collection</tt>
 * interface, to minimize the effort required to implement this interface. <p>
 *
 * To implement an unmodifiable collection, the programmer needs only to
 * extend this class and provide implementations for the <tt>iterator</tt> and
 * <tt>size</tt> methods.  (The iterator returned by the <tt>iterator</tt>
 * method must implement <tt>hasNext</tt> and <tt>next</tt>.)<p>
 *
 * To implement a modifiable collection, the programmer must additionally
 * override this class's <tt>add</tt> method (which otherwise throws an
 * <tt>UnsupportedOperationException</tt>), and the iterator returned by the
 * <tt>iterator</tt> method must additionally implement its <tt>remove</tt>
 * method.<p>
 *
 * The programmer should generally provide a void (no argument) and
 * <tt>Collection</tt> constructor, as per the recommendation in the
 * <tt>Collection</tt> interface specification.<p>
 *
 * The documentation for each non-abstract method in this class describes its
 * implementation in detail.  Each of these methods may be overridden if
 * the collection being implemented admits a more efficient implementation.<p>
 */

這個類實現了Collection介面的骨架,使用者繼承這個類可以用最小化的時間來實現一個集合。

如果我們需要實現一個簡單的集合,只需要重寫iterator()、int size()和add(E o)方法。同時這個集合的特性取決於我們的實現方式。

List介面

線性表(List):零個或多個數據元素的有限序列。

/**
 * An ordered collection (also known as a <i>sequence</i>).  The user of this
 * interface has precise control over where in the list each element is
 * inserted.  The user can access elements by their integer index (position in
 * the list), and search for elements in the list.<p>
 */

一個有序的集合,使用者可以通過這個介面精確的控制在哪裡插入元素。使用者可以通過元素的int下標來拿到元素和查詢List中的元素。可以有重複元素。

List介面的擴充套件方法:

  • add(int index, E element)
  • addAll(int index, Collection c)
  • E get(int index)
  • int indexOf(Object o)
  • int lastIndexOf(Object o)
  • ListIterator listIterator()
  • ListIterator listIterator(int index)
  • E remove(int index)
  • default void replaceAll(UnaryOperator operator)
  • E set(int index, E element)
  • default void sort(Comparator c)
ListIterator介面
/**
 * An iterator for lists that allows the programmer
 * to traverse the list in either direction, modify
 * the list during iteration, and obtain the iterator's
 * current position in the list. A {@code ListIterator}
 * has no current element; its <I>cursor position</I> always
 * lies between the element that would be returned by a call
 * to {@code previous()} and the element that would be
 * returned by a call to {@code next()}.
 */

一個為List設計的迭代器,允許使用者從任意方向遍歷List,在遍歷的過程中修改List,並且獲得迭代器的當前位置。

ListIterator的擴充套件方法:

  • boolean hasPrevious()
  • E previous()
  • int nextIndex()
  • int previousIndex()
  • void set(E e)
  • void add(E e)
Set介面

集合(Set):是標記著具有某些相關聯或相互依賴的一系列離散資料。

/**
 * A collection that contains no duplicate elements.  More formally, sets
 * contain no pair of elements <code>e1</code> and <code>e2</code> such that
 * <code>e1.equals(e2)</code>, and at most one null element.  As implied by
 * its name, this interface models the mathematical <i>set</i> abstraction.
 */

一個不包含重複元素的集合。所謂不重複是指不能有兩個元素e1.equals(e2),並且至多包含一個null元素。

Set介面只包含繼承自Collection的方法,並增加了重複的元素被禁止約束性

還增加了對equals和hashCode操作的行為更強的契約,允許Set集合例項進行有意義的比較,即使他們的實現型別不同。

Queue

佇列(Queue):是隻允許在一端進行插入操作,而在另一端進行刪除操作的線性表。

/**
 * A collection designed for holding elements prior to processing.
 * Besides basic {@link java.util.Collection Collection} operations,
 * queues provide additional insertion, extraction, and inspection
 * operations.  Each of these methods exists in two forms: one throws
 * an exception if the operation fails, the other returns a special
 * value (either {@code null} or {@code false}, depending on the
 * operation).  The latter form of the insert operation is designed
 * specifically for use with capacity-restricted {@code Queue}
 * implementations; in most implementations, insert operations cannot
 * fail.
 */

佇列的介面,提供了Collection之外的插入、提取和檢索操作。這三種操作都有兩種形式,一種失敗之後會丟擲異常,另一種會返回特定的值(null或者false)。

這裡寫圖片描述

Map部分

Map是一個對映介面,其中的每個元素都是一個key-value鍵值對

Map介面

雜湊技術是在記錄的儲存位置和它的關鍵字之間建立一個確定的對應關係f,使得每個關鍵字key對應一個儲存位置f(key)。

對應關係f稱為雜湊函式,又稱為雜湊(Hash)函式。

採用雜湊技術將記錄儲存在一塊連續的儲存空間中,這塊連續的儲存空間稱為散列表或雜湊表(Hash table)。

/**
 * An object that maps keys to values.  A map cannot contain duplicate keys;
 * each key can map to at most one value.
 */

一個匹配key和value的物件。

包含的主要方法:

  • int size()
  • boolean isEmpty()
  • boolean containsKey(Object key)
  • boolean containsValue(Object value)
  • V get(Object key)
  • V put(K key, V value)
  • V remove(Object key)
  • putAll(Map m)
  • clear()
  • Set keySet()
  • Collection values()
  • entrySet()
  • boolean equals(Object o)
  • int hashCode()

Map介面中包含一個Entry介面,這是對Map一個條目的封裝即一個鍵值對。可以通過它操作條目的鍵值。

AbstractMap

實現Map介面的骨架,減小使用者建立自定義Map的成本。

如果你想建立一個自己的可以修改的Map,比如重寫V put(K key, V value)和V remove(Object key)方法以及實現entrySet().iterator()。

SortedMap介面
/**
 * A {@link Map} that further provides a <em>total ordering</em> on its keys.
 * The map is ordered according to the {@linkplain Comparable natural
 * ordering} of its keys, or by a {@link Comparator} typically
 * provided at sorted map creation time.  This order is reflected when
 * iterating over the sorted map's collection views (returned by the
 * {@code entrySet}, {@code keySet} and {@code values} methods).
 * Several additional operations are provided to take advantage of the
 * ordering.  (This interface is the map analogue of {@link SortedSet}.)
 */

提供了進一步對Map的key進行排序的操作。

擴充套件的方法:

  • Comparator comparator()
  • K firstKey()
  • K lastKey()
  • SortedMap headMap(K toKey)
  • SortedMap subMap(K fromKey, K toKey)
  • SortedMap tailMap(K fromKey)

關注博主是一種態度,評論博主是一種欣賞!!

最後,歡迎大家關注我的微信公眾號:CoderTopia。