java集合類(Set、List、Map)的一些事

一、java集合類的整體繼承關(guān)系

image.png

可以看出java集合的頂層接口分兩類:Collection,Map
兩個工具類:Collections,Arrays
Collection接口中定義了15種方法:


image.png

1.8之后還新增了
Stream<E> stream()
Stream<E> parallelStream()
stream與parallelStream可以理解為一種高級版的Iterator?;趕tream的函數(shù)式變成可以讓程序變的更簡潔。
下面是一個獲取list集合中字符串長度大于4的程序,分別比較了三種方式普通的,基于stream及parallelStream的運行比較

public static void main(String[] args){
        List<String> handles = new ArrayList<>();
        for (int i= 0;i<2000000;i++){
            List<String> lists = Arrays.asList(new String[]{"abcdsd","bcd","efgdddd","你是誰啊啊啊啊啊","b你爸的加拉水電費"});
            handles.addAll(lists);
        }

        long start0 = System.currentTimeMillis();
        List<String> result0 = new ArrayList<>();
        for (int i=0; i<handles.size(); i++){
            if (handles.get(i).length() > 4){
                result0.add(handles.get(i).substring(1));
            }
        }
        long end0 = System.currentTimeMillis();
        System.out.println("noStream運行時間" + (end0-start0)); //2890


        long start1 = System.currentTimeMillis();
        List<String> result = handles.stream().filter(e->e.length() > 4).map(e->e.substring(1)).collect(toList());
        long end1 = System.currentTimeMillis();
        System.out.println("stream運行時間:" + (end1-start1));//2899

        long start2 = System.currentTimeMillis();
        handles.parallelStream().filter(e->e.length() > 4).map(e->e.substring(1)).collect(toList());
        long end2 = System.currentTimeMillis();
        System.out.println("parallelStream" + (end2-start2));//2500
    }

nostream與stream速度相當,parallelStream速度要比

Map接口與Collection接口地位相同,都是根接口,Map中提供了3個角度來分析Map
Set<K> keySet():所有key的集合
Collections<V> values():所有value的集合
Set<Map.Entry<k,v>> entrySet():key和value的集合,
需要遍歷map集合時建議使用第三種方式,因為這種方式是構(gòu)造map集合的方式,最節(jié)省時間,第一種方式獲取key,value需要遍歷2次。第二種方式只能獲取value的值,它的速度與entry的速度應該差不多。
阿里編程規(guī)范中遍歷Map的key-Value對時使用EntrySet()的方式

二、Set、List、Map的幾種常用的實現(xiàn)類及注意事項

1、List接口是一個元素有序的、可以重復、可以為 null 的集合,常用的實現(xiàn)類,ArrayList,LinkedList這兩個實現(xiàn)類的底層的存儲結(jié)構(gòu),ArrayList采用的是數(shù)組結(jié)構(gòu),


image.png

LinkedList采用的是雙向鏈表結(jié)構(gòu)。


image.png

下面是ArrayList及LinkedList的add、set、及remove操作
1、ArrayList

public void add(int index,E element){ 
    /*檢查下標是否越界,代碼不在拷貝*/ 
    //若需要擴容,則增大底層數(shù)組的長度 
    ensureCapacity(size + 1); 
    //給index下標之后的元素(包括當前元素)的下標加1,空出index位置(將elementData從index起始,復制到index+1的職位 
    System.arraycopy(elementData,index,elementData,index + 1,size - index); 
    //賦值index位置元素 
    elementData[index] = element; 
    //列表的長度+1 
    size++; 
} 

注:這里ArrayList會進行擴容判斷,如果需要擴容就會以當前容量的1.5倍進行擴容。在用淺拷貝的方式對數(shù)組元素進行后移。
也是由于這個原因,阿里的開發(fā)手冊中有了下面這條建議。


image.png
public E remove(int index){ 
    //下標校驗 
    RangeCheck(index); 
    //修改計數(shù)器+1 
    modCount++; 
    //記錄要刪除的元素 
    E oldValue = (E)elementData(index); 
    //有多少個元素向前移動 
    int numMoved = size - index - 1; 
    if(numMoved > 0) 
        //index后的元素向前移動一位 
        System.arraycopy(elementData,index + 1,elementData,index,numMoved); 
    //列表長度減1,并且最后一位設為null 
    elementData[--size] = null; 
    //返回刪除的值 
    return oldValue; 
} 

這是arrayList的刪除操作,同樣需要對數(shù)組元素進行部分移動。

與之對應的LinkedList的操作如下:

public void add(int index,E element){ 
    addBefore(element,(index==size?header:entry(index))); 
} 

private Entry<E> addBefore(E e,Entry<E> entry){ 
    //組裝一個新的節(jié)點,previous指向原節(jié)點的前節(jié)點,next指向原節(jié)點 
    Entry<E> newEntry = new Entry<E>(e,entry,entry.previous); 
    //前節(jié)點的next指向自己 
    newEntry.previous.next = newEntry; 
    //后節(jié)點的previous指向自己 
    newEntry.next.previous = newEntry; 
 
    //長度+1 
    size++; 
    //修改計數(shù)器+1 
    modCount ++; 
 
    return newEntry; 
} 
private E remove(Entry<E> e){ 
    //取得原始值 
    E result = e.element; 
    //前節(jié)點next指向當前節(jié)點的next 
    e.previous.next = e.next; 
    //后節(jié)點的previouse指向當前節(jié)點的previous 
    e.next.previous = e.previous; 
 
    //置空當前節(jié)點的next和previous 
    e.next = e.previous= null; 
    //當前元素置空 
    e.element = null; 
 
    //列表長度減1 
    size --; 
    //修改計數(shù)器+1 
    modCount++; 
 
    return result; 
} 

結(jié)論:在數(shù)據(jù)需要進行大量刪除及新增的時候,LinkedList的速度要比ArrayList的快很多建議使用ArrayList,在數(shù)據(jù)查詢及修改指定元素值時,ArrayList速度要比LinkedList速度快。
注意:在使用ArrayList的sublist()方法時需要注意,不能將sublist()集合強轉(zhuǎn)成ArrayList否則會報java.util.RandomAccessSubList cannot be cast to java.util.ArrayList異常,因為Sublist這個內(nèi)部類沒有繼承ArrayList

public void test(){
 List<String> items = Arrays.asList(new String[]{"a","b","c"});
      List<String> results = new ArrayList<>();
      results.addAll(items);
      List<String> sublist = results.subList(0,3);
      sublist.remove(1);
      for (String str : results){
          System.out.println(str);
      }
}

最后輸出的結(jié)果是a,c,因此asList()方法并沒有創(chuàng)建新的集合,還是引用的原來的內(nèi)存。

2、set是一種無序不可重值可以為null集合,它的實現(xiàn)類有hashSet及LinkedHashSet,當需要對元素進行去重存儲時,可以選擇set集合。

3、hashMap:Node<K,V> table, Set<Map.Entry<K,V>> entrySet
傳統(tǒng)hashMap的缺點:它的存儲結(jié)構(gòu)是數(shù)組+鏈表的形式,當大量的元素hash值相同時會被放入同一個桶中,此時的就相當于一個單鏈表查找時間復雜度為o(n).

java1.8之前hashMap的存儲使用的是數(shù)組+鏈表,即使哈希函數(shù)取得再好,也很難達到元素百分百均勻分布。在1.8之后采用紅黑樹來優(yōu)化這個問題查找時間復雜度為(Ologn),在1.8中hashmap的存儲的具體過程在鏈表的長度小于8時任然采用之前的數(shù)據(jù)接口當長度大于8是采用紅黑樹結(jié)構(gòu)。紅黑樹本質(zhì)上是一種二叉查找樹,但它在二叉查找樹的基礎上額外添加了一個標記(顏色),同時具有一定的規(guī)則。這些規(guī)則可以保證紅黑樹結(jié)構(gòu)在插入、刪除、查找時的最壞時間復雜度為 O(logn)。(紅黑樹的詳細介紹https://blog.csdn.net/u011240877/article/details/53329023

image

原先的鏈表節(jié)點:

static class Node<K,V> implements Map.Entry<K,V> {
    //哈希值,就是位置
    final int hash;
    //鍵
    final K key;
    //值
    V value;
    //指向下一個幾點的指針
    Node<K,V> next;
    //...
}

jdk1.8中新增的紅黑樹結(jié)構(gòu)

static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;
}

加入紅黑樹之后的整個hashMap的put操作


image.png

3、被忽略的Set與Map的聯(lián)系:
Set代表的是一種集合元素無序,集合元素不可重復的集合,Map則代表一種由多個Key-Value對組成的集合,Map集合類似于傳統(tǒng)的關(guān)聯(lián)數(shù)組。表面上看,Map和Set毫無關(guān)聯(lián),但其實Map和Set之間有莫大的關(guān)聯(lián),可以說,Map是Set的擴展。
Set及Map的繼承體系


image.png

這張圖可以明顯地看出Set與Map有著極其相似的繼承體系。
實際上
Map的key是無序的不能重復的,所以Map的所有key的集合實際上就是一個Set集合,事實上Map中的keySet方法也是這樣實現(xiàn)的
從Set到Map,如果將(key-value)看做一個整體,將Entry放入到Set中,實際上Set就轉(zhuǎn)化成了Map集合。
利用Set實現(xiàn)一個Map集合就變得很簡單了
1、定義一個實體類存儲k-v

public class SimpleEntry<K,V> implements Map.Entry<K,V>,Serializable {
    private K key;
    private V value;

    public SimpleEntry(K key,V value){
        this.key = key;
        this.value = value;
    }
    public SimpleEntry(Map.Entry<? extends K,? extends V> entry){
        this.key = entry.getKey();
        this.value = entry.getValue();
    }
    @Override
    public K getKey() {
        return key;
    }

    @Override
    public V getValue() {
        return value;
    }

    @Override
    public V setValue(V value) {
        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }
    public boolean equals(Object object){
        if(object == this){
            return true;
        }
        if(object.getClass() == SimpleEntry.class){
            SimpleEntry se = (SimpleEntry) object;
            return se.getKey().equals(getKey());
        }
        return  false;
    }
    public int hashCode(){
       return  key == null?0:key.hashCode();
    }

    @Override
    public String toString() {
        return key+"="+value;
    }
}

2、繼承hashSet實現(xiàn)一個hashMap2

public class HashMap2<K,V> extends HashSet<SimpleEntry<K,V>> {
    @Override
    public void clear() {
        super.clear();
    }
     //判斷是否包含某個key
    public boolean containsKey(K key){
          return super.contains(new SimpleEntry<K, V>(key,null));
    }
    //判斷是否包含某個value
    public boolean containsValue(V value){
      for(SimpleEntry<K,V> se :this){
         if(se.getValue().equals(value)){
              return true;
          }
      }
        return false;
    }
    //根據(jù)Key取出Value
    public V get(K key){
           for(SimpleEntry<K,V> se:this){
               if(se.getKey().equals(key)){
                   return se.getValue();
               }
           }
        return null;
    }
    //存放入該Map中
    public V put(K key,V value){
       add(new SimpleEntry<K, V>(key,value));
        return value;
    }
    //存放一個Map的key-value對放入該Map中
    public void putAll(Map<? extends K,? extends V> map){
       for(K key:map.keySet()){
           add(new SimpleEntry<K, V>(key,map.get(key)));
       }
    }
    //根據(jù)指定key刪除指定key-value對
    public V removeEntry(K key){
       for(Iterator<SimpleEntry<K,V>> it = this.iterator();it.hasNext();){
         SimpleEntry<K,V> en = it.next();
           if(en.getKey().equals(key)){
               V v = en.getValue();
               it.remove();
               return  v;
           }
       }
        return null;
    }
    public int size(){
        return super.size();
    }
}

最后這是Map中實現(xiàn)類是否安全,能否鍵值為空的一個比較
具體的線程安全的實現(xiàn)細節(jié),有興趣的可以自己看一下,這里沒有做總結(jié)


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、集合入門總結(jié) 集合框架: Java中的集合框架大類可分為Collection和Map;兩者的區(qū)別: 1、Col...
    程序員歐陽閱讀 11,811評論 2 61
  • 本篇文章帶你從Java源碼深入解析關(guān)于Java容器的概念。 參考文獻: Java容器相關(guān)知識全面總結(jié) Java官方...
    Tsy遠閱讀 20,246評論 13 142
  • 第一章:醒來 一百年后:哎!好痛,睜開眼,入眼的是我的房間,我不是死在風天逸的懷里嗎?回憶,在這冰床上有個睡美人,...
    玉雪靜瀾衣閱讀 729評論 0 2
  • 如果能穿越的話,真希望能來到古代,給我一把劍,一匹馬,就可以縱橫天下,行走江湖…… 能動手解決問題...
    孤獨的丫頭閱讀 193評論 0 0
  • 不要手殘 手殘 手殘 上周四中馬傳動 周五看到情況不好應該平倉出的,既然已經(jīng)不看好 為何還加倉 上周五手欠打板江...
    霧尥閱讀 108評論 0 0

友情鏈接更多精彩內(nèi)容