先講下什么是數據結構
程序設計= 數據結構 + 算法
數據結構由以下構成:
- 數 據:是描述客觀事物的符號,是計算機可以操作的對象,是能被計算機識別,並輸入給計算機處理的符號集合,數據不僅僅指我們通常所說的數值類型, 還包括字符及聲音,圖像,視頻等非數值類型 。
- 數 據 項:在數據結構中,我們把數據項定義為最小單位,是有助於我們更好的解決問題。
- 數據元素:一個數據元素可以由若干個數據項組成,並且有一定意義的基本單位,在計算機中通常作為整體處理,也被成為記錄。例如如人由眼,耳,手,鼻,口,腳,這些數據項 數據項是數據不可分割的最小單位,組成一個數據元素。數據元素才是我們在數據結構建立數據模型的着眼點。
- 數據類型:在一種程序設計語言中,變量所具有的數據種類。整型、浮點型、字符型等等
- 物理結構/存儲結構:數據在計算機中的表示。物理結構是描述數據具體在內存中的存儲(如:順序結構、鏈式結構、索引結構、哈希結構等。
- 邏輯結構:數據之間的相互關系。在數據結構中,從邏輯上可以將其分為線性結構和非線性結構
- 集合 結構中的數據元素除了同屬於一種類型外,別無其它關系。
- 線性結構 數據元素之間一對一的關系
- 樹形結構 數據元素之間一對多的關系
- 圖狀結構或網狀結構 結構中的數據元素之間存在多對多的關系
總結:數據結構就是研究數據的邏輯結構和物理結構以及它們之間相互關系,並對這種結構定義相應的運算,而且確保經過這些運算后所得到的新結構仍然是原來的結構類型。
算法的概念:
算法:算法是計算機處理信息的本質,因為計算機程序本質上是一個算法來告訴計算機確切的步驟來執行一個指定的任務。
算法的五大特性
- 輸入: 算法具有0個或多個輸入。
- 輸出: 算法至少有1個或多個輸出。
- 有窮性: 算法在有限的步驟之后會自動結束而不會無限循環,並且每一個步驟可以在可接受的時間內完成。
- 確定性:算法中的每一步都有確定的含義,不能出現歧義。
- 可行性:算法的每一步都是可行的,也就是說每一步都能夠執行有限的次數完成。
java中數據結構有什么用?
當你用着java里面的容器類很爽的時候,你有沒有想過,怎么ArrayList就像一個無限擴充的數組,也好像鏈表之類的。好用嗎?好用,這就是數據結構的用處,只不過你在不知不覺中使用了。
現實世界的存儲,我們使用的工具和建模。每種數據結構有自己的優點和缺點,想想如果Google的數據用的是數組的存儲,我們還能方便地查詢到所需要的數據嗎?而算法,在這么多的數據中如何做到最快的插入,查找,刪除,也是在追求更快。
我們java是面向對象的語言,就好似自動檔轎車,C語言好似手動檔吉普。數據結構呢?是變速箱的工作原理。你完全可以不知道變速箱怎樣工作,就把自動檔的車子從 A點 開到 B點,而且未必就比懂得的人慢。寫程序這件事,和開車一樣,經驗可以起到很大作用,但如果你不知道底層是怎么工作的,就永遠只能開車,既不會修車,也不能造車。當然了,數據結構內容比較多,細細的學起來也是相對費功夫的,不可能達到一蹴而就。我們將常見的數據結構:堆棧、隊列、數組、鏈表和紅黑樹 這幾種給大家介紹一下。
數據存儲的常用結構有:棧、隊列、數組、鏈表和紅黑樹。我們分別來了解一下:
棧
- 棧:stack,又稱堆棧,它是運算受限的線性表,其限制是僅允許在標的一端進行插入和刪除操作,不允許在其他任何位置進行添加、查找、刪除等操作。簡單的說:采用該結構的集合,對元素的存取有如下的特點
- 先進后出(即,存進去的元素,要在后它后面的元素依次取出后,才能取出該元素)。例如,子彈壓進彈夾,先壓進去的子彈在下面,后壓進去的子彈在上面,當開槍時,先彈出上面的子彈,然后才能彈出下面的子彈。
- 棧的入口、出口的都是棧的頂端位置。
- 壓棧:就是存元素。即,把元素存儲到棧的頂端位置,棧中已有元素依次向棧底方向移動一個位置。
- 彈棧:就是取元素。即,把棧的頂端位置元素取出,棧中已有元素依次向棧頂方向移動一個位置。
隊列
- 隊列:queue,簡稱隊,它同堆棧一樣,也是一種運算受限的線性表,其限制是僅允許在表的一端進行插入,而在表的另一端進行刪除。
簡單的說,采用該結構的集合,對元素的存取有如下的特點:
- 先進先出(即,存進去的元素,要在后它前面的元素依次取出后,才能取出該元素)。例如,小火車過山洞,車頭先進去,車尾后進去;車頭先出來,車尾后出來。
- 隊列的入口、出口各占一側。例如,下圖中的左側為入口,右側為出口。
數組
- 數組:Array,是有序的元素序列,數組是在內存中開辟一段連續的空間,並在此空間存放元素。就像是一排出租屋,有100個房間,從001到100每個房間都有固定編號,通過編號就可以快速找到租房子的人。
簡單的說,采用該結構的集合,對元素的存取有如下的特點:
- 查找元素快:通過索引,可以快速訪問指定位置的元素。
-
增刪元素慢,指定索引位置增加元素:需要創建一個新數組,將指定新元素存儲在指定索引位置,再把原數組元素根據索引,復制到新數組對應索引的位置。如下圖
- 指定索引位置刪除元素:**需要創建一個新數組,把原數組元素根據索引,復制到新數組對應索引的位置,原數組中指定索引位置元素不復制到新數組中。如下圖
紅黑樹
- 二叉樹**:binary tree ,是每個結點不超過2的有序樹(tree) 。
簡單的理解,就是一種類似於我們生活中樹的結構,只不過每個結點上都最多只能有兩個子結點。
二叉樹是每個節點最多有兩個子樹的樹結構。頂上的叫根結點,兩邊被稱作“左子樹”和“右子樹”。
如圖:
我們要說的是二叉樹的一種比較有意思的叫做紅黑樹,紅黑樹本身就是一顆二叉查找樹,將節點插入后,該樹仍然是一顆二叉查找樹。也就意味着,樹的鍵值仍然是有序的。
紅黑樹的約束:
-
節點可以是紅色的或者黑色的
-
根節點是黑色的
-
葉子節點(特指空節點)是黑色的
-
每個紅色節點的子節點都是黑色的
-
任何一個節點到其每一個葉子節點的所有路徑上黑色節點數相同
紅黑樹的特點:
速度特別快,趨近平衡樹,查找葉子元素最少和最多次數不多於二倍
好了開始說正事哈:
java集合體系:
單例集合 的體系:
---------| Collection 單例集合的根接口
------------------| List 如果是實現了List接口的集合類, 具備的特點:有序,重復。
-------------------------| ArraryList 底層 是使用了Object數組實現的,特點: 查詢速度快,增刪慢。
-------------------------| LinkedList 底層是使用了鏈表數據結構實現 的, 特點: 查詢速度慢,增刪快。
-------------------------| Vector Vector的實現與ArrayList是一致,但是是線程安全 的,操作效率低。 jdk1.0的時候出現的
------------------| Set 如果是實現了Set接口的集合類,具備的特點:無序,不可重復。
--------------------------| HashSet 底層是使用了一個哈希表支持的, 特點:存取速度快。
---------------------------------| LinkedHashSet 繼承了HashSet 鏈表和哈希表組合的一個數據存儲結構。 特點:有順序的插入。
--------------------------| TreeSet 底層是使用了紅黑樹(二叉樹)數據結構實現的, 特點:會對元素進行排序存儲。(目前已經被淘汰了。)
雙列集合:
-------------| Map 如果是實現了Map接口的集合類,具備的特點: 存儲的數據都是以鍵值對的形式存在的,鍵不可重復,值可以重復。
----------------------| HashMap<K,V>:
--------------------------------| LinkedHashMap<K,V> :繼承HashMap,存儲數據采用的哈希表結構+鏈表結構。通過鏈表結構可以保證元素的存取順序一致;通過哈希表結構可以保證的鍵的唯一、不重復,需要重寫鍵的hashCode()方法、equals()方法。
----------------------| TreeMap
----------------------| Hashtable
1.1 List接口介紹
java.util.List
接口繼承自Collection
接口,是單列集合的一個重要分支,習慣性地會將實現了`List
`接口的對象稱為List集合。在List集合中允許出現重復的元素,所有的元素是以一種線性方式進行存儲的,在程序中可以通過索引來訪問集合中的指定元素。另外,List集合還有一個特點就是元素有序,即元素的存入順序和取出順序一致。
看完API,我們總結一下:
List接口特點:
-
它是一個元素存取有序的集合。例如,存元素的順序是11、22、33。那么集合中,元素的存儲就是按照11、22、33的順序完成的)。
-
它是一個帶有索引的集合,通過索引就可以精確的操作集合中的元素(與數組的索引是一個道理)。
-
集合中可以有重復的元素,通過元素的equals方法,來比較是否為重復的元素。
1.2 List接口中常用方法
List作為Collection集合的子接口,不但繼承了Collection接口中的全部方法,而且還增加了一些根據元素索引來操作集合的特有方法,如下:
-
public void add(int index, E element)
: 將指定的元素,添加到該集合中的指定位置上。 -
public E get(int index)
:返回集合中指定位置的元素。 -
public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。 -
public E set(int index, E element)
:用指定元素替換集合中指定位置的元素,返回值的更新前的元素。
List集合特有的方法
List的子類
------------------- ArrayList集合
java.util.ArrayList
集合數據存儲的結構是數組結構。元素增刪慢,查找快,由於日常開發中使用最多的功能為查詢數據、遍歷數據,所以ArrayList
是最常用的集合。
許多程序員開發時非常隨意地使用ArrayList完成任何需求,並不嚴謹,這種用法是不提倡的。
------------------- LinkedList集合
java.util.LinkedList
集合數據存儲的結構是鏈表結構。方便元素添加、刪除的集合。
LinkedList是一個雙向鏈表,那么雙向鏈表是什么樣子的呢,我們用個圖了解下
實際開發中對一個集合元素的添加與刪除經常涉及到首尾操作,而LinkedList提供了大量首尾操作的方法。這些方法我們作為了解即可:
-
public void addFirst(E e)
:將指定元素插入此列表的開頭。 -
public void addLast(E e)
:將指定元素添加到此列表的結尾。 -
public E getFirst()
:返回此列表的第一個元素。 -
public E getLast()
:返回此列表的最后一個元素。 -
public E removeFirst()
:移除並返回此列表的第一個元素。 -
public E removeLast()
:移除並返回此列表的最后一個元素。 -
public E pop()
:從此列表所表示的堆棧處彈出一個元素。 -
public void push(E e)
:將元素推入此列表所表示的堆棧。 -
public boolean isEmpty()
:如果列表不包含元素,則返回true。
LinkedList是List的子類,List中的方法LinkedList都是可以使用,這里就不做詳細介紹,我們只需要了解LinkedList的特有方法即可。在開發時,LinkedList集合也可以作為堆棧,隊列的結構使用。
Set接口
java.util.Set
接口和java.util.List
接口一樣,同樣繼承自Collection
接口,它與Collection
接口中的方法基本一致,並沒有對Collection
接口進行功能上的擴充,只是比Collection
接口更加嚴格了。與List
接口不同的是,Set
接口中元素無序,並且都會以某種規則保證存入的元素不出現重復。
Set
集合有多個子類,這里我們介紹其中的java.util.HashSet
、java.util.LinkedHashSet
這兩個集合。
tips:Set集合取出元素的方式可以采用:迭代器、增強for。
HashSet集合介紹
java.util.HashSet
是Set
接口的一個實現類,它所存儲的元素是不可重復的,並且元素都是無序的(即存取順序不一致)。java.util.HashSet
底層的實現其實是一個java.util.HashMap
支持,由於我們暫時還未學習,先做了解。
HashSet
是根據對象的哈希值來確定元素在集合中的存儲位置,因此具有良好的存取和查找性能。保證元素唯一性的方式依賴於:hashCode
與equals
方法。
我們先來使用一下Set集合存儲,看下現象,再進行原理的講解:
輸出結果如下,說明集合中不能存儲重復元素:
tips:根據結果我們發現字符串"cba"只存儲了一個,也就是說重復的元素set集合不存儲。
HashSet集合存儲數據的結構(哈希表)
什么是哈希表呢?
在JDK1.8之前,哈希表底層采用數組+鏈表實現,即使用鏈表處理沖突,同一hash值的鏈表都存儲在一個鏈表里。但是當位於一個桶中的元素較多,即hash值相等的元素較多時,通過key值依次查找的效率較低。而JDK1.8中,哈希表存儲采用數組+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換為紅黑樹,這樣大大減少了查找時間。
簡單的來說,哈希表是由數組+鏈表+紅黑樹(JDK1.8增加了紅黑樹部分)實現的,如下圖所示。
看到這張圖就有人要問了,這個是怎么存儲的呢?
為了方便大家的理解我們結合一個存儲流程圖來說明一下:
總而言之,JDK1.8引入紅黑樹大程度優化了HashMap的性能,那么對於我們來講保證HashSet集合元素的唯一,其實就是根據對象的hashCode和equals方法來決定的。如果我們往集合中存放自定義的對象,那么保證其唯一,就必須復寫hashCode和equals方法建立屬於當前對象的比較方式。
HashSet存儲自定義類型元素
給HashSet中存放自定義類型元素時,需要重寫對象中的hashCode和equals方法,建立自己的比較方式,才能保證HashSet集合中的對象唯一
創建自定義Student類
LinkedHashSet
我們知道HashSet保證元素唯一,可是元素存放進去是沒有順序的,那么我們要保證有序,怎么辦呢?
在HashSet下面有一個子類java.util.LinkedHashSet
,它是
演示代碼如下:
TreeSet(目前已經被淘汰了)
------------------| TreeSet 底層是使用了紅黑樹(二叉樹)數據結構實現的, 特點:會對元素進行排序存儲。
TreeSet要注意的事項:
1. 往TreeSet添加元素的時候,如果元素本身具備自然順序的特性,那么會根據元素自然順序的特性進行排序存儲。
2. 往TreeSet添加元素的時候,如果元素本身不具備自然順序的特性,那么元素所屬的類必須要實現Comparable接口,把元素的比較規則定義
在CompareTo方法上。
3. 往TreeSet添加元素的時候,如果元素本身不具備自然順序的特性,而且元素所屬的類沒有實現COmparable接口,那么必須要在創建
TreeSet對象的時候傳入比較器。
4. 如果比較的方法(CompareTo 或者Compare )返回的是0的時候,那么該元素就被視為重復元素,不允許添加。
比較器的定義格式: 自定義一個類實現COmparator接口即可。
class 類名 implements Comparator{
}
泛型
泛型:泛型是jdk1.5出現的新特性。
泛型的好處:
1. 將運行時出現 的問題提前至了編譯時。
2. 避免了無謂強制類型轉換。
自定義泛型: 自定義泛型就是一個數據類型的占位符或者理解為一個數據類型的變量。
泛型方法:
修飾符 <聲明自定義的泛型>返回值類型 函數名(自定義的泛型 變量名..)
泛型方法要注意的事項:
1. 泛型方法中 的自定義泛型的具體數據類型是在調用該函數的時候傳入實參時確定的。
2. 自定義泛型所用 的標識符只要符合標識符 的命名規則即可。但是我們一般都習慣使用一個大寫字母表示。
泛型類:
泛型類的定義格式
class 類名<聲明自定義的泛型>{
}
泛型類要注意的事項:
1. 泛型類上的自定義泛型是在使用該類創建對象的時候指定具體的數據類型的。
2. 如果一個類已經自定義了泛型,使用該類創建對象的時候如果沒有指定泛型的具體數據類型,那么默認為Object類型。
3. 靜態的函數不能使用類上自定義的泛型,如果靜態函數需要使用,必須要在函數上自定義泛型。
泛型接口:
泛型接口的定義格式:
interface 接口名<聲明自定義的泛型>{
}
泛型接口要注意事項:
1. 泛型接口上的自定義泛型是在實現該接口的時候指定具體數據類型的。
2. 如果實現接口的時候沒有指定接口上 的自定義泛型的具體數據類型,那么默認為Object數據類型。
3. 如果需要在創建接口實現類對象的時候才指定接口上自定義泛型,那么需要以下格式: class<T> 類名 implements 接口<T>
泛型上下限:
? super Integer 允許是Integer數據類型或者是Integer父類類型 泛型的下限
? extedns Number 允許是Number數據類型或者是Number子類的數據類型 泛型的上限。
雙列集合:
-------------| Map 如果是實現了Map接口的集合類,具備的特點: 存儲的數據都是以鍵值對的形式存在的,鍵不可重復,值可以重復。
-------------------| HashMap
-----------------------------| LinkedHashMap
------------------| TreeMap
------------------| Hashtable
Map接口中的常用方法
-
public V put(K key, V value)
: 把指定的鍵與指定的值添加到Map集合中。 -
public V remove(Object key)
: 把指定的鍵 所對應的鍵值對元素 在Map集合中刪除,返回被刪除元素的值。 -
public V get(Object key)
根據指定的鍵,在Map集合中獲取對應的值。 -
boolean containsKey(Object key)
判斷集合中是否包含指定的鍵。 -
public Set<K> keySet()
: 獲取Map集合中所有的鍵,存儲到Set集合中。 -
public Set<Map.Entry<K,V>> entrySet()
: 獲取到Map集合中所有的鍵值對對象的集合(Set集合)。
添加:
put(K key, V value)
putAll(Map<? extends K,? extends V> m)
刪除
remove(Object key)
clear()
獲取:
get(Object key)
size()
判斷:
containsKey(Object key)
containsValue(Object value)
isEmpty()
Map接口的方法演示
tips:
使用put方法時,若指定的鍵(key)在集合中沒有,則沒有這個鍵對應的值,返回null,並把指定的鍵值添加到集合中;
若指定的鍵(key)在集合中存在,則返回值為集合中鍵對應的值(該值為替換前的值),並把指定鍵所對應的值,替換成指定的新值。
Map集合遍歷鍵找值方式
鍵找值方式:即通過元素中的鍵,獲取鍵所對應的值
分析步驟:
-
獲取Map中所有的鍵,由於鍵是唯一的,所以返回一個Set集合存儲所有的鍵。方法提示:
keyset()
-
遍歷鍵的Set集合,得到每一個鍵。
-
根據鍵,獲取鍵所對應的值。方法提示:
get(K key)
代碼演示:
遍歷圖解:
Entry鍵值對對象
我們已經知道,Map
中存放的是兩種對象,一種稱為key(鍵),一種稱為value(值),它們在在Map
中是一一對應關系,這一對對象又稱做Map
中的一個Entry(項)
。Entry
將鍵值對的對應關系封裝成了對象。即鍵值對對象,這樣我們在遍歷Map
集合時,就可以從每一個鍵值對(Entry
)對象中獲取對應的鍵與對應的值。
既然Entry表示了一對鍵和值,那么也同樣提供了獲取對應鍵和對應值得方法:
-
public K getKey()
:獲取Entry對象中的鍵。 -
public V getValue()
:獲取Entry對象中的值。
在Map集合中也提供了獲取所有Entry對象的方法:
-
public Set<Map.Entry<K,V>> entrySet()
: 獲取到Map集合中所有的鍵值對對象的集合(Set集合)。
Map集合遍歷鍵值對方式
鍵值對方式:即通過集合中每個鍵值對(Entry)對象,獲取鍵值對(Entry)對象中的鍵與值。
操作步驟與圖解:
-
獲取Map集合中,所有的鍵值對(Entry)對象,以Set集合形式返回。方法提示:
entrySet()
。 -
遍歷包含鍵值對(Entry)對象的Set集合,得到每一個鍵值對(Entry)對象。
-
通過鍵值對(Entry)對象,獲取Entry對象中的鍵與值。 方法提示:
getkey() getValue()
-
遍歷圖解:
tips:Map集合不能直接使用迭代器或者foreach進行遍歷。但是轉成Set之后就可以使用了。
HashMap
HashMap的存儲原理:
往HashMap添加元素的時候,首先會調用鍵的hashCode方法得到元素 的哈希碼值,然后經過運算就可以算出該
元素在哈希表中的存儲位置。
情況1: 如果算出的位置目前沒有任何元素存儲,那么該元素可以直接添加到哈希表中。
情況2:如果算出 的位置目前已經存在其他的元素,那么還會調用該元素的equals方法與這個位置上的元素進行比較
,如果equals方法返回 的是false,那么該元素允許被存儲,如果equals方法返回的是true,那么該元素被視為
重復元素,不允存儲。
編寫測試類:
-
當給HashMap中存放自定義對象時,如果自定義對象作為key存在,這時要保證對象唯一,必須復寫對象的hashCode和equals方法(如果忘記,請回顧HashSet存放自定義對象)。
-
如果要保證map中存放的key和取出的順序一致,可以使用
java.util.LinkedHashMap
集合來存放。
LinkedHashMap
我們知道HashMap保證成對元素唯一,並且查詢速度很快,可是成對元素存放進去是沒有順序的,那么我們要保證有序,還要速度快怎么辦呢?
在HashMap下面有一個子類LinkedHashMap,它是鏈表和哈希表組合的一個數據存儲結構。
結果:
TreeMap(基本沒人使用了)
TreeMap TreeMap也是基於紅黑樹(二叉樹)數據結構實現 的, 特點:會對元素的鍵進行排序存儲。
TreeMap 要注意的事項:
1. 往TreeMap添加元素的時候,如果元素的鍵具備自然順序,那么就會按照鍵的自然順序特性進行排序存儲。
2. 往TreeMap添加元素的時候,如果元素的鍵不具備自然順序特性, 那么鍵所屬的類必須要實現Comparable接口,把鍵
的比較規則定義在CompareTo方法上。
3. 往TreeMap添加元素的時候,如果元素的鍵不具備自然順序特性,而且鍵所屬的類也沒有實現Comparable接口,那么就必須
在創建TreeMap對象的時候傳入比較器
常見面試題 HashMap 和 HashTable 有什么區別?
- HashMap 是線程不安全的,HashMap 是一個接口,是 Map 的一個子接口,是將鍵映射到值得對象,不允許鍵值重復,允許空鍵和空值;由於非線程安全,HashMap 的效率要較 HashTable 的效率高一些.
- HashTable 是線程安全的一個集合,不允許 null 值作為一個 key 值或者 Value 值;
- HashTable 是 sychronize,多個線程訪問時不需要自己為它的方法實現同步,而 HashMap 在被多個線程訪問的時候需要自己為它的方法實現同步
最后再賦兩張圖