數(shù)據(jù)結(jié)構(gòu)(集合)

集合框架
1.常用容器繼承關(guān)系圖
Paste_Image.png
Iterator不是容器古徒,只是一個操作遍歷集合的方法
2.Collection和Map

在Java容器中一共定義了2種集合, 頂層接口分別是Collection和Map。但是這2個接口都不能直接被實現(xiàn)使用,分別代表兩種不同類型的容器钧唐。

Collection:是容器繼承關(guān)系中的頂層接口揖盘。是一組對象元素組。有些容器允許重復(fù)元素有的不允許贴硫,有些有序有些無序炮障。 JDK不直接提供對于這個接口的實現(xiàn),但是提供繼承與該接口的子接口比如 List Set坤候。
接口定義:

  public interface Collection<E> extends Iterable<E> {
      ...
  }

泛型<E>即該Collection中元素對象的類型胁赢,繼承的Iterable是定義的一個遍歷操作接口,采用hasNext next的方式進(jìn)行遍歷白筹。
幾個重要的接口方法:

  add(E e)確保此 collection 包含指定的元素(可選操作)智末。
  clear()移除此 collection 中的所有元素(可選操作)。
  contains(Object o)如果此 collection 包含指定的元素徒河,則返回 true系馆。
  isEmpty()如果此 collection 不包含元素,則返回 true顽照。
  iterator()返回在此 collection 的元素上進(jìn)行迭代的迭代器由蘑。
  remove(Object o)從此 collection 中移除指定元素的單個實例,如果存在的話(可選操作)代兵。
  retainAll(Collection<?> c)僅保留此 collection 中那些也包含在指定 collection 的元素(可選操作)尼酿。
  size()返回此 collection 中的元素數(shù)
  toArray()返回包含此 collection 中所有元素的數(shù)組
  toArray(T[] a)返回包含此 collection 中所有元素的數(shù)組;返回數(shù)組的運行時類型與指定數(shù)組的運行時類型相同

Map:一個保存鍵值映射的對象植影。 映射Map中不能包含重復(fù)的key裳擎,每一個key最多對應(yīng)一個value。
Map集合提供3種遍歷訪問方法:
1.獲得所有key的集合然后通過key訪問value思币。
2.獲得value的集合鹿响。
3.獲得key-value鍵值對的集合(key-value鍵值對其實是一個對象羡微,里面分別有key和value)。

Map的訪問順序取決于Map的遍歷訪問方法的遍歷順序惶我。 有的Map妈倔,比如TreeMap可以保證訪問順序,但是有的比如HashMap指孤,無法保證訪問順序启涯。

接口定義如下:

  public interface Map<K,V> {
      ...
      interface Entry<K,V> {
          K getKey();
          V getValue();
          ...
      }
  }

泛型<K,V>分別代表key和value的類型。這時候注意到還定義了一個內(nèi)部接口Entry恃轩,其實每一個鍵值對都是一個Entry的實例關(guān)系對象结洼,所以Map實際其實就是Entry的一個Collection,然后Entry里面包含key叉跛,value松忍。再設(shè)定key不重復(fù)的規(guī)則,自然就演化成了Map筷厘。(個人理解)

幾個重要的接口方法:

  clear()從此映射中移除所有映射關(guān)系
  containsKey(Object key)如果此映射包含指定鍵的映射關(guān)系鸣峭,則返回 true。
  containsValue(Object value)如果此映射將一個或多個鍵映射到指定值酥艳,則返回 true摊溶。
  entrySet()返回此映射中包含的映射關(guān)系的 Set 視圖。
  equals(Object o) 比較指定的對象與此映射是否相等充石。
  get(Object key)返回指定鍵所映射的值莫换;如果此映射不包含該鍵的映射關(guān)系,則返回 null骤铃。
  isEmpty()如果此映射未包含鍵-值映射關(guān)系拉岁,則返回 true。
  keySet()返回此映射中包含的鍵的 Set 視圖惰爬。
  put(K key, V value)將指定的值與此映射中的指定鍵關(guān)聯(lián)(可選操作)喊暖。
  putAll(Map<? extends K,? extends V> m)從指定映射中將所有映射關(guān)系復(fù)制到此映射中(可選操作)。
  remove(Object key)如果存在一個鍵的映射關(guān)系撕瞧,則將其從此映射中移除(可選操作)陵叽。
  size()返回此映射中的鍵-值映射關(guān)系數(shù)。
  values()返回此映射中包含的值的 Collection 視圖风范。

3個遍歷Map的方法:

1.Set<K> keySet():

會返回所有key的Set集合咨跌,因為key不可以重復(fù),所以返回的是Set格式硼婿,而不是List格式锌半。
獲取到所有key的Set集合后,由于Set是Collection類型的,所以可以通過Iterator去遍歷所有的key刊殉,然后再通過get方法獲取value
如下:

Map<String,String> map = new HashMap<String,String>();
map.put("01", "zhangsan");
map.put("02", "lisi");
map.put("03", "wangwu");

Set<String> keySet = map.keySet();//先獲取map集合的所有鍵的Set集合
Iterator<String> it = keySet.iterator();//有了Set集合殉摔,就可以獲取其迭代器。

while(it.hasNext()) {
     String key = it.next();
     String value = map.get(key);//有了鍵可以通過map集合的get方法獲取其對應(yīng)的值记焊。
     System.out.println("key: "+key+"-->value: "+value);//獲得key和value值
}
2.Collection<V> values():

直接獲取values的集合逸月,無法再獲取到key。所以如果只需要value的場景可以用這個方法遍膜。獲取到后使用Iterator去遍歷所有的value碗硬。
如下:

    Map<String,String> map = new HashMap<String,String>();
    map.put("01", "zhangsan");
    map.put("02", "lisi");
    map.put("03", "wangwu");

    Collection<String> collection = map.values();//返回值是個值的Collection集合
    System.out.println(collection);
3.Set< Map.Entry< K, V>> entrySet():

是將整個Entry對象作為元素返回所有的數(shù)據(jù)。然后遍歷Entry瓢颅,分別再通過getKey和getValue獲取key和value恩尾。
如下:

Map<String,String> map = new HashMap<String,String>();
map.put("01", "zhangsan");
map.put("02", "lisi");
map.put("03", "wangwu");

//通過entrySet()方法將map集合中的映射關(guān)系取出(這個關(guān)系就是Map.Entry類型)
Set<Map.Entry<String, String>> entrySet = map.entrySet();
//將關(guān)系集合entrySet進(jìn)行迭代,存放到迭代器中
Iterator<Map.Entry<String, String>> it = entrySet.iterator();

while(it.hasNext()) {
      Map.Entry<String, String> me = it.next();//獲取Map.Entry關(guān)系對象me
      String key = me.getKey();//通過關(guān)系對象獲取key
      String value = me.getValue();//通過關(guān)系對象獲取value
}

通過以上3種遍歷方式我們可以知道挽懦,如果你只想獲取key翰意,建議使用keySet。如果只想獲取value信柿,建議使用values冀偶。如果key value希望遍歷,建議使用entrySet渔嚷。
(雖然通過keySet可以獲得key再間接獲得value进鸠,但是效率沒entrySet高,不建議使用這種方法)

3.List形病、Set和Queue

List和Set堤如。他們2個是繼承Collection的子接口,就是說他們也都是負(fù)責(zé)存儲單個元素的容器窒朋。
最大的區(qū)別如下:
1.List是存儲的元素容器是有個有序的可以索引到元素的容器,并且里面的元素可以重復(fù)蝗岖。
2.Set里面和List最大的區(qū)別是Set里面的元素對象不可重復(fù)侥猩。

List:一個有序的Collection(或者叫做序列)。使用這個接口可以精確掌控元素的插入抵赢,還可以根據(jù)index獲取相應(yīng)位置的元素欺劳。
不像Set,list允許重復(fù)元素的插入铅鲤。有人希望自己實現(xiàn)一個list划提,禁止重復(fù)元素,并且在重復(fù)元素插入的時候拋出異常邢享,但是我們不建議這么做鹏往。

List提供了一種特殊的iterator遍歷器,叫做ListIterator骇塘。這種遍歷器允許遍歷時插入伊履,替換韩容,刪除,雙向訪問唐瀑。 并且還有一個重載方法允許從一個指定位置開始遍歷群凶。
List接口新增的接口,會發(fā)現(xiàn)add哄辣,get這些都多了index參數(shù)请梢,說明在原來Collection的基礎(chǔ)上,List是一個可以指定索引力穗,有序的容器毅弧。
在這注意以下添加的2個新Iteractor方法:
ListIterator<E> listIterator();
ListIterator<E> listIterator(int index);

ListIterator的代碼:

public interface ListIterator<E> extends Iterator<E> {
    // Query Operations

    boolean hasNext();

    E next();

    boolean hasPrevious();

    E previous();

    int previousIndex();

    void remove();

    void set(E e);

    void add(E e);
}

一個集合在遍歷過程中進(jìn)行插入刪除操作很容易造成錯誤,特別是無序隊列睛廊,是無法在遍歷過程中進(jìn)行這些操作的形真。
但是List是一個有序集合,所以在這實現(xiàn)了一個ListIteractor超全,可以在遍歷過程中進(jìn)行元素操作咆霜,并且可以雙向訪問。

List的實現(xiàn)類嘶朱,ArrayList和LinkedList.

1.ArrayList
ArrayList是一個實現(xiàn)了List接口的可變數(shù)組可以插入null它的size, isEmpty, get, set, iterator,add這些方法的時間復(fù)雜度是
O(1),如果add n個數(shù)據(jù)則時間復(fù)雜度是O(n).ArrayList不是synchronized的蛾坯。

然后我們來簡單看下ArrayList源碼實現(xiàn)。這里只寫部分源碼分析疏遏。
所有元素都是保存在一個Object數(shù)組中脉课,然后通過size控制長度。

       transient Object[] elementData;
       private int size;
       public boolean add(E e) {
           ensureCapacityInternal(size + 1);  // Increments modCount!!
           elementData[size++] = e;
           return true;
       }

       private void ensureCapacityInternal(int minCapacity) {
           if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
               minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
           }

           ensureExplicitCapacity(minCapacity);
       }

       private void ensureExplicitCapacity(int minCapacity) {
           modCount++;

           // overflow-conscious code
           if (minCapacity - elementData.length > 0)
               grow(minCapacity);
       }

       private void grow(int minCapacity) {
           // overflow-conscious code
           int oldCapacity = elementData.length;
           int newCapacity = oldCapacity + (oldCapacity >> 1);
           if (newCapacity - minCapacity < 0)
               newCapacity = minCapacity;
           if (newCapacity - MAX_ARRAY_SIZE > 0)
               newCapacity = hugeCapacity(minCapacity);
           // minCapacity is usually close to size, so this is a win:
           elementData = Arrays.copyOf(elementData, newCapacity);
       }

其實在每次add的時候會判斷數(shù)據(jù)長度财异,如果不夠的話會調(diào)用Arrays.copyOf倘零,復(fù)制一份更長的數(shù)組,并把前面的數(shù)據(jù)放進(jìn)去戳寸。
我們再看下remove的代碼是如何實現(xiàn)的呈驶。

public E remove(int index) {
      rangeCheck(index);
      modCount++;
      E oldValue = elementData(index);
      int numMoved = size - index - 1;
      if (numMoved > 0)
      System.arraycopy(elementData, index+1, elementData, index,numMoved);
      elementData[--size] = null; // clear to let GC do its work
      return oldValue;
}
其實就是直接使用System.arraycopy把需要刪除index后面的都往前移一位然后再把最后一個去掉。
2.LinkedList

LinkedList是一個鏈表維護的序列容器疫鹊。和ArrayList都是序列容器袖瞻,一個使用數(shù)組存儲,一個使用鏈表存儲拆吆。
數(shù)組和鏈表2種數(shù)據(jù)結(jié)構(gòu)的對比:
1.查找方面聋迎。數(shù)組的效率更高,可以直接索引出查找枣耀,而鏈表必須從頭查找霉晕。
2.插入刪除方面。特別是在中間進(jìn)行插入刪除,這時候鏈表體現(xiàn)出了極大的便利性娄昆,只需要在插入或者刪除的地方斷掉鏈
然后插入或者移除元素佩微,然后再將前后鏈重新組裝,但是數(shù)組必須重新復(fù)制一份將所有數(shù)據(jù)后移或者前移萌焰。
3.在內(nèi)存申請方面哺眯,當(dāng)數(shù)組達(dá)到初始的申請長度后,需要重新申請一個更大的數(shù)組然后把數(shù)據(jù)遷移過去才行扒俯。而鏈表只需
要動態(tài)創(chuàng)建即可奶卓。
如上LinkedList和ArrayList的區(qū)別也就在此.

總結(jié):

List實現(xiàn) 使用場景 數(shù)據(jù)結(jié)構(gòu)
ArrayList 數(shù)組形式訪問List鏈?zhǔn)郊蠑?shù)據(jù),元素可重復(fù)撼玄,訪問元素較快 數(shù)組
LinkedList 鏈表方式的List鏈?zhǔn)郊隙峁茫乜芍貜?fù),元素的插入刪除較快 雙向鏈表
Vector: 底層是數(shù)組數(shù)據(jù)結(jié)構(gòu)掌猛。線程同步盏浙。被ArrayList替代了。因為效率低荔茬。

4.Set

Set的核心概念就是集合內(nèi)所有元素不重復(fù)废膘。在Set這個子接口中沒有在Collection特別實現(xiàn)什么額外的方法,應(yīng)該只是定義了一個Set概念慕蔚。下面我們來看Set的幾個常用的實現(xiàn)HashSet丐黄、LinkedHashSet、TreeSet

HashSet:
HashSet實現(xiàn)了Set接口孔飒,基于HashMap進(jìn)行存儲灌闺。遍歷時不保證順序,并且不保證下次遍歷的順序和之前一樣坏瞄。HashSet中允許null元素桂对。

進(jìn)入到HashSet源碼中我們發(fā)現(xiàn),所有數(shù)據(jù)存儲在:
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
意思就是HashSet的集合其實就是HashMap的key的集合鸠匀,然后HashMap的val默認(rèn)都是PRESENT接校。HashMap的定義即是key不重復(fù)的集合。使用HashMap實現(xiàn)狮崩,這樣HashSet就不需要再實現(xiàn)一遍。
所以所有的add鹿寻,remove等操作其實都是HashMap的add睦柴、remove操作。遍歷操作其實就是HashMap的keySet的遍歷,舉例如下

    ...
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }

    public boolean contains(Object o) {
        return map.containsKey(o);
    }

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

    public void clear() {
        map.clear();
    }
    ...

LinkedHashSet:
LinkedHashSet的核心概念相對于HashSet來說就是一個可以保持順序的Set集合毡熏。HashSet是無序的坦敌,LinkedHashSet會根據(jù)add,remove這些操作的順序在遍歷時返回固定的集合順序。這個順序不是元素的大小順序狱窘,而是可以保證2次遍歷的順序是一樣的杜顺。
類似HashSet基于HashMap的源碼實現(xiàn),LinkedHashSet的數(shù)據(jù)結(jié)構(gòu)是基于LinkedHashMap蘸炸。過多的就不說了躬络。

TreeSet:
TreeSet即是一組有次序的集合,如果沒有指定排序規(guī)則Comparator搭儒,則會按照自然排序穷当。(自然排序即e1.compareTo(e2) == 0作為比較)
注意:TreeSet內(nèi)的元素必須實現(xiàn)Comparable接口。
TreeSet源碼的算法即基于TreeMap淹禾,具體算法在說明TreeMap的時候進(jìn)行解釋馁菜。

總結(jié):

Set實現(xiàn) 使用場景 數(shù)據(jù)結(jié)構(gòu)
HashSet 無序的、無重復(fù)的數(shù)據(jù)集合 基于HashMap
LinkedSet 維護次序的HashSet 基于LinkedHashMap
TreeSet 保持元素大小次序的集合铃岔,元素需要實現(xiàn)Comparable接口 基于TreeMap

4.HashMap汪疮、LinkedHashMap、TreeMap和WeakHashMap
HashMap就是最基礎(chǔ)最常用的一種Map毁习,它無序智嚷,以散列表的方式進(jìn)行存儲。之前提到過蜓洪,HashSet就是基于HashMap纤勒,只使用了HashMap的key作為單個元素存儲。
HashMap的訪問方式就是繼承于Map的最基礎(chǔ)的3種方式隆檀,詳細(xì)見上摇天。在這里我具體分析一下HashMap的底層數(shù)據(jù)結(jié)構(gòu)的實現(xiàn)。

在看HashMap源碼前恐仑,先理解一下他的存儲方式-散列表(哈希表)泉坐。像之前提到過的用數(shù)組存儲,用鏈表存儲裳仆。哈希表是使用數(shù)組和鏈表的組合的方式進(jìn)行存儲腕让。(具體哈希表的概念自行搜索)如下圖就是HashMap采用的存儲方法。

Map實現(xiàn) 使用場景 數(shù)據(jù)結(jié)構(gòu)
HashMap 哈希表存儲鍵值對歧斟,key不重復(fù)纯丸,無序 哈希散列表
LinkedHashMap 是一個可以記錄插入順序和訪問順序的HashMap 存儲方式是哈希散列表,但是維護了頭尾指針用來記錄順序
TreeMap 具有元素排序功能 紅黑樹
WeakHashMap 弱鍵映射静袖,映射之外無引用的鍵觉鼻,可以被垃圾回收 哈希散列表

詳細(xì):http://www.reibang.com/p/047e33fdefd2

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市队橙,隨后出現(xiàn)的幾起案子坠陈,更是在濱河造成了極大的恐慌萨惑,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件仇矾,死亡現(xiàn)場離奇詭異庸蔼,居然都是意外死亡,警方通過查閱死者的電腦和手機贮匕,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進(jìn)店門姐仅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人粗合,你說我怎么就攤上這事萍嬉。” “怎么了隙疚?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵壤追,是天一觀的道長。 經(jīng)常有香客問我供屉,道長行冰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任伶丐,我火速辦了婚禮悼做,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哗魂。我一直安慰自己肛走,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布录别。 她就那樣靜靜地躺著朽色,像睡著了一般。 火紅的嫁衣襯著肌膚如雪组题。 梳的紋絲不亂的頭發(fā)上葫男,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音崔列,去河邊找鬼梢褐。 笑死,一個胖子當(dāng)著我的面吹牛赵讯,可吹牛的內(nèi)容都是我干的盈咳。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼边翼,長吁一口氣:“原來是場噩夢啊……” “哼鱼响!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起讯私,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤热押,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后斤寇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體桶癣,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年娘锁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡汗侵,死狀恐怖催什,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情镊屎,我是刑警寧澤惹挟,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站缝驳,受9級特大地震影響连锯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜用狱,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一运怖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧夏伊,春花似錦摇展、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至砸狞,卻和暖如春捻勉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背刀森。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工踱启, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人研底。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓埠偿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親榜晦。 傳聞我的和親對象是個殘疾皇子冠蒋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容